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
background: c5cae9
vim-background: a5d6a7
vim-insert-background: c5cae9
vim-normal-background: a5d6a7
vim-visual-background: a5d6a7
vim-replace-background: a5d6a7
H1
foreground: 111111

View File

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

View File

@ -64,7 +64,8 @@ SOURCES += main.cpp\
vimagepreviewer.cpp \
vexporter.cpp \
vmdtab.cpp \
vhtmltab.cpp
vhtmltab.cpp \
utils/vvim.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -115,7 +116,8 @@ HEADERS += vmainwindow.h \
vimagepreviewer.h \
vexporter.h \
vmdtab.h \
vhtmltab.h
vhtmltab.h \
utils/vvim.h
RESOURCES += \
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",
"enable_trailing_space_highlight").toBool();
m_enableVimMode = getConfigFromSettings("global",
"enable_vim_mode").toBool();
}
void VConfigManager::readPredefinedColorsFromSettings()
@ -347,34 +350,48 @@ void VConfigManager::updateMarkdownEditStyle()
QMap<QString, QMap<QString, QString>> styles;
parser.fetchMarkdownEditorStyles(mdEditPalette, mdEditFont, styles);
m_editorCurrentLineBackground = defaultCurrentLineBackground;
m_editorCurrentLineVimBackground = defaultCurrentLineVimBackground;
m_editorCurrentLineBg = defaultCurrentLineBackground;
m_editorVimInsertBg = defaultCurrentLineBackground;
m_editorVimNormalBg = defaultCurrentLineVimBackground;
m_editorVimVisualBg = m_editorVimNormalBg;
m_editorVimReplaceBg = m_editorVimNormalBg;
auto editorCurrentLineIt = styles.find("editor-current-line");
if (editorCurrentLineIt != styles.end()) {
auto backgroundIt = editorCurrentLineIt->find("background");
if (backgroundIt != editorCurrentLineIt->end()) {
// 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");
if (vimBackgroundIt != editorCurrentLineIt->end()) {
m_editorCurrentLineVimBackground = "#" + *vimBackgroundIt;
auto vimBgIt = editorCurrentLineIt->find("vim-insert-background");
if (vimBgIt != editorCurrentLineIt->end()) {
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");
if (editorIt != styles.end()) {
auto trailingIt = editorIt->find("trailing-space");
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()

View File

@ -156,10 +156,14 @@ public:
void setWebZoomFactor(qreal p_factor);
inline bool isCustomWebZoomFactor();
inline QString getEditorCurrentLineBackground() const;
inline QString getEditorCurrentLineVimBackground() const;
inline const QString &getEditorCurrentLineBg() 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 void setEnableCodeBlockHighlight(bool p_enabled);
@ -183,6 +187,9 @@ public:
inline bool getEnableTrailingSpaceHighlight() const;
inline void setEnableTrailingSapceHighlight(bool p_enabled);
inline bool getEnableVimMode() const;
inline void setEnableVimMode(bool p_enabled);
// Get the folder the ini file exists.
QString getConfigFolder() const;
@ -292,13 +299,22 @@ private:
qreal m_webZoomFactor;
// Current line background color in editor.
QString m_editorCurrentLineBackground;
QString m_editorCurrentLineBg;
// Current line background color in editor in Vim mode.
QString m_editorCurrentLineVimBackground;
// Current line background color in editor in Vim normal mode.
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.
QString m_editorTrailingSpaceBackground;
QString m_editorTrailingSpaceBg;
// Enable colde block syntax highlight.
bool m_enableCodeBlockHighlight;
@ -322,6 +338,9 @@ private:
// Enable trailing-space highlight.
bool m_enableTrailingSpaceHighlight;
// Enable Vim mode.
bool m_enableVimMode;
// The name of the config file in each directory, obsolete.
// Use c_dirConfigFile instead.
static const QString c_obsoleteDirConfigFile;
@ -722,19 +741,34 @@ inline bool VConfigManager::isCustomWebZoomFactor()
return factorFromIni > 0;
}
inline QString VConfigManager::getEditorCurrentLineBackground() const
inline const QString &VConfigManager::getEditorCurrentLineBg() const
{
return m_editorCurrentLineBackground;
}
inline QString VConfigManager::getEditorCurrentLineVimBackground() const
{
return m_editorCurrentLineVimBackground;
return m_editorCurrentLineBg;
}
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
@ -857,4 +891,20 @@ inline void VConfigManager::setEnableTrailingSapceHighlight(bool p_enabled)
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

View File

@ -13,6 +13,27 @@
extern VConfigManager vconfig;
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)
: 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 labelSize = 64;
m_cursorLineColor = QColor(g_vnote->getColorFromPalette("Indigo1"));
m_selectedWordColor = QColor("Yellow");
m_searchedWordColor = QColor(g_vnote->getColorFromPalette("Green4"));
m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBackground());
@ -45,8 +65,11 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
(VFile *)m_file, &VFile::setModified);
m_extraSelections.resize((int)SelectionId::MaxSelection);
updateFontAndPalette();
updateConfig();
connect(this, &VEdit::cursorPositionChanged,
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()
{
updateFontAndPalette();
updateConfig();
setReadOnly(false);
setModified(false);
}
@ -404,7 +440,7 @@ void VEdit::highlightCurrentLine()
if (vconfig.getHighlightCursorLine() && !isReadOnly()) {
// Need to highlight current line.
QTextEdit::ExtraSelection select;
select.format.setBackground(m_cursorLineColor);
select.format.setBackground(m_config.m_cursorLineBg);
select.format.setProperty(QTextFormat::FullWidthSelection, true);
select.cursor = textCursor();
select.cursor.clearSelection();
@ -651,3 +687,7 @@ void VEdit::handleCursorPositionChanged()
lastCursor = cursor;
}
VEditConfig &VEdit::getConfig()
{
return m_config;
}

View File

@ -7,6 +7,7 @@
#include <QVector>
#include <QList>
#include <QColor>
#include <QFontMetrics>
#include "vconstants.h"
#include "vtoc.h"
#include "vfile.h"
@ -23,6 +24,27 @@ enum class SelectionId {
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
{
Q_OBJECT
@ -51,11 +73,19 @@ public:
void clearSearchedWordHighlight();
VFile *getFile() const;
VEditConfig &getConfig();
signals:
void saveAndRead();
void discardAndRead();
void editNote();
// Emit when m_config has been updated.
void configUpdated();
public slots:
virtual void highlightCurrentLine();
private slots:
void labelTimerTimeout();
void highlightSelectedWord();
@ -65,17 +95,18 @@ private slots:
void highlightTrailingSpace();
void handleCursorPositionChanged();
protected slots:
virtual void highlightCurrentLine();
protected:
QPointer<VFile> m_file;
VEditOperations *m_editOps;
QColor m_cursorLineColor;
VEditConfig m_config;
virtual void updateFontAndPalette();
virtual void contextMenuEvent(QContextMenuEvent *p_event) Q_DECL_OVERRIDE;
// Update m_config according to VConfigManager.
void updateConfig();
private:
QLabel *m_wrapLabel;
QTimer *m_labelTimer;

View File

@ -4,14 +4,20 @@
#include "vedit.h"
#include "veditoperations.h"
#include "vconfigmanager.h"
#include "utils/vutils.h"
extern VConfigManager vconfig;
VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file)
: QObject(p_editor), m_editor(p_editor), m_file(p_file), m_expandTab(false),
m_keyState(KeyState::Normal), m_pendingTime(2)
: QObject(p_editor), m_editor(p_editor), m_file(p_file),
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)
@ -25,14 +31,46 @@ VEditOperations::~VEditOperations()
{
}
void VEditOperations::updateTabSettings()
void VEditOperations::updateCursorLineBg()
{
if (vconfig.getTabStopWidth() > 0) {
QFontMetrics metrics(vconfig.getMdEditFont());
m_editor->setTabStopWidth(vconfig.getTabStopWidth() * metrics.width(' '));
if (m_editConfig->m_enableVimMode) {
switch (m_vim->getMode()) {
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();
if (m_expandTab && (vconfig.getTabStopWidth() > 0)) {
m_tabSpaces = QString(vconfig.getTabStopWidth(), ' ');
} else {
m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorCurrentLineBg());
}
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 <QList>
#include "vfile.h"
#include "utils/vvim.h"
class VEdit;
class VEditConfig;
class QMimeData;
class QKeyEvent;
enum class KeyState { Normal = 0, Vim, VimVisual};
class VEditOperations: public QObject
{
Q_OBJECT
@ -22,25 +22,29 @@ public:
virtual bool insertImageFromMimeData(const QMimeData *source) = 0;
virtual bool insertImage() = 0;
virtual bool insertImageFromURL(const QUrl &p_imageUrl) = 0;
// Return true if @p_event has been handled and no need to be further
// processed.
virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0;
void updateTabSettings();
signals:
void keyStateChanged(KeyState p_state);
protected slots:
// 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:
void insertTextAtCurPos(const QString &p_text);
VEdit *m_editor;
QPointer<VFile> m_file;
bool m_expandTab;
QString m_tabSpaces;
KeyState m_keyState;
// Seconds for pending mode.
int m_pendingTime;
QList<QString> m_pendingKey;
VEditConfig *m_editConfig;
VVim *m_vim;
};
#endif // VEDITOPERATIONS_H

View File

@ -605,6 +605,13 @@ void VMainWindow::initEditMenu()
connect(autoListAct, &QAction::triggered,
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.
QAction *cursorLineAct = new QAction(tr("Highlight Cursor Line"), this);
cursorLineAct->setToolTip(tr("Highlight current cursor line"));
@ -687,6 +694,9 @@ void VMainWindow::initEditMenu()
}
Q_ASSERT(!(autoListAct->isChecked() && !m_autoIndentAct->isChecked()));
editMenu->addAction(vimAct);
vimAct->setChecked(vconfig.getEnableVimMode());
editMenu->addSeparator();
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)
{
vconfig.setEnableCodeBlockHighlight(p_checked);

View File

@ -76,6 +76,7 @@ private slots:
void handleCaptainModeChanged(bool p_enabled);
void changeAutoIndent(bool p_checked);
void changeAutoList(bool p_checked);
void changeVimMode(bool p_checked);
void enableCodeBlockHighlight(bool p_checked);
void enableImagePreview(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_editOps = new VMdEditOperations(this, m_file);
connect(m_editOps, &VEditOperations::keyStateChanged,
this, &VMdEdit::handleEditStateChanged);
connect(this, &VMdEdit::cursorPositionChanged,
this, &VMdEdit::updateCurHeader);
@ -56,22 +54,23 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
connect(QApplication::clipboard(), &QClipboard::changed,
this, &VMdEdit::handleClipboardChanged);
m_editOps->updateTabSettings();
updateFontAndPalette();
updateConfig();
}
void VMdEdit::updateFontAndPalette()
{
setFont(vconfig.getMdEditFont());
setPalette(vconfig.getMdEditPalette());
m_cursorLineColor = vconfig.getEditorCurrentLineBackground();
}
void VMdEdit::beginEdit()
{
m_editOps->updateTabSettings();
updateFontAndPalette();
updateConfig();
Q_ASSERT(m_file->getContent() == toPlainTextWithoutImg());
initInitImages();
@ -366,17 +365,6 @@ int VMdEdit::removeObjectReplacementLine(QString &p_text, int p_index) const
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()
{
if (!vconfig.getEnablePreviewImages()) {

View File

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

View File

@ -21,6 +21,7 @@
#include "vfile.h"
#include "vmdedit.h"
#include "vconfigmanager.h"
#include "utils/vvim.h"
extern VConfigManager vconfig;
@ -29,10 +30,6 @@ const QString VMdEditOperations::c_defaultImageTitle = "image";
VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
: 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)
@ -193,32 +190,17 @@ bool VMdEditOperations::insertImage()
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)
{
if (m_editConfig->m_enableVimMode && m_vim->handleKeyPressEvent(p_event)) {
m_autoIndentPos = -1;
return true;
}
bool ret = false;
int key = p_event->key();
int modifiers = p_event->modifiers();
if (shouldTriggerVimMode(p_event)) {
if (handleKeyPressVim(p_event)) {
ret = true;
goto exit;
}
} else {
switch (key) {
case Qt::Key_1:
case Qt::Key_2:
@ -265,15 +247,6 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
break;
}
case Qt::Key_D:
{
if (handleKeyD(p_event)) {
ret = true;
goto exit;
}
break;
}
case Qt::Key_H:
{
if (handleKeyH(p_event)) {
@ -349,7 +322,6 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
default:
break;
}
}
exit:
// Qt::Key_Return, Qt::Key_Tab and Qt::Key_Backtab will handle m_autoIndentPos.
@ -357,44 +329,32 @@ exit:
key != Qt::Key_Shift) {
m_autoIndentPos = -1;
}
return ret;
}
// Let Ctrl+[ behave exactly like ESC.
bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
{
// 1. If it is not in Normal state, just go back to Normal state;
// 2. If it is already Normal state, try to clear selection;
// 3. Otherwise, ignore this event and let parent handles it.
bool accept = false;
// 1. If there is any selection, clear it.
// 2. Otherwise, ignore this event and let parent handles it.
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();
if (cursor.hasSelection()) {
cursor.clearSelection();
m_editor->setTextCursor(cursor);
accept = true;
}
}
}
if (accept) {
p_event->accept();
return true;
}
return accept;
}
return false;
}
bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
{
QTextDocument *doc = m_editor->document();
QString text("\t");
if (m_expandTab) {
text = m_tabSpaces;
}
QString text(m_editConfig->m_tabSpaces);
if (p_event->modifiers() == Qt::NoModifier) {
QTextCursor cursor = m_editor->textCursor();
@ -483,8 +443,8 @@ bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
continue;
} else {
// Spaces.
if (m_expandTab) {
int width = m_tabSpaces.size();
if (m_editConfig->m_expandTab) {
int width = m_editConfig->m_tabSpaces.size();
for (int i = 0; i < width; ++i) {
if (text[i] == ' ') {
blockCursor.deleteChar();
@ -555,20 +515,6 @@ bool VMdEditOperations::handleKeyB(QKeyEvent *p_event)
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)
{
if (p_event->modifiers() == Qt::ControlModifier) {
@ -713,27 +659,17 @@ bool VMdEditOperations::handleKeyW(QKeyEvent *p_event)
bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event)
{
// 1. If it is not in Normal state, just go back to Normal state;
// 2. If it is already Normal state, try to clear selection;
// 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 {
// 1. If there is any selection, clear it.
// 2. Otherwise, ignore this event and let parent handles it.
QTextCursor cursor = m_editor->textCursor();
if (cursor.hasSelection()) {
cursor.clearSelection();
m_editor->setTextCursor(cursor);
accept = true;
}
}
if (accept) {
p_event->accept();
return true;
}
return accept;
return false;
}
bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
@ -935,399 +871,6 @@ void VMdEditOperations::deleteIndentAndListMark()
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)
{
Q_ASSERT(p_level > 0 && p_level < 7);

View File

@ -21,9 +21,6 @@ public:
bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
bool insertImageFromURL(const QUrl &p_imageUrl) Q_DECL_OVERRIDE;
private slots:
void pendingTimerTimeout();
private:
void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath);
@ -32,13 +29,10 @@ private:
// @image: the image to be inserted;
void insertImageFromQImage(const QString &title, const QString &path, const QImage &image);
void setKeyState(KeyState p_state);
// Key press handlers.
bool handleKeyTab(QKeyEvent *p_event);
bool handleKeyBackTab(QKeyEvent *p_event);
bool handleKeyB(QKeyEvent *p_event);
bool handleKeyD(QKeyEvent *p_event);
bool handleKeyH(QKeyEvent *p_event);
bool handleKeyI(QKeyEvent *p_event);
bool handleKeyO(QKeyEvent *p_event);
@ -46,11 +40,7 @@ private:
bool handleKeyW(QKeyEvent *p_event);
bool handleKeyEsc(QKeyEvent *p_event);
bool handleKeyReturn(QKeyEvent *p_event);
bool handleKeyPressVim(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 insertNewBlockWithIndent();
bool insertListMarkAsPreviousLine();
@ -67,7 +57,6 @@ private:
// Change the sequence number of a list block.
void changeListBlockSeqNumber(QTextBlock &p_block, int p_seq);
QTimer *m_pendingTimer;
// 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.
int m_autoIndentPos;