mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
support Vim mode key bindings
After hitting `Ctrl+D` or keeping pressing `Ctrl+Alt`, VNote will enter Vim normal pending mode. 1. Support <num>h,j,k,l to move cursor around; TODO: Design a finite state machine to handle more Vim commands elegantly. Signed-off-by: Le Tan <tamlokveer@gmail.com>
This commit is contained in:
parent
d2f61bc605
commit
26016dd44a
@ -18,15 +18,10 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
|
|||||||
|
|
||||||
VEdit::~VEdit()
|
VEdit::~VEdit()
|
||||||
{
|
{
|
||||||
qDebug() << "VEdit destruction";
|
|
||||||
if (m_file) {
|
if (m_file) {
|
||||||
disconnect(document(), &QTextDocument::modificationChanged,
|
disconnect(document(), &QTextDocument::modificationChanged,
|
||||||
(VFile *)m_file, &VFile::setModified);
|
(VFile *)m_file, &VFile::setModified);
|
||||||
}
|
}
|
||||||
if (m_editOps) {
|
|
||||||
delete m_editOps;
|
|
||||||
m_editOps = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VEdit::beginEdit()
|
void VEdit::beginEdit()
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
extern VConfigManager vconfig;
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file)
|
VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file)
|
||||||
: m_editor(p_editor), m_file(p_file), m_expandTab(false),
|
: QObject(p_editor), m_editor(p_editor), m_file(p_file), m_expandTab(false),
|
||||||
m_keyState(KeyState::Normal)
|
m_keyState(KeyState::Normal), m_pendingTime(2)
|
||||||
{
|
{
|
||||||
updateTabSettings();
|
updateTabSettings();
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
#include "vfile.h"
|
#include "vfile.h"
|
||||||
|
|
||||||
class VEdit;
|
class VEdit;
|
||||||
@ -11,8 +13,9 @@ class QKeyEvent;
|
|||||||
|
|
||||||
enum class KeyState { Normal = 0, Vim };
|
enum class KeyState { Normal = 0, Vim };
|
||||||
|
|
||||||
class VEditOperations
|
class VEditOperations: public QObject
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
VEditOperations(VEdit *p_editor, VFile *p_file);
|
VEditOperations(VEdit *p_editor, VFile *p_file);
|
||||||
virtual ~VEditOperations();
|
virtual ~VEditOperations();
|
||||||
@ -32,6 +35,9 @@ protected:
|
|||||||
bool m_expandTab;
|
bool m_expandTab;
|
||||||
QString m_tabSpaces;
|
QString m_tabSpaces;
|
||||||
KeyState m_keyState;
|
KeyState m_keyState;
|
||||||
|
// Seconds for pending mode.
|
||||||
|
int m_pendingTime;
|
||||||
|
QList<QString> m_pendingKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VEDITOPERATIONS_H
|
#endif // VEDITOPERATIONS_H
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
#include <QTimer>
|
||||||
#include "vmdeditoperations.h"
|
#include "vmdeditoperations.h"
|
||||||
#include "dialog/vinsertimagedialog.h"
|
#include "dialog/vinsertimagedialog.h"
|
||||||
#include "utils/vutils.h"
|
#include "utils/vutils.h"
|
||||||
@ -20,6 +20,10 @@
|
|||||||
VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
|
VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
|
||||||
: VEditOperations(p_editor, p_file)
|
: VEditOperations(p_editor, p_file)
|
||||||
{
|
{
|
||||||
|
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)
|
||||||
@ -28,7 +32,7 @@ bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
|
|||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
VInsertImageDialog dialog(QObject::tr("Insert image from clipboard"), QObject::tr("image_title"),
|
VInsertImageDialog dialog(tr("Insert image from clipboard"), tr("image_title"),
|
||||||
"", (QWidget *)m_editor);
|
"", (QWidget *)m_editor);
|
||||||
dialog.setBrowseable(false);
|
dialog.setBrowseable(false);
|
||||||
dialog.setImage(image);
|
dialog.setImage(image);
|
||||||
@ -48,7 +52,7 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
|
|||||||
VUtils::makeDirectory(path);
|
VUtils::makeDirectory(path);
|
||||||
bool ret = image.save(filePath);
|
bool ret = image.save(filePath);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
QMessageBox msgBox(QMessageBox::Warning, QObject::tr("Warning"), QString("Fail to save image %1").arg(filePath),
|
QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Fail to save image %1").arg(filePath),
|
||||||
QMessageBox::Ok, (QWidget *)m_editor);
|
QMessageBox::Ok, (QWidget *)m_editor);
|
||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
return;
|
return;
|
||||||
@ -73,7 +77,7 @@ void VMdEditOperations::insertImageFromPath(const QString &title,
|
|||||||
bool ret = QFile::copy(oriImagePath, filePath);
|
bool ret = QFile::copy(oriImagePath, filePath);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
qWarning() << "error: fail to copy" << oriImagePath << "to" << filePath;
|
qWarning() << "error: fail to copy" << oriImagePath << "to" << filePath;
|
||||||
QMessageBox msgBox(QMessageBox::Warning, QObject::tr("Warning"), QString("Fail to save image %1").arg(filePath),
|
QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Fail to save image %1").arg(filePath),
|
||||||
QMessageBox::Ok, (QWidget *)m_editor);
|
QMessageBox::Ok, (QWidget *)m_editor);
|
||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
return;
|
return;
|
||||||
@ -154,7 +158,7 @@ bool VMdEditOperations::insertURLFromMimeData(const QMimeData *source)
|
|||||||
|
|
||||||
bool VMdEditOperations::insertImage()
|
bool VMdEditOperations::insertImage()
|
||||||
{
|
{
|
||||||
VInsertImageDialog dialog(QObject::tr("Insert Image From File"), QObject::tr("image_title"),
|
VInsertImageDialog dialog(tr("Insert Image From File"), tr("image_title"),
|
||||||
"", (QWidget *)m_editor);
|
"", (QWidget *)m_editor);
|
||||||
if (dialog.exec() == QDialog::Accepted) {
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
QString title = dialog.getImageTitleInput();
|
QString title = dialog.getImageTitleInput();
|
||||||
@ -165,8 +169,44 @@ bool VMdEditOperations::insertImage()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VMdEditOperations::shouldTriggerVimMode(QKeyEvent *p_event)
|
||||||
|
{
|
||||||
|
if (m_keyState == KeyState::Vim) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (p_event->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) {
|
||||||
|
switch (p_event->key()) {
|
||||||
|
// Should add one item for each supported Ctrl+ALT+<Key> Vim binding.
|
||||||
|
case Qt::Key_H:
|
||||||
|
case Qt::Key_J:
|
||||||
|
case Qt::Key_K:
|
||||||
|
case Qt::Key_L:
|
||||||
|
case Qt::Key_0:
|
||||||
|
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:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
|
bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
|
||||||
{
|
{
|
||||||
|
if (shouldTriggerVimMode(p_event)) {
|
||||||
|
if (handleKeyPressVim(p_event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
switch (p_event->key()) {
|
switch (p_event->key()) {
|
||||||
case Qt::Key_Tab:
|
case Qt::Key_Tab:
|
||||||
{
|
{
|
||||||
@ -184,30 +224,6 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::Key_H:
|
|
||||||
{
|
|
||||||
if (handleKeyH(p_event)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Qt::Key_W:
|
|
||||||
{
|
|
||||||
if (handleKeyW(p_event)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Qt::Key_U:
|
|
||||||
{
|
|
||||||
if (handleKeyU(p_event)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Qt::Key_B:
|
case Qt::Key_B:
|
||||||
{
|
{
|
||||||
if (handleKeyB(p_event)) {
|
if (handleKeyB(p_event)) {
|
||||||
@ -216,6 +232,22 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Qt::Key_D:
|
||||||
|
{
|
||||||
|
if (handleKeyD(p_event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Qt::Key_H:
|
||||||
|
{
|
||||||
|
if (handleKeyH(p_event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case Qt::Key_I:
|
case Qt::Key_I:
|
||||||
{
|
{
|
||||||
if (handleKeyI(p_event)) {
|
if (handleKeyI(p_event)) {
|
||||||
@ -224,12 +256,27 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
case Qt::Key_U:
|
||||||
// Fall through.
|
{
|
||||||
|
if (handleKeyU(p_event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_keyState = KeyState::Normal;
|
case Qt::Key_W:
|
||||||
|
{
|
||||||
|
if (handleKeyW(p_event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,6 +320,9 @@ bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
|
|||||||
|
|
||||||
bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
|
bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
|
||||||
{
|
{
|
||||||
|
if (p_event->modifiers() != Qt::ShiftModifier) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
QTextDocument *doc = m_editor->document();
|
QTextDocument *doc = m_editor->document();
|
||||||
QTextCursor cursor = m_editor->textCursor();
|
QTextCursor cursor = m_editor->textCursor();
|
||||||
QTextBlock block = doc->findBlock(cursor.selectionStart());
|
QTextBlock block = doc->findBlock(cursor.selectionStart());
|
||||||
@ -345,6 +395,20 @@ 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.
|
||||||
|
m_keyState = 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) {
|
||||||
@ -427,3 +491,106 @@ bool VMdEditOperations::handleKeyW(QKeyEvent *p_event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event)
|
||||||
|
{
|
||||||
|
int modifiers = p_event->modifiers();
|
||||||
|
bool ctrlAlt = modifiers == (Qt::ControlModifier | Qt::AltModifier);
|
||||||
|
switch (p_event->key()) {
|
||||||
|
// Ctrl may be sent out first.
|
||||||
|
case Qt::Key_Control:
|
||||||
|
{
|
||||||
|
goto pending;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Qt::Key_H:
|
||||||
|
case Qt::Key_J:
|
||||||
|
case Qt::Key_K:
|
||||||
|
case Qt::Key_L:
|
||||||
|
{
|
||||||
|
if (modifiers == Qt::NoModifier || ctrlAlt) {
|
||||||
|
QTextCursor::MoveOperation op;
|
||||||
|
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);
|
||||||
|
QTextCursor cursor = m_editor->textCursor();
|
||||||
|
cursor.movePosition(op, QTextCursor::MoveAnchor,
|
||||||
|
repeat == 0 ? 1 : repeat);
|
||||||
|
m_editor->setTextCursor(cursor);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Qt::Key_0:
|
||||||
|
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 || ctrlAlt) {
|
||||||
|
int num = p_event->key() - Qt::Key_0;
|
||||||
|
m_pendingKey.append(QString::number(num));
|
||||||
|
goto pending;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unknown key. End Vim mode.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_keyState = KeyState::Normal;
|
||||||
|
m_pendingKey.clear();
|
||||||
|
m_pendingTimer->stop();
|
||||||
|
p_event->accept();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
pending:
|
||||||
|
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";
|
||||||
|
m_keyState = KeyState::Normal;
|
||||||
|
m_pendingKey.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -7,9 +7,12 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include "veditoperations.h"
|
#include "veditoperations.h"
|
||||||
|
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
// Editor operations for Markdown
|
// Editor operations for Markdown
|
||||||
class VMdEditOperations : public VEditOperations
|
class VMdEditOperations : public VEditOperations
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
VMdEditOperations(VEdit *p_editor, VFile *p_file);
|
VMdEditOperations(VEdit *p_editor, VFile *p_file);
|
||||||
bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
|
bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
|
||||||
@ -17,6 +20,9 @@ public:
|
|||||||
bool insertImage() Q_DECL_OVERRIDE;
|
bool insertImage() Q_DECL_OVERRIDE;
|
||||||
bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
|
bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void pendingTimerTimeout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool insertImageFromURL(const QUrl &imageUrl);
|
bool insertImageFromURL(const QUrl &imageUrl);
|
||||||
void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath);
|
void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath);
|
||||||
@ -26,10 +32,16 @@ private:
|
|||||||
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 handleKeyU(QKeyEvent *p_event);
|
bool handleKeyU(QKeyEvent *p_event);
|
||||||
bool handleKeyW(QKeyEvent *p_event);
|
bool handleKeyW(QKeyEvent *p_event);
|
||||||
|
bool handleKeyPressVim(QKeyEvent *p_event);
|
||||||
|
bool shouldTriggerVimMode(QKeyEvent *p_event);
|
||||||
|
int keySeqToNumber(const QList<QString> &p_seq);
|
||||||
|
|
||||||
|
QTimer *m_pendingTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VMDEDITOPERATIONS_H
|
#endif // VMDEDITOPERATIONS_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user