support more Vim key bindings

1. Esc or Ctrl + [ to clear selection or exit Vim mode;
2. v to enter visual mode;
3. y to copy selected text; "y2j" is not supported yet;

Signed-off-by: Le Tan <tamlokveer@gmail.com>
This commit is contained in:
Le Tan 2016-12-25 10:13:32 +08:00
parent 58c8506855
commit 76bd2c7d64
5 changed files with 132 additions and 32 deletions

View File

@ -13,7 +13,7 @@ VAvatar::VAvatar(QWidget *p_parent)
resize(m_diameter, m_diameter); resize(m_diameter, m_diameter);
} }
void VAvatar::paintEvent(QPaintEvent *p_event) void VAvatar::paintEvent(QPaintEvent * /*p_event*/)
{ {
int diameter = width(); int diameter = width();
int x = diameter / 2; int x = diameter / 2;

View File

@ -11,7 +11,7 @@ class VEdit;
class QMimeData; class QMimeData;
class QKeyEvent; class QKeyEvent;
enum class KeyState { Normal = 0, Vim }; enum class KeyState { Normal = 0, Vim, VimVisual};
class VEditOperations: public QObject class VEditOperations: public QObject
{ {

View File

@ -10,6 +10,8 @@
#include <QTextCursor> #include <QTextCursor>
#include <QTimer> #include <QTimer>
#include <QGuiApplication> #include <QGuiApplication>
#include <QApplication>
#include <QClipboard>
#include "vmdeditoperations.h" #include "vmdeditoperations.h"
#include "dialog/vinsertimagedialog.h" #include "dialog/vinsertimagedialog.h"
#include "utils/vutils.h" #include "utils/vutils.h"
@ -173,11 +175,16 @@ bool VMdEditOperations::insertImage()
// Will modify m_pendingKey. // Will modify m_pendingKey.
bool VMdEditOperations::shouldTriggerVimMode(QKeyEvent *p_event) bool VMdEditOperations::shouldTriggerVimMode(QKeyEvent *p_event)
{ {
if (m_keyState == KeyState::Vim) { 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 true;
} else { } else {
if (p_event->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) { if (modifiers == (Qt::ControlModifier | Qt::AltModifier)) {
switch (p_event->key()) { switch (key) {
// Should add one item for each supported Ctrl+ALT+<Key> Vim binding. // Should add one item for each supported Ctrl+ALT+<Key> Vim binding.
case Qt::Key_H: case Qt::Key_H:
case Qt::Key_J: case Qt::Key_J:
@ -198,6 +205,8 @@ bool VMdEditOperations::shouldTriggerVimMode(QKeyEvent *p_event)
case Qt::Key_E: case Qt::Key_E:
case Qt::Key_B: case Qt::Key_B:
case Qt::Key_G: case Qt::Key_G:
case Qt::Key_V:
case Qt::Key_Y:
case Qt::Key_Dollar: case Qt::Key_Dollar:
case Qt::Key_AsciiCircum: case Qt::Key_AsciiCircum:
{ {
@ -207,9 +216,9 @@ bool VMdEditOperations::shouldTriggerVimMode(QKeyEvent *p_event)
m_pendingKey.clear(); m_pendingKey.clear();
break; break;
} }
} else if (p_event->modifiers() == } else if (modifiers ==
(Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) { (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
switch (p_event->key()) { switch (key) {
case Qt::Key_G: case Qt::Key_G:
case Qt::Key_Dollar: case Qt::Key_Dollar:
case Qt::Key_AsciiCircum: case Qt::Key_AsciiCircum:
@ -299,6 +308,21 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
break; break;
} }
case Qt::Key_BracketLeft:
{
if (p_event->modifiers() != Qt::ControlModifier) {
break;
}
// Ctrl+[, Fall through.
}
case Qt::Key_Escape:
{
if (handleKeyEsc(p_event)) {
return true;
}
break;
}
default: default:
break; break;
} }
@ -544,11 +568,29 @@ bool VMdEditOperations::handleKeyW(QKeyEvent *p_event)
return false; return false;
} }
bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event)
{
// Esc, clear any Vim mode, clear selection.
QTextCursor cursor = m_editor->textCursor();
cursor.clearSelection();
m_editor->setTextCursor(cursor);
m_pendingTimer->stop();
m_keyState = KeyState::Normal;
m_pendingKey.clear();
p_event->accept();
return true;
}
bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event) bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
{ {
int modifiers = p_event->modifiers(); int modifiers = p_event->modifiers();
bool ctrlAlt = modifiers == (Qt::ControlModifier | Qt::AltModifier); bool ctrlAlt = modifiers == (Qt::ControlModifier | Qt::AltModifier);
bool ctrlShiftAlt = modifiers == (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier); bool ctrlShiftAlt = modifiers == (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier);
bool visualMode = m_keyState == KeyState::VimVisual;
QTextCursor::MoveMode mode = visualMode ? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor;
switch (p_event->key()) { switch (p_event->key()) {
// Ctrl and Shift may be sent out first. // Ctrl and Shift may be sent out first.
@ -583,9 +625,11 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
// Move cursor <repeat> characters left/Down/Up/Right. // Move cursor <repeat> characters left/Down/Up/Right.
int repeat = keySeqToNumber(m_pendingKey); int repeat = keySeqToNumber(m_pendingKey);
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(op, QTextCursor::MoveAnchor, cursor.movePosition(op, mode, repeat == 0 ? 1 : repeat);
repeat == 0 ? 1 : repeat);
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
} }
break; break;
} }
@ -640,9 +684,11 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
if (repeat == 0) { if (repeat == 0) {
repeat = 1; repeat = 1;
} }
cursor.movePosition(QTextCursor::NextWord, QTextCursor::MoveAnchor, cursor.movePosition(QTextCursor::NextWord, mode, repeat);
repeat);
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
} }
break; break;
} }
@ -660,18 +706,20 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
cursor.beginEditBlock(); cursor.beginEditBlock();
int pos = cursor.position(); int pos = cursor.position();
// First move to the end of current word. // First move to the end of current word.
cursor.movePosition(QTextCursor::EndOfWord); cursor.movePosition(QTextCursor::EndOfWord, mode);
if (cursor.position() != pos) { if (cursor.position() != pos) {
// We did move. // We did move.
repeat--; repeat--;
} }
if (repeat) { if (repeat) {
cursor.movePosition(QTextCursor::NextWord, QTextCursor::MoveAnchor, cursor.movePosition(QTextCursor::NextWord, mode, repeat);
repeat); cursor.movePosition(QTextCursor::EndOfWord, mode);
cursor.movePosition(QTextCursor::EndOfWord);
} }
cursor.endEditBlock(); cursor.endEditBlock();
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
} }
break; break;
} }
@ -689,18 +737,19 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
cursor.beginEditBlock(); cursor.beginEditBlock();
int pos = cursor.position(); int pos = cursor.position();
// First move to the start of current word. // First move to the start of current word.
cursor.movePosition(QTextCursor::StartOfWord); cursor.movePosition(QTextCursor::StartOfWord, mode);
if (cursor.position() != pos) { if (cursor.position() != pos) {
// We did move. // We did move.
repeat--; repeat--;
} }
if (repeat) { if (repeat) {
cursor.movePosition(QTextCursor::PreviousWord, cursor.movePosition(QTextCursor::PreviousWord, mode, repeat);
QTextCursor::MoveAnchor,
repeat);
} }
cursor.endEditBlock(); cursor.endEditBlock();
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
} }
break; break;
} }
@ -710,8 +759,11 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
if (modifiers == Qt::NoModifier || ctrlAlt) { if (modifiers == Qt::NoModifier || ctrlAlt) {
if (keySeqToNumber(m_pendingKey) == 0) { if (keySeqToNumber(m_pendingKey) == 0) {
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(QTextCursor::StartOfLine); cursor.movePosition(QTextCursor::StartOfLine, mode);
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
} else { } else {
m_pendingKey.append("0"); m_pendingKey.append("0");
goto pending; goto pending;
@ -726,8 +778,11 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
if (m_pendingKey.isEmpty()) { if (m_pendingKey.isEmpty()) {
// Go to end of line. // Go to end of line.
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(QTextCursor::EndOfLine); cursor.movePosition(QTextCursor::EndOfLine, mode);
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
} }
} }
break; break;
@ -743,17 +798,20 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
QString text = block.text(); QString text = block.text();
cursor.beginEditBlock(); cursor.beginEditBlock();
if (text.trimmed().isEmpty()) { if (text.trimmed().isEmpty()) {
cursor.movePosition(QTextCursor::StartOfLine); cursor.movePosition(QTextCursor::StartOfLine, mode);
} else { } else {
cursor.movePosition(QTextCursor::StartOfLine); cursor.movePosition(QTextCursor::StartOfLine, mode);
int pos = cursor.positionInBlock(); int pos = cursor.positionInBlock();
while (pos < text.size() && text[pos].isSpace()) { while (pos < text.size() && text[pos].isSpace()) {
cursor.movePosition(QTextCursor::NextWord); cursor.movePosition(QTextCursor::NextWord, mode);
pos = cursor.positionInBlock(); pos = cursor.positionInBlock();
} }
} }
cursor.endEditBlock(); cursor.endEditBlock();
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
} }
} }
break; break;
@ -767,28 +825,62 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
m_pendingKey.append("g"); m_pendingKey.append("g");
goto pending; goto pending;
} else if (m_pendingKey.size() == 1 && m_pendingKey.at(0) == "g") { } else if (m_pendingKey.size() == 1 && m_pendingKey.at(0) == "g") {
m_pendingKey.clear();
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::Start, mode);
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
} }
} else if (modifiers == Qt::ShiftModifier || ctrlShiftAlt) { } else if (modifiers == Qt::ShiftModifier || ctrlShiftAlt) {
// G, go to a certain line or the end of document. // G, go to a certain line or the end of document.
int lineNum = keySeqToNumber(m_pendingKey); int lineNum = keySeqToNumber(m_pendingKey);
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
qDebug() << "G:" << lineNum;
if (lineNum == 0) { if (lineNum == 0) {
cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::End, mode);
} else { } else {
QTextDocument *doc = m_editor->document(); QTextDocument *doc = m_editor->document();
QTextBlock block = doc->findBlockByNumber(lineNum - 1); QTextBlock block = doc->findBlockByNumber(lineNum - 1);
if (block.isValid()) { if (block.isValid()) {
cursor.setPosition(block.position()); cursor.setPosition(block.position(), mode);
} else { } else {
// Go beyond the document. // Go beyond the document.
cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::End, mode);
} }
} }
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
if (visualMode) {
goto pending;
}
}
break;
}
case Qt::Key_V:
{
if (modifiers == Qt::NoModifier || ctrlAlt) {
// V to enter visual mode.
if (m_pendingKey.isEmpty() && m_keyState != KeyState::VimVisual) {
m_keyState = KeyState::VimVisual;
goto pending;
}
}
break;
}
case Qt::Key_Y:
{
// TODO: support y2j.
if (modifiers == Qt::NoModifier || ctrlAlt) {
if (m_pendingKey.isEmpty()) {
QTextCursor cursor = m_editor->textCursor();
if (cursor.hasSelection()) {
QString text = cursor.selectedText();
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text);
}
}
} }
break; break;
} }
@ -798,9 +890,15 @@ bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
break; break;
} }
m_pendingTimer->stop();
if (m_keyState == KeyState::VimVisual) {
// Clear the visual selection.
QTextCursor cursor = m_editor->textCursor();
cursor.clearSelection();
m_editor->setTextCursor(cursor);
}
m_keyState = KeyState::Normal; m_keyState = KeyState::Normal;
m_pendingKey.clear(); m_pendingKey.clear();
m_pendingTimer->stop();
p_event->accept(); p_event->accept();
return true; return true;
@ -833,7 +931,8 @@ void VMdEditOperations::pendingTimerTimeout()
{ {
qDebug() << "key pending timer timeout"; qDebug() << "key pending timer timeout";
int modifiers = QGuiApplication::keyboardModifiers(); int modifiers = QGuiApplication::keyboardModifiers();
if (modifiers == (Qt::ControlModifier | Qt::AltModifier)) { if (modifiers == (Qt::ControlModifier | Qt::AltModifier)
|| m_keyState == KeyState::VimVisual) {
m_pendingTimer->start(); m_pendingTimer->start();
return; return;
} }

View File

@ -37,6 +37,7 @@ private:
bool handleKeyI(QKeyEvent *p_event); bool handleKeyI(QKeyEvent *p_event);
bool handleKeyU(QKeyEvent *p_event); bool handleKeyU(QKeyEvent *p_event);
bool handleKeyW(QKeyEvent *p_event); bool handleKeyW(QKeyEvent *p_event);
bool handleKeyEsc(QKeyEvent *p_event);
bool handleKeyPressVim(QKeyEvent *p_event); bool handleKeyPressVim(QKeyEvent *p_event);
bool shouldTriggerVimMode(QKeyEvent *p_event); bool shouldTriggerVimMode(QKeyEvent *p_event);
int keySeqToNumber(const QList<QString> &p_seq); int keySeqToNumber(const QList<QString> &p_seq);

View File

@ -76,7 +76,7 @@ void VOutline::expandTree()
expandAll(); expandAll();
} }
void VOutline::handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem) void VOutline::handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem * /*p_preItem*/)
{ {
if (!p_curItem) { if (!p_curItem) {
return; return;