vim-mode: support Copy/Paste/Change/Indent/UnIndent/ToLower/ToUpper action

This commit is contained in:
Le Tan 2017-06-16 22:35:39 +08:00
parent f6a91d04a8
commit 5047e19b24
5 changed files with 1785 additions and 140 deletions

View File

@ -19,7 +19,7 @@ void VEditUtils::removeBlock(QTextCursor &p_cursor, QString *p_text)
p_cursor.select(QTextCursor::BlockUnderCursor);
if (p_text) {
*p_text = p_cursor.selectedText() + "\n";
*p_text = selectedText(p_cursor) + "\n";
}
p_cursor.deleteChar();
@ -133,3 +133,80 @@ void VEditUtils::moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor,
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;
}
}
}
}

View File

@ -4,6 +4,8 @@
#include <QTextBlock>
#include <QTextCursor>
class QTextDocument;
// Utils for text edit.
class VEditUtils
{
@ -36,6 +38,31 @@ public:
// Need to call setTextCursor() to make it take effect.
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:
VEditUtils() {}
};

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@
#include <QString>
#include <QTextCursor>
#include <QHash>
#include <QDebug>
#include "vutils.h"
class VEdit;
@ -101,6 +102,7 @@ private:
Delete,
Copy,
Paste,
PasteBefore,
Change,
Indent,
UnIndent,
@ -143,7 +145,22 @@ private:
enum class Range
{
Line = 0,
Word,
WordInner,
WordAround,
WORDInner,
WORDAround,
QuoteInner,
QuoteAround,
DoubleQuoteInner,
DoubleQuoteAround,
ParenthesisInner,
ParenthesisAround,
BracketInner,
BracketAround,
AngleBracketInner,
AngleBracketAround,
BraceInner,
BraceAround,
Invalid
};
@ -257,6 +274,26 @@ private:
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;
QString m_value;
@ -287,6 +324,21 @@ private:
// @p_tokens is the arguments of the Action::Delete action.
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.
// Returns true if there is selection.
bool clearSelection();
@ -331,19 +383,44 @@ private:
// Delete selected text if there is any.
// @p_clearEmptyBlock: whether to remove the empty block after deletion.
void deleteSelectedText(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);
// Move @p_cursor according to @p_moveMode and @p_movement.
// Return true if it has moved @p_cursor.
bool processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
QTextCursor::MoveMode &p_moveMode,
QTextCursor::MoveMode p_moveMode,
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;
const VEditConfig *m_editConfig;
VimMode m_mode;
@ -351,6 +428,7 @@ private:
// A valid command token should follow the rule:
// Action, Repeat, Movement.
// Action, Repeat, Range.
// Action, Repeat.
QList<Key> m_keys;
QList<Token> m_tokens;
@ -360,7 +438,7 @@ private:
QHash<QChar, Register> m_registers;
// Currently used register.
QChar m_register;
QChar m_regName;
static const QChar c_unnamedRegister;
static const QChar c_blackHoleRegister;

View File

@ -363,23 +363,9 @@ bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
m_autoIndentPos = -1;
cursor.beginEditBlock();
// Indent each selected line.
QTextBlock block = doc->findBlock(cursor.selectionStart());
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();
}
VEditUtils::indentSelectedBlocks(doc, cursor, text, true);
cursor.endEditBlock();
m_editor->setTextCursor(cursor);
} else {
// If it is a Tab key following auto list, increase the indent level.
QTextBlock block = cursor.block();
@ -417,49 +403,19 @@ bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
QTextDocument *doc = m_editor->document();
QTextCursor cursor = m_editor->textCursor();
QTextBlock block = doc->findBlock(cursor.selectionStart());
QTextBlock endBlock = doc->findBlock(cursor.selectionEnd());
bool continueAutoIndent = false;
int seq = -1;
if (cursor.position() == m_autoIndentPos && isListBlock(block, &seq) &&
!cursor.hasSelection()) {
continueAutoIndent = true;
}
int endBlockNum = endBlock.blockNumber();
cursor.beginEditBlock();
if (continueAutoIndent && seq != -1) {
changeListBlockSeqNumber(block, 1);
}
for (; block.isValid() && block.blockNumber() <= endBlockNum;
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;
}
}
}
VEditUtils::indentSelectedBlocks(doc, cursor, m_editConfig->m_tabSpaces, false);
cursor.endEditBlock();
if (continueAutoIndent) {