support normal Vim mode

1. Support Insert/Normal/Visual/VisualLine modes:
    - `V`, `v`, `I`, `i`, `A`, `a`, `s`, `Esc`, `Ctrl+[`, `o`, `O`;
2. Support movement commands (with Repeat support):
    - `h`, `j`, `k`, `l`, `gj`, `gk`;
    - `gg`, `G`;
    - `^`, `0`, `$`;
    - `Ctrl+U`, `Ctrl+D`, `PageUp`, `PageDown`, `Ctrl+B`;
This commit is contained in:
Le Tan 2017-06-08 08:15:30 +08:00
parent 224a7253ce
commit a8c76d6742
17 changed files with 1643 additions and 667 deletions

View File

@ -14,7 +14,10 @@ background: 005fff
editor-current-line editor-current-line
background: c5cae9 background: c5cae9
vim-background: a5d6a7 vim-insert-background: c5cae9
vim-normal-background: a5d6a7
vim-visual-background: a5d6a7
vim-replace-background: a5d6a7
H1 H1
foreground: 111111 foreground: 111111

View File

@ -45,6 +45,9 @@ image_folder=_v_images
; Enable trailing space highlight ; Enable trailing space highlight
enable_trailing_space_highlight=true enable_trailing_space_highlight=true
; Enable Vim mode in edit mode
enable_vim_mode=false
[session] [session]
tools_dock_checked=true tools_dock_checked=true

View File

@ -64,7 +64,8 @@ SOURCES += main.cpp\
vimagepreviewer.cpp \ vimagepreviewer.cpp \
vexporter.cpp \ vexporter.cpp \
vmdtab.cpp \ vmdtab.cpp \
vhtmltab.cpp vhtmltab.cpp \
utils/vvim.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -115,7 +116,8 @@ HEADERS += vmainwindow.h \
vimagepreviewer.h \ vimagepreviewer.h \
vexporter.h \ vexporter.h \
vmdtab.h \ vmdtab.h \
vhtmltab.h vhtmltab.h \
utils/vvim.h
RESOURCES += \ RESOURCES += \
vnote.qrc \ vnote.qrc \

1010
src/utils/vvim.cpp Normal file

File diff suppressed because it is too large Load Diff

243
src/utils/vvim.h Normal file
View File

@ -0,0 +1,243 @@
#ifndef VVIM_H
#define VVIM_H
#include <QObject>
#include <QString>
#include <QTextCursor>
#include "vutils.h"
class VEdit;
class QKeyEvent;
class VEditConfig;
class QKeyEvent;
enum class VimMode {
Normal = 0,
Insert,
Visual,
VisualLine,
Replace,
Invalid
};
class VVim : public QObject
{
Q_OBJECT
public:
explicit VVim(VEdit *p_editor);
// Handle key press event.
// Returns true if the event is consumed and need no more handling.
bool handleKeyPressEvent(QKeyEvent *p_event);
// Return current mode.
VimMode getMode() const;
// Set current mode.
void setMode(VimMode p_mode);
signals:
// Emit when current mode has been changed.
void modeChanged(VimMode p_mode);
private slots:
// When user use mouse to select texts in Normal mode, we should change to
// Visual mode.
void selectionToVisualMode(bool p_hasText);
private:
// Struct for a key press.
struct Key
{
Key(int p_key, int p_modifiers = Qt::NoModifier)
: m_key(p_key), m_modifiers(p_modifiers)
{
}
int m_key;
int m_modifiers;
bool isDigit() const
{
return m_key >= Qt::Key_0
&& m_key <= Qt::Key_9
&& m_modifiers == Qt::NoModifier;
}
int toDigit() const
{
V_ASSERT(isDigit());
return m_key - Qt::Key_0;
}
bool operator==(const Key &p_key) const
{
return p_key.m_key == m_key && p_key.m_modifiers == m_modifiers;
}
};
// Supported actions.
enum class Action
{
Move = 0,
Delete,
Copy,
Paste,
Change,
Indent,
UnIndent,
ToUpper,
ToLower,
DeleteToClipboard,
CopyToClipboard,
PasteFromClipboard,
ChangeToClipboard,
Invalid
};
// Supported movements.
enum class Movement
{
Left = 0,
Right,
Up,
Down,
VisualUp,
VisualDown,
PageUp,
PageDown,
HalfPageUp,
HalfPageDown,
StartOfLine,
EndOfLine,
FirstCharacter,
LineJump,
StartOfDocument,
EndOfDocument,
Invalid
};
// Supported ranges.
enum class Range
{
Line = 0,
Word,
Invalid
};
enum class TokenType { Action = 0, Repeat, Movement, Range, Invalid };
struct Token
{
Token(Action p_action)
: m_type(TokenType::Action), m_action(p_action) {}
Token(int p_repeat)
: m_type(TokenType::Repeat), m_repeat(p_repeat) {}
Token(Movement p_movement)
: m_type(TokenType::Movement), m_movement(p_movement) {}
Token(Range p_range)
: m_type(TokenType::Range), m_range(p_range) {}
Token() : m_type(TokenType::Invalid) {}
bool isRepeat() const
{
return m_type == TokenType::Repeat;
}
bool isAction() const
{
return m_type == TokenType::Action;
}
bool isMovement() const
{
return m_type == TokenType::Movement;
}
bool isRange() const
{
return m_type == TokenType::Range;
}
QString toString() const
{
QString str;
switch (m_type) {
case TokenType::Action:
str = QString("action %1").arg((int)m_action);
break;
case TokenType::Repeat:
str = QString("repeat %1").arg(m_repeat);
break;
case TokenType::Movement:
str = QString("movement %1").arg((int)m_movement);
break;
case TokenType::Range:
str = QString("range %1").arg((int)m_range);
break;
default:
str = "invalid";
}
return str;
}
TokenType m_type;
union
{
Action m_action;
int m_repeat;
Movement m_movement;
Range m_range;
};
};
// Reset all key state info.
void resetState();
// Now m_tokens constitute a command. Execute it.
// Will clear @p_tokens.
void processCommand(QList<Token> &p_tokens);
// Return the number represented by @p_keys.
// Return -1 if @p_keys is not a valid digit sequence.
int numberFromKeySequence(const QList<Key> &p_keys);
// Try to generate a Repeat token from @p_keys and insert it to @p_tokens.
// If succeed, clear @p_keys and return true.
bool tryGetRepeatToken(QList<Key> &p_keys, QList<Token> &p_tokens);
// @p_tokens is the arguments of the Action::Move action.
void processMoveAction(QList<Token> &p_tokens);
// Clear selection if there is any.
// Returns true if there is selection.
bool clearSelection();
// Get the block count of one page step in vertical scroll bar.
int blockCountOfPageStep() const;
// Expand selection in the VisualLiine mode which will change the position
// of @p_cursor.
void expandSelectionInVisualLineMode(QTextCursor &p_cursor);
VEdit *m_editor;
const VEditConfig *m_editConfig;
VimMode m_mode;
QList<Key> m_keys;
QList<Token> m_tokens;
// Whether reset the position in block when moving cursor.
bool m_resetPositionInBlock;
};
#endif // VVIM_H

View File

@ -146,6 +146,9 @@ void VConfigManager::initialize()
m_enableTrailingSpaceHighlight = getConfigFromSettings("global", m_enableTrailingSpaceHighlight = getConfigFromSettings("global",
"enable_trailing_space_highlight").toBool(); "enable_trailing_space_highlight").toBool();
m_enableVimMode = getConfigFromSettings("global",
"enable_vim_mode").toBool();
} }
void VConfigManager::readPredefinedColorsFromSettings() void VConfigManager::readPredefinedColorsFromSettings()
@ -347,34 +350,48 @@ void VConfigManager::updateMarkdownEditStyle()
QMap<QString, QMap<QString, QString>> styles; QMap<QString, QMap<QString, QString>> styles;
parser.fetchMarkdownEditorStyles(mdEditPalette, mdEditFont, styles); parser.fetchMarkdownEditorStyles(mdEditPalette, mdEditFont, styles);
m_editorCurrentLineBackground = defaultCurrentLineBackground; m_editorCurrentLineBg = defaultCurrentLineBackground;
m_editorCurrentLineVimBackground = defaultCurrentLineVimBackground; m_editorVimInsertBg = defaultCurrentLineBackground;
m_editorVimNormalBg = defaultCurrentLineVimBackground;
m_editorVimVisualBg = m_editorVimNormalBg;
m_editorVimReplaceBg = m_editorVimNormalBg;
auto editorCurrentLineIt = styles.find("editor-current-line"); auto editorCurrentLineIt = styles.find("editor-current-line");
if (editorCurrentLineIt != styles.end()) { if (editorCurrentLineIt != styles.end()) {
auto backgroundIt = editorCurrentLineIt->find("background"); auto backgroundIt = editorCurrentLineIt->find("background");
if (backgroundIt != editorCurrentLineIt->end()) { if (backgroundIt != editorCurrentLineIt->end()) {
// Do not need to add "#" here, since this is a built-in attribute. // Do not need to add "#" here, since this is a built-in attribute.
m_editorCurrentLineBackground = *backgroundIt; m_editorCurrentLineBg = *backgroundIt;
} }
auto vimBackgroundIt = editorCurrentLineIt->find("vim-background"); auto vimBgIt = editorCurrentLineIt->find("vim-insert-background");
if (vimBackgroundIt != editorCurrentLineIt->end()) { if (vimBgIt != editorCurrentLineIt->end()) {
m_editorCurrentLineVimBackground = "#" + *vimBackgroundIt; m_editorVimInsertBg = "#" + *vimBgIt;
}
vimBgIt = editorCurrentLineIt->find("vim-normal-background");
if (vimBgIt != editorCurrentLineIt->end()) {
m_editorVimNormalBg = "#" + *vimBgIt;
}
vimBgIt = editorCurrentLineIt->find("vim-visual-background");
if (vimBgIt != editorCurrentLineIt->end()) {
m_editorVimVisualBg = "#" + *vimBgIt;
}
vimBgIt = editorCurrentLineIt->find("vim-replace-background");
if (vimBgIt != editorCurrentLineIt->end()) {
m_editorVimReplaceBg = "#" + *vimBgIt;
} }
} }
m_editorTrailingSpaceBackground = defaultTrailingSpaceBackground; m_editorTrailingSpaceBg = defaultTrailingSpaceBackground;
auto editorIt = styles.find("editor"); auto editorIt = styles.find("editor");
if (editorIt != styles.end()) { if (editorIt != styles.end()) {
auto trailingIt = editorIt->find("trailing-space"); auto trailingIt = editorIt->find("trailing-space");
if (trailingIt != editorIt->end()) { if (trailingIt != editorIt->end()) {
m_editorTrailingSpaceBackground = "#" + *trailingIt; m_editorTrailingSpaceBg = "#" + *trailingIt;
} }
} }
qDebug() << "editor-current-line" << m_editorCurrentLineBackground;
qDebug() << "editor-current-line-vim" << m_editorCurrentLineVimBackground;
qDebug() << "editor-trailing-space" << m_editorTrailingSpaceBackground;
} }
void VConfigManager::updateEditStyle() void VConfigManager::updateEditStyle()

View File

@ -156,10 +156,14 @@ public:
void setWebZoomFactor(qreal p_factor); void setWebZoomFactor(qreal p_factor);
inline bool isCustomWebZoomFactor(); inline bool isCustomWebZoomFactor();
inline QString getEditorCurrentLineBackground() const; inline const QString &getEditorCurrentLineBg() const;
inline QString getEditorCurrentLineVimBackground() const;
inline QString getEditorTrailingSpaceBackground() const; inline QString getEditorTrailingSpaceBackground() const;
inline const QString &getEditorVimNormalBg() const;
inline const QString &getEditorVimInsertBg() const;
inline const QString &getEditorVimVisualBg() const;
inline const QString &getEditorVimReplaceBg() const;
inline bool getEnableCodeBlockHighlight() const; inline bool getEnableCodeBlockHighlight() const;
inline void setEnableCodeBlockHighlight(bool p_enabled); inline void setEnableCodeBlockHighlight(bool p_enabled);
@ -183,6 +187,9 @@ public:
inline bool getEnableTrailingSpaceHighlight() const; inline bool getEnableTrailingSpaceHighlight() const;
inline void setEnableTrailingSapceHighlight(bool p_enabled); inline void setEnableTrailingSapceHighlight(bool p_enabled);
inline bool getEnableVimMode() const;
inline void setEnableVimMode(bool p_enabled);
// Get the folder the ini file exists. // Get the folder the ini file exists.
QString getConfigFolder() const; QString getConfigFolder() const;
@ -292,13 +299,22 @@ private:
qreal m_webZoomFactor; qreal m_webZoomFactor;
// Current line background color in editor. // Current line background color in editor.
QString m_editorCurrentLineBackground; QString m_editorCurrentLineBg;
// Current line background color in editor in Vim mode. // Current line background color in editor in Vim normal mode.
QString m_editorCurrentLineVimBackground; QString m_editorVimNormalBg;
// Current line background color in editor in Vim insert mode.
QString m_editorVimInsertBg;
// Current line background color in editor in Vim visual mode.
QString m_editorVimVisualBg;
// Current line background color in editor in Vim replace mode.
QString m_editorVimReplaceBg;
// Trailing space background color in editor. // Trailing space background color in editor.
QString m_editorTrailingSpaceBackground; QString m_editorTrailingSpaceBg;
// Enable colde block syntax highlight. // Enable colde block syntax highlight.
bool m_enableCodeBlockHighlight; bool m_enableCodeBlockHighlight;
@ -322,6 +338,9 @@ private:
// Enable trailing-space highlight. // Enable trailing-space highlight.
bool m_enableTrailingSpaceHighlight; bool m_enableTrailingSpaceHighlight;
// Enable Vim mode.
bool m_enableVimMode;
// The name of the config file in each directory, obsolete. // The name of the config file in each directory, obsolete.
// Use c_dirConfigFile instead. // Use c_dirConfigFile instead.
static const QString c_obsoleteDirConfigFile; static const QString c_obsoleteDirConfigFile;
@ -722,19 +741,34 @@ inline bool VConfigManager::isCustomWebZoomFactor()
return factorFromIni > 0; return factorFromIni > 0;
} }
inline QString VConfigManager::getEditorCurrentLineBackground() const inline const QString &VConfigManager::getEditorCurrentLineBg() const
{ {
return m_editorCurrentLineBackground; return m_editorCurrentLineBg;
}
inline QString VConfigManager::getEditorCurrentLineVimBackground() const
{
return m_editorCurrentLineVimBackground;
} }
inline QString VConfigManager::getEditorTrailingSpaceBackground() const inline QString VConfigManager::getEditorTrailingSpaceBackground() const
{ {
return m_editorTrailingSpaceBackground; return m_editorTrailingSpaceBg;
}
inline const QString &VConfigManager::getEditorVimNormalBg() const
{
return m_editorVimNormalBg;
}
inline const QString &VConfigManager::getEditorVimInsertBg() const
{
return m_editorVimInsertBg;
}
inline const QString &VConfigManager::getEditorVimVisualBg() const
{
return m_editorVimVisualBg;
}
inline const QString &VConfigManager::getEditorVimReplaceBg() const
{
return m_editorVimReplaceBg;
} }
inline bool VConfigManager::getEnableCodeBlockHighlight() const inline bool VConfigManager::getEnableCodeBlockHighlight() const
@ -857,4 +891,20 @@ inline void VConfigManager::setEnableTrailingSapceHighlight(bool p_enabled)
m_enableTrailingSpaceHighlight); m_enableTrailingSpaceHighlight);
} }
inline bool VConfigManager::getEnableVimMode() const
{
return m_enableVimMode;
}
inline void VConfigManager::setEnableVimMode(bool p_enabled)
{
if (m_enableVimMode == p_enabled) {
return;
}
m_enableVimMode = p_enabled;
setConfigToSettings("global", "enable_vim_mode",
m_enableVimMode);
}
#endif // VCONFIGMANAGER_H #endif // VCONFIGMANAGER_H

View File

@ -13,6 +13,27 @@
extern VConfigManager vconfig; extern VConfigManager vconfig;
extern VNote *g_vnote; extern VNote *g_vnote;
void VEditConfig::init(const QFontMetrics &p_metric)
{
if (vconfig.getTabStopWidth() > 0) {
m_tabStopWidth = vconfig.getTabStopWidth() * p_metric.width(' ');
} else {
m_tabStopWidth = 0;
}
m_expandTab = vconfig.getIsExpandTab();
if (m_expandTab && (vconfig.getTabStopWidth() > 0)) {
m_tabSpaces = QString(vconfig.getTabStopWidth(), ' ');
} else {
m_tabSpaces = "\t";
}
m_enableVimMode = vconfig.getEnableVimMode();
m_cursorLineBg = QColor(vconfig.getEditorCurrentLineBg());
}
VEdit::VEdit(VFile *p_file, QWidget *p_parent) VEdit::VEdit(VFile *p_file, QWidget *p_parent)
: QTextEdit(p_parent), m_file(p_file), m_editOps(NULL) : QTextEdit(p_parent), m_file(p_file), m_editOps(NULL)
{ {
@ -20,7 +41,6 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
const int extraSelectionHighlightTimer = 500; const int extraSelectionHighlightTimer = 500;
const int labelSize = 64; const int labelSize = 64;
m_cursorLineColor = QColor(g_vnote->getColorFromPalette("Indigo1"));
m_selectedWordColor = QColor("Yellow"); m_selectedWordColor = QColor("Yellow");
m_searchedWordColor = QColor(g_vnote->getColorFromPalette("Green4")); m_searchedWordColor = QColor(g_vnote->getColorFromPalette("Green4"));
m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBackground()); m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBackground());
@ -45,8 +65,11 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
(VFile *)m_file, &VFile::setModified); (VFile *)m_file, &VFile::setModified);
m_extraSelections.resize((int)SelectionId::MaxSelection); m_extraSelections.resize((int)SelectionId::MaxSelection);
updateFontAndPalette(); updateFontAndPalette();
updateConfig();
connect(this, &VEdit::cursorPositionChanged, connect(this, &VEdit::cursorPositionChanged,
this, &VEdit::handleCursorPositionChanged); this, &VEdit::handleCursorPositionChanged);
@ -62,10 +85,23 @@ VEdit::~VEdit()
} }
} }
void VEdit::updateConfig()
{
m_config.init(QFontMetrics(font()));
if (m_config.m_tabStopWidth > 0) {
setTabStopWidth(m_config.m_tabStopWidth);
}
emit configUpdated();
}
void VEdit::beginEdit() void VEdit::beginEdit()
{ {
updateFontAndPalette(); updateFontAndPalette();
updateConfig();
setReadOnly(false); setReadOnly(false);
setModified(false); setModified(false);
} }
@ -404,7 +440,7 @@ void VEdit::highlightCurrentLine()
if (vconfig.getHighlightCursorLine() && !isReadOnly()) { if (vconfig.getHighlightCursorLine() && !isReadOnly()) {
// Need to highlight current line. // Need to highlight current line.
QTextEdit::ExtraSelection select; QTextEdit::ExtraSelection select;
select.format.setBackground(m_cursorLineColor); select.format.setBackground(m_config.m_cursorLineBg);
select.format.setProperty(QTextFormat::FullWidthSelection, true); select.format.setProperty(QTextFormat::FullWidthSelection, true);
select.cursor = textCursor(); select.cursor = textCursor();
select.cursor.clearSelection(); select.cursor.clearSelection();
@ -651,3 +687,7 @@ void VEdit::handleCursorPositionChanged()
lastCursor = cursor; lastCursor = cursor;
} }
VEditConfig &VEdit::getConfig()
{
return m_config;
}

View File

@ -7,6 +7,7 @@
#include <QVector> #include <QVector>
#include <QList> #include <QList>
#include <QColor> #include <QColor>
#include <QFontMetrics>
#include "vconstants.h" #include "vconstants.h"
#include "vtoc.h" #include "vtoc.h"
#include "vfile.h" #include "vfile.h"
@ -23,6 +24,27 @@ enum class SelectionId {
MaxSelection MaxSelection
}; };
class VEditConfig {
public:
VEditConfig() : m_tabStopWidth(0), m_tabSpaces("\t"),
m_enableVimMode(false) {}
void init(const QFontMetrics &p_metric);
// Width in pixels.
int m_tabStopWidth;
bool m_expandTab;
// The literal string for Tab. It is spaces if Tab is expanded.
QString m_tabSpaces;
bool m_enableVimMode;
// The background color of cursor line.
QColor m_cursorLineBg;
};
class VEdit : public QTextEdit class VEdit : public QTextEdit
{ {
Q_OBJECT Q_OBJECT
@ -51,11 +73,19 @@ public:
void clearSearchedWordHighlight(); void clearSearchedWordHighlight();
VFile *getFile() const; VFile *getFile() const;
VEditConfig &getConfig();
signals: signals:
void saveAndRead(); void saveAndRead();
void discardAndRead(); void discardAndRead();
void editNote(); void editNote();
// Emit when m_config has been updated.
void configUpdated();
public slots:
virtual void highlightCurrentLine();
private slots: private slots:
void labelTimerTimeout(); void labelTimerTimeout();
void highlightSelectedWord(); void highlightSelectedWord();
@ -65,17 +95,18 @@ private slots:
void highlightTrailingSpace(); void highlightTrailingSpace();
void handleCursorPositionChanged(); void handleCursorPositionChanged();
protected slots:
virtual void highlightCurrentLine();
protected: protected:
QPointer<VFile> m_file; QPointer<VFile> m_file;
VEditOperations *m_editOps; VEditOperations *m_editOps;
QColor m_cursorLineColor;
VEditConfig m_config;
virtual void updateFontAndPalette(); virtual void updateFontAndPalette();
virtual void contextMenuEvent(QContextMenuEvent *p_event) Q_DECL_OVERRIDE; virtual void contextMenuEvent(QContextMenuEvent *p_event) Q_DECL_OVERRIDE;
// Update m_config according to VConfigManager.
void updateConfig();
private: private:
QLabel *m_wrapLabel; QLabel *m_wrapLabel;
QTimer *m_labelTimer; QTimer *m_labelTimer;

View File

@ -4,14 +4,20 @@
#include "vedit.h" #include "vedit.h"
#include "veditoperations.h" #include "veditoperations.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h"
extern VConfigManager vconfig; extern VConfigManager vconfig;
VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file) VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file)
: QObject(p_editor), m_editor(p_editor), m_file(p_file), m_expandTab(false), : QObject(p_editor), m_editor(p_editor), m_file(p_file),
m_keyState(KeyState::Normal), m_pendingTime(2) m_editConfig(&p_editor->getConfig())
{ {
updateTabSettings(); m_vim = new VVim(m_editor);
connect(m_editor, &VEdit::configUpdated,
this, &VEditOperations::handleEditConfigUpdated);
connect(m_vim, &VVim::modeChanged,
this, &VEditOperations::handleVimModeChanged);
} }
void VEditOperations::insertTextAtCurPos(const QString &p_text) void VEditOperations::insertTextAtCurPos(const QString &p_text)
@ -25,14 +31,46 @@ VEditOperations::~VEditOperations()
{ {
} }
void VEditOperations::updateTabSettings() void VEditOperations::updateCursorLineBg()
{ {
if (vconfig.getTabStopWidth() > 0) { if (m_editConfig->m_enableVimMode) {
QFontMetrics metrics(vconfig.getMdEditFont()); switch (m_vim->getMode()) {
m_editor->setTabStopWidth(vconfig.getTabStopWidth() * metrics.width(' ')); case VimMode::Normal:
m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorVimNormalBg());
break;
case VimMode::Insert:
m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorVimInsertBg());
break;
case VimMode::Visual:
case VimMode::VisualLine:
m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorVimVisualBg());
break;
case VimMode::Replace:
m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorVimReplaceBg());
break;
default:
V_ASSERT(false);
break;
} }
m_expandTab = vconfig.getIsExpandTab(); } else {
if (m_expandTab && (vconfig.getTabStopWidth() > 0)) { m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorCurrentLineBg());
m_tabSpaces = QString(vconfig.getTabStopWidth(), ' ');
} }
m_editor->highlightCurrentLine();
}
void VEditOperations::handleEditConfigUpdated()
{
updateCursorLineBg();
}
void VEditOperations::handleVimModeChanged(VimMode p_mode)
{
Q_UNUSED(p_mode);
updateCursorLineBg();
} }

View File

@ -6,13 +6,13 @@
#include <QObject> #include <QObject>
#include <QList> #include <QList>
#include "vfile.h" #include "vfile.h"
#include "utils/vvim.h"
class VEdit; class VEdit;
class VEditConfig;
class QMimeData; class QMimeData;
class QKeyEvent; class QKeyEvent;
enum class KeyState { Normal = 0, Vim, VimVisual};
class VEditOperations: public QObject class VEditOperations: public QObject
{ {
Q_OBJECT Q_OBJECT
@ -22,25 +22,29 @@ public:
virtual bool insertImageFromMimeData(const QMimeData *source) = 0; virtual bool insertImageFromMimeData(const QMimeData *source) = 0;
virtual bool insertImage() = 0; virtual bool insertImage() = 0;
virtual bool insertImageFromURL(const QUrl &p_imageUrl) = 0; virtual bool insertImageFromURL(const QUrl &p_imageUrl) = 0;
// Return true if @p_event has been handled and no need to be further // Return true if @p_event has been handled and no need to be further
// processed. // processed.
virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0; virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0;
void updateTabSettings();
signals: protected slots:
void keyStateChanged(KeyState p_state); // Handle the update of VEditConfig of the editor.
virtual void handleEditConfigUpdated();
// Vim mode changed.
void handleVimModeChanged(VimMode p_mode);
private:
// Update m_editConfig->m_cursorLineBg.
void updateCursorLineBg();
protected: protected:
void insertTextAtCurPos(const QString &p_text); void insertTextAtCurPos(const QString &p_text);
VEdit *m_editor; VEdit *m_editor;
QPointer<VFile> m_file; QPointer<VFile> m_file;
bool m_expandTab; VEditConfig *m_editConfig;
QString m_tabSpaces; VVim *m_vim;
KeyState m_keyState;
// Seconds for pending mode.
int m_pendingTime;
QList<QString> m_pendingKey;
}; };
#endif // VEDITOPERATIONS_H #endif // VEDITOPERATIONS_H

View File

@ -605,6 +605,13 @@ void VMainWindow::initEditMenu()
connect(autoListAct, &QAction::triggered, connect(autoListAct, &QAction::triggered,
this, &VMainWindow::changeAutoList); this, &VMainWindow::changeAutoList);
// Vim Mode.
QAction *vimAct = new QAction(tr("Vim Mode"), this);
vimAct->setToolTip(tr("Enable Vim mode for editing (re-enter edit mode to make it work)"));
vimAct->setCheckable(true);
connect(vimAct, &QAction::triggered,
this, &VMainWindow::changeVimMode);
// Highlight current cursor line. // Highlight current cursor line.
QAction *cursorLineAct = new QAction(tr("Highlight Cursor Line"), this); QAction *cursorLineAct = new QAction(tr("Highlight Cursor Line"), this);
cursorLineAct->setToolTip(tr("Highlight current cursor line")); cursorLineAct->setToolTip(tr("Highlight current cursor line"));
@ -687,6 +694,9 @@ void VMainWindow::initEditMenu()
} }
Q_ASSERT(!(autoListAct->isChecked() && !m_autoIndentAct->isChecked())); Q_ASSERT(!(autoListAct->isChecked() && !m_autoIndentAct->isChecked()));
editMenu->addAction(vimAct);
vimAct->setChecked(vconfig.getEnableVimMode());
editMenu->addSeparator(); editMenu->addSeparator();
initEditorStyleMenu(editMenu); initEditorStyleMenu(editMenu);
@ -1381,6 +1391,11 @@ void VMainWindow::changeAutoList(bool p_checked)
} }
} }
void VMainWindow::changeVimMode(bool p_checked)
{
vconfig.setEnableVimMode(p_checked);
}
void VMainWindow::enableCodeBlockHighlight(bool p_checked) void VMainWindow::enableCodeBlockHighlight(bool p_checked)
{ {
vconfig.setEnableCodeBlockHighlight(p_checked); vconfig.setEnableCodeBlockHighlight(p_checked);

View File

@ -76,6 +76,7 @@ private slots:
void handleCaptainModeChanged(bool p_enabled); void handleCaptainModeChanged(bool p_enabled);
void changeAutoIndent(bool p_checked); void changeAutoIndent(bool p_checked);
void changeAutoList(bool p_checked); void changeAutoList(bool p_checked);
void changeVimMode(bool p_checked);
void enableCodeBlockHighlight(bool p_checked); void enableCodeBlockHighlight(bool p_checked);
void enableImagePreview(bool p_checked); void enableImagePreview(bool p_checked);
void enableImagePreviewConstraint(bool p_checked); void enableImagePreviewConstraint(bool p_checked);

View File

@ -45,8 +45,6 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
m_imagePreviewer = new VImagePreviewer(this, 500); m_imagePreviewer = new VImagePreviewer(this, 500);
m_editOps = new VMdEditOperations(this, m_file); m_editOps = new VMdEditOperations(this, m_file);
connect(m_editOps, &VEditOperations::keyStateChanged,
this, &VMdEdit::handleEditStateChanged);
connect(this, &VMdEdit::cursorPositionChanged, connect(this, &VMdEdit::cursorPositionChanged,
this, &VMdEdit::updateCurHeader); this, &VMdEdit::updateCurHeader);
@ -56,22 +54,23 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
connect(QApplication::clipboard(), &QClipboard::changed, connect(QApplication::clipboard(), &QClipboard::changed,
this, &VMdEdit::handleClipboardChanged); this, &VMdEdit::handleClipboardChanged);
m_editOps->updateTabSettings();
updateFontAndPalette(); updateFontAndPalette();
updateConfig();
} }
void VMdEdit::updateFontAndPalette() void VMdEdit::updateFontAndPalette()
{ {
setFont(vconfig.getMdEditFont()); setFont(vconfig.getMdEditFont());
setPalette(vconfig.getMdEditPalette()); setPalette(vconfig.getMdEditPalette());
m_cursorLineColor = vconfig.getEditorCurrentLineBackground();
} }
void VMdEdit::beginEdit() void VMdEdit::beginEdit()
{ {
m_editOps->updateTabSettings();
updateFontAndPalette(); updateFontAndPalette();
updateConfig();
Q_ASSERT(m_file->getContent() == toPlainTextWithoutImg()); Q_ASSERT(m_file->getContent() == toPlainTextWithoutImg());
initInitImages(); initInitImages();
@ -366,17 +365,6 @@ int VMdEdit::removeObjectReplacementLine(QString &p_text, int p_index) const
return prevLineIdx - 1; return prevLineIdx - 1;
} }
void VMdEdit::handleEditStateChanged(KeyState p_state)
{
qDebug() << "edit state" << (int)p_state;
if (p_state == KeyState::Normal) {
m_cursorLineColor = vconfig.getEditorCurrentLineBackground();
} else {
m_cursorLineColor = vconfig.getEditorCurrentLineVimBackground();
}
highlightCurrentLine();
}
void VMdEdit::handleSelectionChanged() void VMdEdit::handleSelectionChanged()
{ {
if (!vconfig.getEnablePreviewImages()) { if (!vconfig.getEnablePreviewImages()) {

View File

@ -53,7 +53,6 @@ private slots:
// When there is no header in current cursor, will signal an invalid header. // When there is no header in current cursor, will signal an invalid header.
void updateCurHeader(); void updateCurHeader();
void handleEditStateChanged(KeyState p_state);
void handleSelectionChanged(); void handleSelectionChanged();
void handleClipboardChanged(QClipboard::Mode p_mode); void handleClipboardChanged(QClipboard::Mode p_mode);

View File

@ -21,6 +21,7 @@
#include "vfile.h" #include "vfile.h"
#include "vmdedit.h" #include "vmdedit.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vvim.h"
extern VConfigManager vconfig; extern VConfigManager vconfig;
@ -29,10 +30,6 @@ const QString VMdEditOperations::c_defaultImageTitle = "image";
VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file) VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
: VEditOperations(p_editor, p_file), m_autoIndentPos(-1) : VEditOperations(p_editor, p_file), m_autoIndentPos(-1)
{ {
m_pendingTimer = new QTimer(this);
m_pendingTimer->setSingleShot(true);
m_pendingTimer->setInterval(m_pendingTime * 1000); // milliseconds
connect(m_pendingTimer, &QTimer::timeout, this, &VMdEditOperations::pendingTimerTimeout);
} }
bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source) bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
@ -193,32 +190,17 @@ bool VMdEditOperations::insertImage()
return true; return true;
} }
// Will modify m_pendingKey.
bool VMdEditOperations::shouldTriggerVimMode(QKeyEvent *p_event)
{
int modifiers = p_event->modifiers();
int key = p_event->key();
if (key == Qt::Key_Escape ||
(key == Qt::Key_BracketLeft && modifiers == Qt::ControlModifier)) {
return false;
} else if (m_keyState == KeyState::Vim || m_keyState == KeyState::VimVisual) {
return true;
}
return false;
}
bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event) bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
{ {
if (m_editConfig->m_enableVimMode && m_vim->handleKeyPressEvent(p_event)) {
m_autoIndentPos = -1;
return true;
}
bool ret = false; bool ret = false;
int key = p_event->key(); int key = p_event->key();
int modifiers = p_event->modifiers(); int modifiers = p_event->modifiers();
if (shouldTriggerVimMode(p_event)) {
if (handleKeyPressVim(p_event)) {
ret = true;
goto exit;
}
} else {
switch (key) { switch (key) {
case Qt::Key_1: case Qt::Key_1:
case Qt::Key_2: case Qt::Key_2:
@ -265,15 +247,6 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
break; break;
} }
case Qt::Key_D:
{
if (handleKeyD(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_H: case Qt::Key_H:
{ {
if (handleKeyH(p_event)) { if (handleKeyH(p_event)) {
@ -349,7 +322,6 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
default: default:
break; break;
} }
}
exit: exit:
// Qt::Key_Return, Qt::Key_Tab and Qt::Key_Backtab will handle m_autoIndentPos. // Qt::Key_Return, Qt::Key_Tab and Qt::Key_Backtab will handle m_autoIndentPos.
@ -357,44 +329,32 @@ exit:
key != Qt::Key_Shift) { key != Qt::Key_Shift) {
m_autoIndentPos = -1; m_autoIndentPos = -1;
} }
return ret; return ret;
} }
// Let Ctrl+[ behave exactly like ESC. // Let Ctrl+[ behave exactly like ESC.
bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event) bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
{ {
// 1. If it is not in Normal state, just go back to Normal state; // 1. If there is any selection, clear it.
// 2. If it is already Normal state, try to clear selection; // 2. Otherwise, ignore this event and let parent handles it.
// 3. Otherwise, ignore this event and let parent handles it.
bool accept = false;
if (p_event->modifiers() == Qt::ControlModifier) { if (p_event->modifiers() == Qt::ControlModifier) {
if (m_keyState != KeyState::Normal) {
m_pendingTimer->stop();
setKeyState(KeyState::Normal);
m_pendingKey.clear();
accept = true;
} else {
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
cursor.clearSelection(); cursor.clearSelection();
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
accept = true;
}
}
}
if (accept) {
p_event->accept(); p_event->accept();
return true;
} }
return accept; }
return false;
} }
bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event) bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
{ {
QTextDocument *doc = m_editor->document(); QTextDocument *doc = m_editor->document();
QString text("\t"); QString text(m_editConfig->m_tabSpaces);
if (m_expandTab) {
text = m_tabSpaces;
}
if (p_event->modifiers() == Qt::NoModifier) { if (p_event->modifiers() == Qt::NoModifier) {
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
@ -483,8 +443,8 @@ bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
continue; continue;
} else { } else {
// Spaces. // Spaces.
if (m_expandTab) { if (m_editConfig->m_expandTab) {
int width = m_tabSpaces.size(); int width = m_editConfig->m_tabSpaces.size();
for (int i = 0; i < width; ++i) { for (int i = 0; i < width; ++i) {
if (text[i] == ' ') { if (text[i] == ' ') {
blockCursor.deleteChar(); blockCursor.deleteChar();
@ -555,20 +515,6 @@ bool VMdEditOperations::handleKeyB(QKeyEvent *p_event)
return false; return false;
} }
bool VMdEditOperations::handleKeyD(QKeyEvent *p_event)
{
if (p_event->modifiers() == Qt::ControlModifier) {
// Ctrl+D, enter Vim-pending mode.
// Will accept the key stroke in m_pendingTime as Vim normal command.
setKeyState(KeyState::Vim);
m_pendingTimer->stop();
m_pendingTimer->start();
p_event->accept();
return true;
}
return false;
}
bool VMdEditOperations::handleKeyH(QKeyEvent *p_event) bool VMdEditOperations::handleKeyH(QKeyEvent *p_event)
{ {
if (p_event->modifiers() == Qt::ControlModifier) { if (p_event->modifiers() == Qt::ControlModifier) {
@ -713,27 +659,17 @@ bool VMdEditOperations::handleKeyW(QKeyEvent *p_event)
bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event) bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event)
{ {
// 1. If it is not in Normal state, just go back to Normal state; // 1. If there is any selection, clear it.
// 2. If it is already Normal state, try to clear selection; // 2. Otherwise, ignore this event and let parent handles it.
// 3. Otherwise, ignore this event and let parent handles it.
bool accept = false;
if (m_keyState != KeyState::Normal) {
m_pendingTimer->stop();
setKeyState(KeyState::Normal);
m_pendingKey.clear();
accept = true;
} else {
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
cursor.clearSelection(); cursor.clearSelection();
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
accept = true;
}
}
if (accept) {
p_event->accept(); p_event->accept();
return true;
} }
return accept;
return false;
} }
bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event) bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
@ -935,399 +871,6 @@ void VMdEditOperations::deleteIndentAndListMark()
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
} }
bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
{
int modifiers = p_event->modifiers();
bool visualMode = m_keyState == KeyState::VimVisual;
QTextCursor::MoveMode mode = visualMode ? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor;
switch (p_event->key()) {
// Ctrl and Shift may be sent out first.
case Qt::Key_Control:
// Fall through.
case Qt::Key_Shift:
{
goto pending;
break;
}
case Qt::Key_H:
case Qt::Key_J:
case Qt::Key_K:
case Qt::Key_L:
{
if (modifiers == Qt::NoModifier) {
QTextCursor::MoveOperation op = QTextCursor::Left;
switch (p_event->key()) {
case Qt::Key_H:
op = QTextCursor::Left;
break;
case Qt::Key_J:
op = QTextCursor::Down;
break;
case Qt::Key_K:
op = QTextCursor::Up;
break;
case Qt::Key_L:
op = QTextCursor::Right;
}
// Move cursor <repeat> characters left/Down/Up/Right.
int repeat = keySeqToNumber(m_pendingKey);
m_pendingKey.clear();
QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(op, mode, repeat == 0 ? 1 : repeat);
m_editor->setTextCursor(cursor);
goto pending;
}
break;
}
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
{
if (modifiers == Qt::NoModifier) {
if (!suffixNumAllowed(m_pendingKey)) {
break;
}
int num = p_event->key() - Qt::Key_0;
m_pendingKey.append(QString::number(num));
goto pending;
}
break;
}
case Qt::Key_X:
{
// Delete characters.
if (modifiers == Qt::NoModifier) {
int repeat = keySeqToNumber(m_pendingKey);
m_pendingKey.clear();
QTextCursor cursor = m_editor->textCursor();
if (repeat == 0) {
repeat = 1;
}
cursor.beginEditBlock();
if (cursor.hasSelection()) {
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(cursor.selectedText());
cursor.removeSelectedText();
} else {
for (int i = 0; i < repeat; ++i) {
cursor.deleteChar();
}
}
cursor.endEditBlock();
m_editor->setTextCursor(cursor);
goto pending;
}
break;
}
case Qt::Key_W:
{
if (modifiers == Qt::NoModifier) {
// Move to the start of the next word.
// Slightly different from the Vim behavior.
int repeat = keySeqToNumber(m_pendingKey);
m_pendingKey.clear();
QTextCursor cursor = m_editor->textCursor();
if (repeat == 0) {
repeat = 1;
}
cursor.movePosition(QTextCursor::NextWord, mode, repeat);
m_editor->setTextCursor(cursor);
goto pending;
}
break;
}
case Qt::Key_E:
{
if (modifiers == Qt::NoModifier) {
// Move to the end of the next word.
// Slightly different from the Vim behavior.
int repeat = keySeqToNumber(m_pendingKey);
m_pendingKey.clear();
QTextCursor cursor = m_editor->textCursor();
if (repeat == 0) {
repeat = 1;
}
cursor.beginEditBlock();
int pos = cursor.position();
// First move to the end of current word.
cursor.movePosition(QTextCursor::EndOfWord, mode);
if (cursor.position() != pos) {
// We did move.
repeat--;
}
if (repeat) {
cursor.movePosition(QTextCursor::NextWord, mode, repeat);
cursor.movePosition(QTextCursor::EndOfWord, mode);
}
cursor.endEditBlock();
m_editor->setTextCursor(cursor);
goto pending;
}
break;
}
case Qt::Key_B:
{
if (modifiers == Qt::NoModifier) {
// Move to the start of the previous word.
// Slightly different from the Vim behavior.
int repeat = keySeqToNumber(m_pendingKey);
m_pendingKey.clear();
QTextCursor cursor = m_editor->textCursor();
if (repeat == 0) {
repeat = 1;
}
cursor.beginEditBlock();
int pos = cursor.position();
// First move to the start of current word.
cursor.movePosition(QTextCursor::StartOfWord, mode);
if (cursor.position() != pos) {
// We did move.
repeat--;
}
if (repeat) {
cursor.movePosition(QTextCursor::PreviousWord, mode, repeat);
}
cursor.endEditBlock();
m_editor->setTextCursor(cursor);
goto pending;
}
break;
}
case Qt::Key_0:
{
if (modifiers == Qt::NoModifier) {
if (keySeqToNumber(m_pendingKey) == 0) {
QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(QTextCursor::StartOfLine, mode);
m_editor->setTextCursor(cursor);
} else {
m_pendingKey.append("0");
}
goto pending;
}
break;
}
case Qt::Key_Dollar:
{
if (modifiers == Qt::ShiftModifier) {
if (m_pendingKey.isEmpty()) {
// Go to end of line.
QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(QTextCursor::EndOfLine, mode);
m_editor->setTextCursor(cursor);
goto pending;
}
}
break;
}
case Qt::Key_AsciiCircum:
{
if (modifiers == Qt::ShiftModifier) {
if (m_pendingKey.isEmpty()) {
// Go to first non-space character of current line.
QTextCursor cursor = m_editor->textCursor();
QTextBlock block = cursor.block();
QString text = block.text();
cursor.beginEditBlock();
if (text.trimmed().isEmpty()) {
cursor.movePosition(QTextCursor::StartOfLine, mode);
} else {
cursor.movePosition(QTextCursor::StartOfLine, mode);
int pos = cursor.positionInBlock();
while (pos < text.size() && text[pos].isSpace()) {
cursor.movePosition(QTextCursor::NextWord, mode);
pos = cursor.positionInBlock();
}
}
cursor.endEditBlock();
m_editor->setTextCursor(cursor);
goto pending;
}
}
break;
}
case Qt::Key_G:
{
if (modifiers == Qt::NoModifier) {
// g, pending or go to first line.
if (m_pendingKey.isEmpty()) {
m_pendingKey.append("g");
goto pending;
} else if (m_pendingKey.size() == 1 && m_pendingKey.at(0) == "g") {
m_pendingKey.clear();
QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(QTextCursor::Start, mode);
m_editor->setTextCursor(cursor);
goto pending;
}
} else if (modifiers == Qt::ShiftModifier) {
// G, go to a certain line or the end of document.
int lineNum = keySeqToNumber(m_pendingKey);
m_pendingKey.clear();
QTextCursor cursor = m_editor->textCursor();
if (lineNum == 0) {
cursor.movePosition(QTextCursor::End, mode);
} else {
QTextDocument *doc = m_editor->document();
QTextBlock block = doc->findBlockByNumber(lineNum - 1);
if (block.isValid()) {
cursor.setPosition(block.position(), mode);
} else {
// Go beyond the document.
cursor.movePosition(QTextCursor::End, mode);
}
}
m_editor->setTextCursor(cursor);
goto pending;
}
break;
}
case Qt::Key_V:
{
if (modifiers == Qt::NoModifier) {
// V to enter visual mode.
if (m_pendingKey.isEmpty() && m_keyState != KeyState::VimVisual) {
setKeyState(KeyState::VimVisual);
goto pending;
}
}
break;
}
case Qt::Key_Y:
{
if (modifiers == Qt::NoModifier) {
if (m_pendingKey.isEmpty()) {
QTextCursor cursor = m_editor->textCursor();
if (cursor.hasSelection()) {
QString text = cursor.selectedText();
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text);
}
goto pending;
}
}
break;
}
case Qt::Key_D:
{
if (modifiers == Qt::NoModifier) {
// d, pending or delete current line.
QTextCursor cursor = m_editor->textCursor();
if (m_pendingKey.isEmpty()) {
if (cursor.hasSelection()) {
cursor.deleteChar();
m_editor->setTextCursor(cursor);
} else {
m_pendingKey.append("d");
}
goto pending;
} else if (m_pendingKey.size() == 1 && m_pendingKey.at(0) == "d") {
m_pendingKey.clear();
cursor.select(QTextCursor::BlockUnderCursor);
cursor.removeSelectedText();
m_editor->setTextCursor(cursor);
goto pending;
}
}
break;
}
default:
// Unknown key. End Vim mode.
break;
}
m_pendingTimer->stop();
if (m_keyState == KeyState::VimVisual) {
// Clear the visual selection.
QTextCursor cursor = m_editor->textCursor();
cursor.clearSelection();
m_editor->setTextCursor(cursor);
}
setKeyState(KeyState::Normal);
m_pendingKey.clear();
p_event->accept();
return true;
pending:
// When pending in Ctrl+Alt, we just want to clear m_pendingKey.
if (m_pendingTimer->isActive()) {
m_pendingTimer->stop();
m_pendingTimer->start();
}
p_event->accept();
return true;
}
int VMdEditOperations::keySeqToNumber(const QList<QString> &p_seq)
{
int num = 0;
for (int i = 0; i < p_seq.size(); ++i) {
QString tmp = p_seq.at(i);
bool ok;
int tmpInt = tmp.toInt(&ok);
if (!ok) {
return 0;
}
num = num * 10 + tmpInt;
}
return num;
}
void VMdEditOperations::pendingTimerTimeout()
{
qDebug() << "key pending timer timeout";
if (m_keyState == KeyState::VimVisual) {
m_pendingTimer->start();
return;
}
setKeyState(KeyState::Normal);
m_pendingKey.clear();
}
bool VMdEditOperations::suffixNumAllowed(const QList<QString> &p_seq)
{
if (!p_seq.isEmpty()) {
QString firstEle = p_seq.at(0);
if (firstEle[0].isDigit()) {
return true;
} else {
return false;
}
}
return true;
}
void VMdEditOperations::setKeyState(KeyState p_state)
{
if (m_keyState == p_state) {
return;
}
m_keyState = p_state;
emit keyStateChanged(m_keyState);
}
bool VMdEditOperations::insertTitle(int p_level) bool VMdEditOperations::insertTitle(int p_level)
{ {
Q_ASSERT(p_level > 0 && p_level < 7); Q_ASSERT(p_level > 0 && p_level < 7);

View File

@ -21,9 +21,6 @@ public:
bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
bool insertImageFromURL(const QUrl &p_imageUrl) Q_DECL_OVERRIDE; bool insertImageFromURL(const QUrl &p_imageUrl) Q_DECL_OVERRIDE;
private slots:
void pendingTimerTimeout();
private: private:
void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath); void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath);
@ -32,13 +29,10 @@ private:
// @image: the image to be inserted; // @image: the image to be inserted;
void insertImageFromQImage(const QString &title, const QString &path, const QImage &image); void insertImageFromQImage(const QString &title, const QString &path, const QImage &image);
void setKeyState(KeyState p_state);
// Key press handlers. // Key press handlers.
bool handleKeyTab(QKeyEvent *p_event); bool handleKeyTab(QKeyEvent *p_event);
bool handleKeyBackTab(QKeyEvent *p_event); bool handleKeyBackTab(QKeyEvent *p_event);
bool handleKeyB(QKeyEvent *p_event); bool handleKeyB(QKeyEvent *p_event);
bool handleKeyD(QKeyEvent *p_event);
bool handleKeyH(QKeyEvent *p_event); bool handleKeyH(QKeyEvent *p_event);
bool handleKeyI(QKeyEvent *p_event); bool handleKeyI(QKeyEvent *p_event);
bool handleKeyO(QKeyEvent *p_event); bool handleKeyO(QKeyEvent *p_event);
@ -46,11 +40,7 @@ private:
bool handleKeyW(QKeyEvent *p_event); bool handleKeyW(QKeyEvent *p_event);
bool handleKeyEsc(QKeyEvent *p_event); bool handleKeyEsc(QKeyEvent *p_event);
bool handleKeyReturn(QKeyEvent *p_event); bool handleKeyReturn(QKeyEvent *p_event);
bool handleKeyPressVim(QKeyEvent *p_event);
bool handleKeyBracketLeft(QKeyEvent *p_event); bool handleKeyBracketLeft(QKeyEvent *p_event);
bool shouldTriggerVimMode(QKeyEvent *p_event);
int keySeqToNumber(const QList<QString> &p_seq);
bool suffixNumAllowed(const QList<QString> &p_seq);
bool insertTitle(int p_level); bool insertTitle(int p_level);
bool insertNewBlockWithIndent(); bool insertNewBlockWithIndent();
bool insertListMarkAsPreviousLine(); bool insertListMarkAsPreviousLine();
@ -67,7 +57,6 @@ private:
// Change the sequence number of a list block. // Change the sequence number of a list block.
void changeListBlockSeqNumber(QTextBlock &p_block, int p_seq); void changeListBlockSeqNumber(QTextBlock &p_block, int p_seq);
QTimer *m_pendingTimer;
// The cursor position after auto indent or auto list. // The cursor position after auto indent or auto list.
// It will be -1 if last key press do not trigger the auto indent or auto list. // It will be -1 if last key press do not trigger the auto indent or auto list.
int m_autoIndentPos; int m_autoIndentPos;