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);
}
void VAvatar::paintEvent(QPaintEvent *p_event)
void VAvatar::paintEvent(QPaintEvent * /*p_event*/)
{
int diameter = width();
int x = diameter / 2;

View File

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

View File

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

View File

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

View File

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