fakevim: Improved word movement

Change-Id: I26f2dfd8780af3cbcc47646c2ddff757c919c88a
Reviewed-by: hjk <qthjk@ovi.com>
This commit is contained in:
Lukas Holecek
2012-08-19 19:08:04 +02:00
committed by hjk
parent e669f05406
commit 2481e58da8

View File

@@ -127,6 +127,8 @@ namespace Internal {
#define EndOfDocument QTextCursor::End #define EndOfDocument QTextCursor::End
#define StartOfDocument QTextCursor::Start #define StartOfDocument QTextCursor::Start
#define ParagraphSeparator QChar::ParagraphSeparator
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s) #define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
enum { enum {
@@ -140,7 +142,6 @@ enum {
#define MetaModifier // Use RealControlModifier instead #define MetaModifier // Use RealControlModifier instead
#define ControlModifier // Use RealControlModifier instead #define ControlModifier // Use RealControlModifier instead
const int ParagraphSeparator = 0x00002029;
typedef QLatin1String _; typedef QLatin1String _;
/* Clipboard MIME types used by Vim. */ /* Clipboard MIME types used by Vim. */
@@ -884,8 +885,19 @@ public:
QTextBlock block() const { return cursor().block(); } QTextBlock block() const { return cursor().block(); }
int leftDist() const { return position() - block().position(); } int leftDist() const { return position() - block().position(); }
int rightDist() const { return block().length() - leftDist() - 1; } int rightDist() const { return block().length() - leftDist() - 1; }
bool atBlockStart() const { return cursor().atBlockStart(); }
bool atBlockEnd() const { return cursor().atBlockEnd(); } bool atBlockEnd() const { return cursor().atBlockEnd(); }
bool atEndOfLine() const { return atBlockEnd() && block().length() > 1; } bool atEndOfLine() const { return atBlockEnd() && block().length() > 1; }
bool atDocumentEnd() const { return cursor().atEnd(); }
bool atDocumentStart() const { return cursor().atStart(); }
bool atEmptyLine(const QTextCursor &tc = QTextCursor()) const;
bool atBoundary(bool end, bool simple, bool onlyWords = false,
const QTextCursor &tc = QTextCursor()) const;
bool atWordBoundary(bool end, bool simple, const QTextCursor &tc = QTextCursor()) const;
bool atWordStart(bool simple, const QTextCursor &tc = QTextCursor()) const;
bool atWordEnd(bool simple, const QTextCursor &tc = QTextCursor()) const;
bool isFirstNonBlankOnLine(int pos);
int lastPositionInDocument() const; // Returns last valid position in doc. int lastPositionInDocument() const; // Returns last valid position in doc.
int firstPositionInLine(int line) const; // 1 based line, 0 based pos int firstPositionInLine(int line) const; // 1 based line, 0 based pos
@@ -932,9 +944,18 @@ public:
m_visualTargetColumn = m_targetColumn; m_visualTargetColumn = m_targetColumn;
//qDebug() << "TARGET: " << m_targetColumn; //qDebug() << "TARGET: " << m_targetColumn;
} }
void moveToNextWord(bool simple, bool deleteWord = false);
void moveToMatchingParanthesis(); void moveToMatchingParanthesis();
void moveToWordBoundary(bool simple, bool forward, bool changeWord = false); void moveToBoundary(bool simple, bool forward = true);
void moveToNextBoundary(bool end, int count, bool simple, bool forward);
void moveToNextBoundaryStart(int count, bool simple, bool forward = true);
void moveToNextBoundaryEnd(int count, bool simple, bool forward = true);
void moveToBoundaryStart(int count, bool simple, bool forward = true);
void moveToBoundaryEnd(int count, bool simple, bool forward = true);
void moveToNextWord(bool end, int count, bool simple, bool forward, bool emptyLines);
void moveToNextWordStart(int count, bool simple, bool forward = true, bool emptyLines = true);
void moveToNextWordEnd(int count, bool simple, bool forward = true, bool emptyLines = true);
void moveToWordStart(int count, bool simple, bool forward = true, bool emptyLines = true);
void moveToWordEnd(int count, bool simple, bool forward = true, bool emptyLines = true);
// Convenience wrappers to reduce line noise. // Convenience wrappers to reduce line noise.
void moveToStartOfLine(); void moveToStartOfLine();
@@ -1027,6 +1048,7 @@ public:
bool isVisualBlockMode() const { return m_visualMode == VisualBlockMode; } bool isVisualBlockMode() const { return m_visualMode == VisualBlockMode; }
void updateEditor(); void updateEditor();
void selectTextObject(bool simple, bool inner);
void selectWordTextObject(bool inner); void selectWordTextObject(bool inner);
void selectWORDTextObject(bool inner); void selectWORDTextObject(bool inner);
void selectSentenceTextObject(bool inner); void selectSentenceTextObject(bool inner);
@@ -1626,6 +1648,52 @@ void FakeVimHandler::Private::stopIncrementalFind()
} }
} }
bool FakeVimHandler::Private::atEmptyLine(const QTextCursor &tc) const
{
if (tc.isNull())
return atEmptyLine(cursor());
return tc.block().length() == 1;
}
bool FakeVimHandler::Private::atBoundary(bool end, bool simple, bool onlyWords,
const QTextCursor &tc) const
{
if (tc.isNull())
return atBoundary(end, simple, onlyWords, cursor());
if (atEmptyLine(tc))
return true;
int pos = tc.position();
QChar c1 = document()->characterAt(pos);
QChar c2 = document()->characterAt(pos + (end ? 1 : -1));
int thisClass = charClass(c1, simple);
return (!onlyWords || thisClass != 0)
&& (c2.isNull() || c2 == ParagraphSeparator || thisClass != charClass(c2, simple));
}
bool FakeVimHandler::Private::atWordBoundary(bool end, bool simple, const QTextCursor &tc) const
{
return atBoundary(end, simple, true, tc);
}
bool FakeVimHandler::Private::atWordStart(bool simple, const QTextCursor &tc) const
{
return atWordBoundary(false, simple, tc);
}
bool FakeVimHandler::Private::atWordEnd(bool simple, const QTextCursor &tc) const
{
return atWordBoundary(true, simple, tc);
}
bool FakeVimHandler::Private::isFirstNonBlankOnLine(int pos)
{
for (int i = document()->findBlock(pos).position(); i < pos; ++i) {
if (!document()->characterAt(i).isSpace())
return false;
}
return true;
}
void FakeVimHandler::Private::setUndoPosition() void FakeVimHandler::Private::setUndoPosition()
{ {
int pos = qMin(position(), anchor()); int pos = qMin(position(), anchor());
@@ -1707,6 +1775,22 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
|| m_submode == YankSubMode || m_submode == YankSubMode
|| m_submode == TransformSubMode) { || m_submode == TransformSubMode) {
if (m_movetype == MoveExclusive) {
if (anchor() != position() && atBlockStart()) {
// Exlusive motion ending at the beginning of line
// becomes inclusive and end is moved to end of previous line.
m_movetype = MoveInclusive;
moveToStartOfLine();
moveLeft();
// Exclusive motion ending at the beginning of line and
// starting at or before first non-blank on a line becomes linewise.
if (anchor() < block().position() && isFirstNonBlankOnLine(anchor())) {
m_movetype = MoveLineWise;
}
}
}
if (m_submode != YankSubMode) if (m_submode != YankSubMode)
beginEditBlock(); beginEditBlock();
@@ -1717,8 +1801,18 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
if (m_movetype == MoveInclusive) { if (m_movetype == MoveInclusive) {
if (anchor() <= position()) { if (anchor() <= position()) {
if (!cursor().atBlockEnd()) if (!atBlockEnd())
setPosition(position() + 1); // correction setPosition(position() + 1); // correction
// If more than one line is selected and all are selected completely
// movement becomes linewise.
int start = anchor();
if (start < block().position() && isFirstNonBlankOnLine(start) && atBlockEnd()) {
moveRight();
if (atEmptyLine())
moveRight();
m_movetype = MoveLineWise;
}
} else { } else {
setAnchorAndPosition(anchor() + 1, position()); setAnchorAndPosition(anchor() + 1, position());
} }
@@ -1756,7 +1850,8 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
enterInsertMode(); enterInsertMode();
m_submode = NoSubMode; m_submode = NoSubMode;
} else if (m_submode == DeleteSubMode) { } else if (m_submode == DeleteSubMode) {
removeText(currentRange()); Range range = currentRange();
removeText(range);
if (!dotCommand.isEmpty()) if (!dotCommand.isEmpty())
setDotCommand(QLatin1Char('d') + dotCommand); setDotCommand(QLatin1Char('d') + dotCommand);
if (m_movetype == MoveLineWise) if (m_movetype == MoveLineWise)
@@ -2400,12 +2495,12 @@ EventResult FakeVimHandler::Private::handleCommandMode1(const Input &input)
changeNumberTextObject(true); changeNumberTextObject(true);
} else if (input.is('b') || input.isShift(Key_Left)) { } else if (input.is('b') || input.isShift(Key_Left)) {
m_movetype = MoveExclusive; m_movetype = MoveExclusive;
moveToWordBoundary(false, false); moveToNextWordStart(count(), false, false);
setTargetColumn(); setTargetColumn();
finishMovement(); finishMovement();
} else if (input.is('B')) { } else if (input.is('B')) {
m_movetype = MoveExclusive; m_movetype = MoveExclusive;
moveToWordBoundary(true, false); moveToNextWordStart(count(), true, false);
setTargetColumn(); setTargetColumn();
finishMovement(); finishMovement();
} else if (input.is('c') && isNoVisualMode()) { } else if (input.is('c') && isNoVisualMode()) {
@@ -2500,14 +2595,24 @@ EventResult FakeVimHandler::Private::handleCommandMode1(const Input &input)
handleStartOfLine(); handleStartOfLine();
scrollToLine(cursorLine() - sline); scrollToLine(cursorLine() - sline);
finishMovement(); finishMovement();
} else if (input.is('e') && m_gflag) {
m_movetype = MoveInclusive;
moveToNextWordEnd(count(), false, false);
setTargetColumn();
finishMovement("%1ge", count());
} else if (input.is('e') || input.isShift(Key_Right)) { } else if (input.is('e') || input.isShift(Key_Right)) {
m_movetype = MoveInclusive; m_movetype = MoveInclusive;
moveToWordBoundary(false, true); moveToNextWordEnd(count(), false, true, false);
setTargetColumn(); setTargetColumn();
finishMovement("%1e", count()); finishMovement("%1e", count());
} else if (input.is('E') && m_gflag) {
m_movetype = MoveInclusive;
moveToNextWordEnd(count(), true, false);
setTargetColumn();
finishMovement("%1gE", count());
} else if (input.is('E')) { } else if (input.is('E')) {
m_movetype = MoveInclusive; m_movetype = MoveInclusive;
moveToWordBoundary(true, true); moveToNextWordEnd(count(), true, true, false);
setTargetColumn(); setTargetColumn();
finishMovement("%1E", count()); finishMovement("%1E", count());
} else if (input.isControl('e')) { } else if (input.isControl('e')) {
@@ -2815,23 +2920,23 @@ EventResult FakeVimHandler::Private::handleCommandMode2(const Input &input)
// cursor is on a non-blank - except if the cursor is on the last // cursor is on a non-blank - except if the cursor is on the last
// character of a word: only the current word will be changed // character of a word: only the current word will be changed
if (m_submode == ChangeSubMode) { if (m_submode == ChangeSubMode) {
moveToWordBoundary(false, true, true); moveToWordEnd(count(), false, true);
setTargetColumn();
m_movetype = MoveInclusive; m_movetype = MoveInclusive;
} else { } else {
moveToNextWord(false, m_submode == DeleteSubMode); moveToNextWordStart(count(), false, true);
m_movetype = MoveExclusive; m_movetype = MoveExclusive;
} }
setTargetColumn();
finishMovement("%1w", count()); finishMovement("%1w", count());
} else if (input.is('W')) { } else if (input.is('W')) {
if (m_submode == ChangeSubMode) { if (m_submode == ChangeSubMode) {
moveToWordBoundary(true, true, true); moveToWordEnd(count(), true, true);
setTargetColumn();
m_movetype = MoveInclusive; m_movetype = MoveInclusive;
} else { } else {
moveToNextWord(true, m_submode == DeleteSubMode); moveToNextWordStart(count(), true, true);
m_movetype = MoveExclusive; m_movetype = MoveExclusive;
} }
setTargetColumn();
finishMovement("%1W", count()); finishMovement("%1W", count());
} else if (input.isControl('w')) { } else if (input.isControl('w')) {
m_submode = WindowSubMode; m_submode = WindowSubMode;
@@ -3077,7 +3182,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
m_ctrlVActive = true; m_ctrlVActive = true;
} else if (input.isControl('w')) { } else if (input.isControl('w')) {
int endPos = position(); int endPos = position();
moveToWordBoundary(false, false, false); moveToNextWordStart(count(), false, false);
setTargetColumn(); setTargetColumn();
int beginPos = position(); int beginPos = position();
Range range(beginPos, endPos, RangeCharMode); Range range(beginPos, endPos, RangeCharMode);
@@ -3092,7 +3197,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
setTargetColumn(); setTargetColumn();
m_lastInsertion.clear(); m_lastInsertion.clear();
} else if (input.isControl(Key_Left)) { } else if (input.isControl(Key_Left)) {
moveToWordBoundary(false, false); moveToNextWordStart(count(), false, false);
setTargetColumn(); setTargetColumn();
m_lastInsertion.clear(); m_lastInsertion.clear();
} else if (input.isKey(Key_Down)) { } else if (input.isKey(Key_Down)) {
@@ -3110,7 +3215,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
setTargetColumn(); setTargetColumn();
m_lastInsertion.clear(); m_lastInsertion.clear();
} else if (input.isControl(Key_Right)) { } else if (input.isControl(Key_Right)) {
moveToWordBoundary(false, true); moveToNextWordStart(count(), false, true);
moveRight(); // we need one more move since we are in insert mode moveRight(); // we need one more move since we are in insert mode
setTargetColumn(); setTargetColumn();
m_lastInsertion.clear(); m_lastInsertion.clear();
@@ -3205,7 +3310,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
// // ignore these // // ignore these
} else if (input.isControl('p') || input.isControl('n')) { } else if (input.isControl('p') || input.isControl('n')) {
QTextCursor tc = EDITOR(textCursor()); QTextCursor tc = EDITOR(textCursor());
moveToWordBoundary(false, false); moveToNextWordStart(count(), false, false);
QString str = selectText(Range(position(), tc.position())); QString str = selectText(Range(position(), tc.position()));
EDITOR(setTextCursor(tc)); EDITOR(setTextCursor(tc));
emit q->simpleCompletionRequested(str, input.isControl('n')); emit q->simpleCompletionRequested(str, input.isControl('n'));
@@ -4186,7 +4291,7 @@ void FakeVimHandler::Private::highlightMatches(const QString &needle)
sel.format = tc.blockCharFormat(); sel.format = tc.blockCharFormat();
sel.format.setBackground(QColor(177, 177, 0)); sel.format.setBackground(QColor(177, 177, 0));
m_searchSelections.append(sel); m_searchSelections.append(sel);
if (document()->characterAt(tc.position()) == ParagraphSeparator) if (atEmptyLine(tc))
tc.movePosition(Right, MoveAnchor); tc.movePosition(Right, MoveAnchor);
} }
} }
@@ -4384,34 +4489,91 @@ void FakeVimHandler::Private::setupCharClass()
} }
} }
void FakeVimHandler::Private::moveToWordBoundary(bool simple, bool forward, bool changeWord) void FakeVimHandler::Private::moveToBoundary(bool simple, bool forward)
{ {
int repeat = count();
QTextDocument *doc = document(); QTextDocument *doc = document();
int n = forward ? lastPositionInDocument() : 0; QTextCursor tc(doc);
int lastClass = -1; tc.setPosition(position());
if (changeWord) { if (forward ? tc.atBlockEnd() : tc.atBlockStart())
lastClass = charClass(characterAtCursor(), simple); return;
--repeat;
if (changeWord && block().length() == 1) // empty line QChar c = document()->characterAt(tc.position() + (forward ? -1 : 1));
--repeat; int lastClass = tc.atStart() ? -1 : charClass(c, simple);
} QTextCursor::MoveOperation op = forward ? Right : Left;
while (repeat >= 0) { while (true) {
QChar c = doc->characterAt(position() + (forward ? 1 : -1)); c = doc->characterAt(tc.position());
int thisClass = charClass(c, simple); int thisClass = charClass(c, simple);
if (thisClass != lastClass && (lastClass != 0 || changeWord)) if (thisClass != lastClass || (forward ? tc.atBlockEnd() : tc.atBlockStart())) {
--repeat; if (tc != cursor())
if (repeat == -1) tc.movePosition(forward ? Left : Right);
break;
lastClass = thisClass;
if (position() == n)
break;
forward ? moveRight() : moveLeft();
if (changeWord && block().length() == 1) // empty line
--repeat;
if (repeat == -1)
break; break;
} }
lastClass = thisClass;
tc.movePosition(op);
}
setPosition(tc.position());
}
void FakeVimHandler::Private::moveToNextBoundary(bool end, int count, bool simple, bool forward)
{
int repeat = count;
while (repeat > 0 && !(forward ? atDocumentEnd() : atDocumentStart())) {
setPosition(position() + (forward ? 1 : -1));
moveToBoundary(simple, forward);
if (atBoundary(end, simple))
--repeat;
}
}
void FakeVimHandler::Private::moveToNextBoundaryStart(int count, bool simple, bool forward)
{
moveToNextBoundary(false, count, simple, forward);
}
void FakeVimHandler::Private::moveToNextBoundaryEnd(int count, bool simple, bool forward)
{
moveToNextBoundary(true, count, simple, forward);
}
void FakeVimHandler::Private::moveToBoundaryStart(int count, bool simple, bool forward)
{
moveToNextBoundaryStart(atBoundary(false, simple) ? count - 1 : count, simple, forward);
}
void FakeVimHandler::Private::moveToBoundaryEnd(int count, bool simple, bool forward)
{
moveToNextBoundaryEnd(atBoundary(true, simple) ? count - 1 : count, simple, forward);
}
void FakeVimHandler::Private::moveToNextWord(bool end, int count, bool simple, bool forward, bool emptyLines)
{
int repeat = count;
while (repeat > 0 && !(forward ? atDocumentEnd() : atDocumentStart())) {
setPosition(position() + (forward ? 1 : -1));
moveToBoundary(simple, forward);
if (atWordBoundary(end, simple) && (emptyLines || !atEmptyLine()) )
--repeat;
}
}
void FakeVimHandler::Private::moveToNextWordStart(int count, bool simple, bool forward, bool emptyLines)
{
moveToNextWord(false, count, simple, forward, emptyLines);
}
void FakeVimHandler::Private::moveToNextWordEnd(int count, bool simple, bool forward, bool emptyLines)
{
moveToNextWord(true, count, simple, forward, emptyLines);
}
void FakeVimHandler::Private::moveToWordStart(int count, bool simple, bool forward, bool emptyLines)
{
moveToNextWordStart(atWordStart(simple) ? count - 1 : count, simple, forward, emptyLines);
}
void FakeVimHandler::Private::moveToWordEnd(int count, bool simple, bool forward, bool emptyLines)
{
moveToNextWordEnd(atWordEnd(simple) ? count - 1 : count, simple, forward, emptyLines);
} }
bool FakeVimHandler::Private::handleFfTt(QString key) bool FakeVimHandler::Private::handleFfTt(QString key)
@@ -4456,35 +4618,6 @@ bool FakeVimHandler::Private::handleFfTt(QString key)
return false; return false;
} }
void FakeVimHandler::Private::moveToNextWord(bool simple, bool deleteWord)
{
int repeat = count();
int n = lastPositionInDocument();
int lastClass = charClass(characterAtCursor(), simple);
while (true) {
QChar c = characterAtCursor();
int thisClass = charClass(c, simple);
if (thisClass != lastClass && thisClass != 0)
--repeat;
if (repeat == 0)
break;
lastClass = thisClass;
moveRight();
if (deleteWord) {
if (atBlockEnd())
--repeat;
} else {
if (block().length() == 1) // empty line
--repeat;
}
if (repeat == 0)
break;
if (position() == n)
break;
}
setTargetColumn();
}
void FakeVimHandler::Private::moveToMatchingParanthesis() void FakeVimHandler::Private::moveToMatchingParanthesis()
{ {
bool moved = false; bool moved = false;
@@ -5254,32 +5387,77 @@ void FakeVimHandler::Private::replay(const QString &command, int n)
} }
} }
void FakeVimHandler::Private::selectTextObject(bool simple, bool inner)
{
bool setupAnchor = (position() == anchor());
// set anchor if not already set
if (setupAnchor) {
moveToBoundaryStart(1, simple, false);
setAnchor();
} else {
moveRight();
if (atEndOfLine())
moveRight();
}
const int repeat = count();
if (inner) {
moveToBoundaryEnd(repeat, simple);
} else {
for (int i = 0; i < repeat; ++i) {
// select leading spaces
bool leadingSpace = characterAtCursor().isSpace();
if (leadingSpace)
moveToNextBoundaryStart(1, simple);
// select word
moveToWordEnd(1, simple);
// select trailing spaces if no leading space
if (!leadingSpace && document()->characterAt(position() + 1).isSpace()
&& !atBlockStart()) {
moveToNextBoundaryEnd(1, simple);
}
// if there are no trailing spaces in selection select all leading spaces
// after previous character
if (setupAnchor && (!characterAtCursor().isSpace() || atBlockEnd())) {
int min = block().position();
int pos = anchor();
while (pos >= min && document()->characterAt(--pos).isSpace()) {}
if (pos >= min)
setAnchorAndPosition(pos + 1, position());
}
if (i + 1 < repeat) {
moveRight();
if (atEndOfLine())
moveRight();
}
}
}
if (inner) {
m_movetype = MoveInclusive;
} else {
m_movetype = MoveExclusive;
moveRight();
if (atEndOfLine())
moveRight();
}
setTargetColumn();
}
void FakeVimHandler::Private::selectWordTextObject(bool inner) void FakeVimHandler::Private::selectWordTextObject(bool inner)
{ {
Q_UNUSED(inner); // FIXME selectTextObject(false, inner);
m_movetype = MoveExclusive;
moveToWordBoundary(false, false, true);
setAnchor();
// FIXME: Rework the 'anchor' concept.
//if (isVisualMode())
// setMark('<', cursor().position());
moveToWordBoundary(false, true, true);
setTargetColumn();
m_movetype = MoveInclusive;
} }
void FakeVimHandler::Private::selectWORDTextObject(bool inner) void FakeVimHandler::Private::selectWORDTextObject(bool inner)
{ {
Q_UNUSED(inner); // FIXME selectTextObject(true, inner);
m_movetype = MoveExclusive;
moveToWordBoundary(true, false, true);
setAnchor();
// FIXME: Rework the 'anchor' concept.
//if (isVisualMode())
// setMark('<', cursor().position());
moveToWordBoundary(true, true, true);
setTargetColumn();
m_movetype = MoveInclusive;
} }
void FakeVimHandler::Private::selectSentenceTextObject(bool inner) void FakeVimHandler::Private::selectSentenceTextObject(bool inner)