mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-06 14:29:54 +08:00
vim-mode: support Copy/Paste/Change/Indent/UnIndent/ToLower/ToUpper action
This commit is contained in:
parent
f6a91d04a8
commit
5047e19b24
@ -19,7 +19,7 @@ void VEditUtils::removeBlock(QTextCursor &p_cursor, QString *p_text)
|
|||||||
|
|
||||||
p_cursor.select(QTextCursor::BlockUnderCursor);
|
p_cursor.select(QTextCursor::BlockUnderCursor);
|
||||||
if (p_text) {
|
if (p_text) {
|
||||||
*p_text = p_cursor.selectedText() + "\n";
|
*p_text = selectedText(p_cursor) + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
p_cursor.deleteChar();
|
p_cursor.deleteChar();
|
||||||
@ -133,3 +133,80 @@ void VEditUtils::moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor,
|
|||||||
p_cursor.setPosition(block.position() + idx, p_mode);
|
p_cursor.setPosition(block.position() + idx, p_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VEditUtils::removeObjectReplacementCharacter(QString &p_text)
|
||||||
|
{
|
||||||
|
QRegExp orcBlockExp(QString("[\\n|^][ |\\t]*\\xfffc[ |\\t]*(?=\\n)"));
|
||||||
|
p_text.remove(orcBlockExp);
|
||||||
|
p_text.remove(QChar::ObjectReplacementCharacter);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VEditUtils::selectedText(const QTextCursor &p_cursor)
|
||||||
|
{
|
||||||
|
QString text = p_cursor.selectedText();
|
||||||
|
text.replace(QChar::ParagraphSeparator, '\n');
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use another QTextCursor to remain the selection.
|
||||||
|
void VEditUtils::indentSelectedBlocks(const QTextDocument *p_doc,
|
||||||
|
const QTextCursor &p_cursor,
|
||||||
|
const QString &p_indentationText,
|
||||||
|
bool p_isIndent)
|
||||||
|
{
|
||||||
|
int nrBlocks = 1;
|
||||||
|
int start = p_cursor.selectionStart();
|
||||||
|
int end = p_cursor.selectionEnd();
|
||||||
|
|
||||||
|
QTextBlock sBlock = p_doc->findBlock(start);
|
||||||
|
if (start != end) {
|
||||||
|
QTextBlock eBlock = p_doc->findBlock(end);
|
||||||
|
nrBlocks = eBlock.blockNumber() - sBlock.blockNumber() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextCursor bCursor(sBlock);
|
||||||
|
bCursor.beginEditBlock();
|
||||||
|
for (int i = 0; i < nrBlocks; ++i) {
|
||||||
|
if (p_isIndent) {
|
||||||
|
indentBlock(bCursor, p_indentationText);
|
||||||
|
} else {
|
||||||
|
unindentBlock(bCursor, p_indentationText);
|
||||||
|
}
|
||||||
|
|
||||||
|
bCursor.movePosition(QTextCursor::NextBlock);
|
||||||
|
}
|
||||||
|
bCursor.endEditBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VEditUtils::indentBlock(QTextCursor &p_cursor,
|
||||||
|
const QString &p_indentationText)
|
||||||
|
{
|
||||||
|
QTextBlock block = p_cursor.block();
|
||||||
|
if (block.length() > 1) {
|
||||||
|
p_cursor.movePosition(QTextCursor::StartOfBlock);
|
||||||
|
p_cursor.insertText(p_indentationText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VEditUtils::unindentBlock(QTextCursor &p_cursor,
|
||||||
|
const QString &p_indentationText)
|
||||||
|
{
|
||||||
|
QTextBlock block = p_cursor.block();
|
||||||
|
QString text = block.text();
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_cursor.movePosition(QTextCursor::StartOfBlock);
|
||||||
|
if (text[0] == '\t') {
|
||||||
|
p_cursor.deleteChar();
|
||||||
|
} else if (text[0].isSpace()) {
|
||||||
|
int width = p_indentationText.size();
|
||||||
|
for (int i = 0; i < width; ++i) {
|
||||||
|
if (text[i] == ' ') {
|
||||||
|
p_cursor.deleteChar();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
|
||||||
|
class QTextDocument;
|
||||||
|
|
||||||
// Utils for text edit.
|
// Utils for text edit.
|
||||||
class VEditUtils
|
class VEditUtils
|
||||||
{
|
{
|
||||||
@ -36,6 +38,31 @@ public:
|
|||||||
// Need to call setTextCursor() to make it take effect.
|
// Need to call setTextCursor() to make it take effect.
|
||||||
static bool insertListMarkAsPreviousBlock(QTextCursor &p_cursor);
|
static bool insertListMarkAsPreviousBlock(QTextCursor &p_cursor);
|
||||||
|
|
||||||
|
// Remove ObjectReplaceCharacter in p_text.
|
||||||
|
// If the ObjectReplaceCharacter is in a block with only other spaces, remove the
|
||||||
|
// whole block.
|
||||||
|
static void removeObjectReplacementCharacter(QString &p_text);
|
||||||
|
|
||||||
|
// p_cursor.selectedText() will use U+2029 (QChar::ParagraphSeparator)
|
||||||
|
// instead of \n for a new line.
|
||||||
|
// This function will translate it to \n.
|
||||||
|
static QString selectedText(const QTextCursor &p_cursor);
|
||||||
|
|
||||||
|
// Indent selected blocks. If no selection, indent current block.
|
||||||
|
// @p_isIndent: whether it is indentation or unindentation.
|
||||||
|
static void indentSelectedBlocks(const QTextDocument *p_doc,
|
||||||
|
const QTextCursor &p_cursor,
|
||||||
|
const QString &p_indentationText,
|
||||||
|
bool p_isIndent);
|
||||||
|
|
||||||
|
// Indent current block.
|
||||||
|
// Skip empty block.
|
||||||
|
static void indentBlock(QTextCursor &p_cursor,
|
||||||
|
const QString &p_indentationText);
|
||||||
|
|
||||||
|
static void unindentBlock(QTextCursor &p_cursor,
|
||||||
|
const QString &p_indentationText);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VEditUtils() {}
|
VEditUtils() {}
|
||||||
};
|
};
|
||||||
|
1655
src/utils/vvim.cpp
1655
src/utils/vvim.cpp
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QDebug>
|
||||||
#include "vutils.h"
|
#include "vutils.h"
|
||||||
|
|
||||||
class VEdit;
|
class VEdit;
|
||||||
@ -101,6 +102,7 @@ private:
|
|||||||
Delete,
|
Delete,
|
||||||
Copy,
|
Copy,
|
||||||
Paste,
|
Paste,
|
||||||
|
PasteBefore,
|
||||||
Change,
|
Change,
|
||||||
Indent,
|
Indent,
|
||||||
UnIndent,
|
UnIndent,
|
||||||
@ -143,7 +145,22 @@ private:
|
|||||||
enum class Range
|
enum class Range
|
||||||
{
|
{
|
||||||
Line = 0,
|
Line = 0,
|
||||||
Word,
|
WordInner,
|
||||||
|
WordAround,
|
||||||
|
WORDInner,
|
||||||
|
WORDAround,
|
||||||
|
QuoteInner,
|
||||||
|
QuoteAround,
|
||||||
|
DoubleQuoteInner,
|
||||||
|
DoubleQuoteAround,
|
||||||
|
ParenthesisInner,
|
||||||
|
ParenthesisAround,
|
||||||
|
BracketInner,
|
||||||
|
BracketAround,
|
||||||
|
AngleBracketInner,
|
||||||
|
AngleBracketAround,
|
||||||
|
BraceInner,
|
||||||
|
BraceAround,
|
||||||
Invalid
|
Invalid
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -257,6 +274,26 @@ private:
|
|||||||
return m_name == c_blackHoleRegister;
|
return m_name == c_blackHoleRegister;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isSelectionRegister() const
|
||||||
|
{
|
||||||
|
return m_name == c_selectionRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isBlock() const
|
||||||
|
{
|
||||||
|
return m_value.endsWith('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// @p_value is the content to update.
|
||||||
|
// If @p_value ends with \n, then it is a block.
|
||||||
|
// When @p_value is a block, we need to add \n at the end if necessary.
|
||||||
|
// If @m_append is true and @p_value is a block, we need to add \n between
|
||||||
|
// them if necessary.
|
||||||
|
void update(const QString &p_value);
|
||||||
|
|
||||||
|
// Read the value of this register.
|
||||||
|
const QString &read();
|
||||||
|
|
||||||
QChar m_name;
|
QChar m_name;
|
||||||
QString m_value;
|
QString m_value;
|
||||||
|
|
||||||
@ -287,6 +324,21 @@ private:
|
|||||||
// @p_tokens is the arguments of the Action::Delete action.
|
// @p_tokens is the arguments of the Action::Delete action.
|
||||||
void processDeleteAction(QList<Token> &p_tokens);
|
void processDeleteAction(QList<Token> &p_tokens);
|
||||||
|
|
||||||
|
// @p_tokens is the arguments of the Action::Copy action.
|
||||||
|
void processCopyAction(QList<Token> &p_tokens);
|
||||||
|
|
||||||
|
// @p_tokens is the arguments of the Action::Paste and Action::PasteBefore action.
|
||||||
|
void processPasteAction(QList<Token> &p_tokens, bool p_pasteBefore);
|
||||||
|
|
||||||
|
// @p_tokens is the arguments of the Action::Change action.
|
||||||
|
void processChangeAction(QList<Token> &p_tokens);
|
||||||
|
|
||||||
|
// @p_tokens is the arguments of the Action::Indent and Action::UnIndent action.
|
||||||
|
void processIndentAction(QList<Token> &p_tokens, bool p_isIndent);
|
||||||
|
|
||||||
|
// @p_tokens is the arguments of the Action::ToLower and Action::ToUpper action.
|
||||||
|
void processToLowerAction(QList<Token> &p_tokens, bool p_toLower);
|
||||||
|
|
||||||
// Clear selection if there is any.
|
// Clear selection if there is any.
|
||||||
// Returns true if there is selection.
|
// Returns true if there is selection.
|
||||||
bool clearSelection();
|
bool clearSelection();
|
||||||
@ -331,19 +383,44 @@ private:
|
|||||||
|
|
||||||
// Delete selected text if there is any.
|
// Delete selected text if there is any.
|
||||||
// @p_clearEmptyBlock: whether to remove the empty block after deletion.
|
// @p_clearEmptyBlock: whether to remove the empty block after deletion.
|
||||||
void deleteSelectedText(bool p_clearEmptyBlock);
|
|
||||||
|
|
||||||
void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock);
|
void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock);
|
||||||
|
|
||||||
// Save @p_text to the Register pointed by m_register.
|
// Copy selected text if there is any.
|
||||||
|
// Will clear selection.
|
||||||
|
// @p_addNewLine: whether to add a new line \n to the selection.
|
||||||
|
void copySelectedText(bool p_addNewLine);
|
||||||
|
|
||||||
|
void copySelectedText(QTextCursor &p_cursor, bool p_addNewLine);
|
||||||
|
|
||||||
|
// Convert the case of selected text if there is any.
|
||||||
|
// Will clear selection.
|
||||||
|
// @p_toLower: to lower or upper.
|
||||||
|
void convertCaseOfSelectedText(QTextCursor &p_cursor, bool p_toLower);
|
||||||
|
|
||||||
|
// Save @p_text to the Register pointed by m_regName.
|
||||||
|
// Remove QChar::ObjectReplacementCharacter before saving.
|
||||||
void saveToRegister(const QString &p_text);
|
void saveToRegister(const QString &p_text);
|
||||||
|
|
||||||
// Move @p_cursor according to @p_moveMode and @p_movement.
|
// Move @p_cursor according to @p_moveMode and @p_movement.
|
||||||
// Return true if it has moved @p_cursor.
|
// Return true if it has moved @p_cursor.
|
||||||
bool processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
|
bool processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
|
||||||
QTextCursor::MoveMode &p_moveMode,
|
QTextCursor::MoveMode p_moveMode,
|
||||||
Movement p_movement, int p_repeat);
|
Movement p_movement, int p_repeat);
|
||||||
|
|
||||||
|
// Move @p_cursor according to @p_moveMode and @p_range.
|
||||||
|
// Return true if it has moved @p_cursor.
|
||||||
|
bool selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
|
||||||
|
Range p_range, int p_repeat);
|
||||||
|
|
||||||
|
// Check if there is an Action token with Delete/Copy/Change action.
|
||||||
|
bool hasActionTokenValidForTextObject() const;
|
||||||
|
|
||||||
|
// Check if m_keys only contains @p_key.
|
||||||
|
bool checkPendingKey(const Key &p_key) const;
|
||||||
|
|
||||||
|
// Check if m_tokens only contains action token @p_action.
|
||||||
|
bool checkActionToken(Action p_action) const;
|
||||||
|
|
||||||
VEdit *m_editor;
|
VEdit *m_editor;
|
||||||
const VEditConfig *m_editConfig;
|
const VEditConfig *m_editConfig;
|
||||||
VimMode m_mode;
|
VimMode m_mode;
|
||||||
@ -351,6 +428,7 @@ private:
|
|||||||
// A valid command token should follow the rule:
|
// A valid command token should follow the rule:
|
||||||
// Action, Repeat, Movement.
|
// Action, Repeat, Movement.
|
||||||
// Action, Repeat, Range.
|
// Action, Repeat, Range.
|
||||||
|
// Action, Repeat.
|
||||||
QList<Key> m_keys;
|
QList<Key> m_keys;
|
||||||
QList<Token> m_tokens;
|
QList<Token> m_tokens;
|
||||||
|
|
||||||
@ -360,7 +438,7 @@ private:
|
|||||||
QHash<QChar, Register> m_registers;
|
QHash<QChar, Register> m_registers;
|
||||||
|
|
||||||
// Currently used register.
|
// Currently used register.
|
||||||
QChar m_register;
|
QChar m_regName;
|
||||||
|
|
||||||
static const QChar c_unnamedRegister;
|
static const QChar c_unnamedRegister;
|
||||||
static const QChar c_blackHoleRegister;
|
static const QChar c_blackHoleRegister;
|
||||||
|
@ -363,23 +363,9 @@ bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
|
|||||||
m_autoIndentPos = -1;
|
m_autoIndentPos = -1;
|
||||||
cursor.beginEditBlock();
|
cursor.beginEditBlock();
|
||||||
// Indent each selected line.
|
// Indent each selected line.
|
||||||
QTextBlock block = doc->findBlock(cursor.selectionStart());
|
VEditUtils::indentSelectedBlocks(doc, cursor, text, true);
|
||||||
QTextBlock endBlock = doc->findBlock(cursor.selectionEnd());
|
|
||||||
int endBlockNum = endBlock.blockNumber();
|
|
||||||
while (true) {
|
|
||||||
Q_ASSERT(block.isValid());
|
|
||||||
if (!block.text().isEmpty()) {
|
|
||||||
QTextCursor blockCursor(block);
|
|
||||||
blockCursor.insertText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.blockNumber() == endBlockNum) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
block = block.next();
|
|
||||||
}
|
|
||||||
cursor.endEditBlock();
|
cursor.endEditBlock();
|
||||||
|
m_editor->setTextCursor(cursor);
|
||||||
} else {
|
} else {
|
||||||
// If it is a Tab key following auto list, increase the indent level.
|
// If it is a Tab key following auto list, increase the indent level.
|
||||||
QTextBlock block = cursor.block();
|
QTextBlock block = cursor.block();
|
||||||
@ -417,49 +403,19 @@ bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
|
|||||||
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());
|
||||||
QTextBlock endBlock = doc->findBlock(cursor.selectionEnd());
|
|
||||||
|
|
||||||
bool continueAutoIndent = false;
|
bool continueAutoIndent = false;
|
||||||
int seq = -1;
|
int seq = -1;
|
||||||
if (cursor.position() == m_autoIndentPos && isListBlock(block, &seq) &&
|
if (cursor.position() == m_autoIndentPos && isListBlock(block, &seq) &&
|
||||||
!cursor.hasSelection()) {
|
!cursor.hasSelection()) {
|
||||||
continueAutoIndent = true;
|
continueAutoIndent = true;
|
||||||
}
|
}
|
||||||
int endBlockNum = endBlock.blockNumber();
|
|
||||||
cursor.beginEditBlock();
|
cursor.beginEditBlock();
|
||||||
if (continueAutoIndent && seq != -1) {
|
if (continueAutoIndent && seq != -1) {
|
||||||
changeListBlockSeqNumber(block, 1);
|
changeListBlockSeqNumber(block, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; block.isValid() && block.blockNumber() <= endBlockNum;
|
VEditUtils::indentSelectedBlocks(doc, cursor, m_editConfig->m_tabSpaces, false);
|
||||||
block = block.next()) {
|
|
||||||
QTextCursor blockCursor(block);
|
|
||||||
QString text = block.text();
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
} else if (text[0] == '\t') {
|
|
||||||
blockCursor.deleteChar();
|
|
||||||
continue;
|
|
||||||
} else if (text[0] != ' ') {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// Spaces.
|
|
||||||
if (m_editConfig->m_expandTab) {
|
|
||||||
int width = m_editConfig->m_tabSpaces.size();
|
|
||||||
for (int i = 0; i < width; ++i) {
|
|
||||||
if (text[i] == ' ') {
|
|
||||||
blockCursor.deleteChar();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
blockCursor.deleteChar();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor.endEditBlock();
|
cursor.endEditBlock();
|
||||||
|
|
||||||
if (continueAutoIndent) {
|
if (continueAutoIndent) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user