fakevim: Fix last position and selection (marks)

Change-Id: If75164be930dac3c4f3b9c26179ee54aa643ab4e
Reviewed-by: hjk <qthjk@ovi.com>
This commit is contained in:
Lukas Holecek
2012-10-23 16:35:13 +02:00
committed by hjk
parent e35d71085c
commit 49a337f235
3 changed files with 205 additions and 129 deletions

View File

@@ -235,15 +235,35 @@ enum EventResult
EventPassedToCore
};
typedef QHash<int, QTextCursor> Marks;
struct CursorPosition
{
CursorPosition() : line(-1), column(-1) {}
CursorPosition(int block, int column) : line(block), column(column) {}
explicit CursorPosition(const QTextCursor &tc)
: line(tc.block().blockNumber()), column(tc.positionInBlock()) {}
CursorPosition(const QTextDocument *document, int position)
{
QTextBlock block = document->findBlock(position);
line = block.blockNumber();
column = position - block.position();
}
bool isValid() const { return line >= 0 && column >= 0; }
bool operator==(const CursorPosition &other) const
{ return line == other.line && column == other.column; }
bool operator!=(const CursorPosition &other) const { return !operator==(other); }
int line; // Line in document (from 0, folded lines included).
int column; // Position on line.
};
typedef QHash<int, CursorPosition> Marks;
struct State
{
State() : revision(-1), position(-1), line(-1), marks() {}
State(int revision, int position, int line, const Marks &marks)
: revision(revision), position(position), line(line), marks(marks) {}
State() : revision(-1), position(), marks() {}
State(int revision, const CursorPosition &position, const Marks &marks)
: revision(revision), position(position), marks(marks) {}
int revision;
int position;
int line;
CursorPosition position;
Marks marks;
};
@@ -259,15 +279,6 @@ QDebug operator<<(QDebug ts, const Column &col)
return ts << "(p: " << col.physical << ", l: " << col.logical << ")";
}
struct CursorPosition
{
// for jump history
CursorPosition() : position(-1), scrollLine(-1) {}
CursorPosition(int pos, int line) : position(pos), scrollLine(line) {}
int position; // Position in document
int scrollLine; // First visible line
};
struct Register
{
Register() : rangemode(RangeCharMode) {}
@@ -1273,6 +1284,7 @@ public:
// The following use all zero-based counting.
int cursorLineOnScreen() const;
int cursorLine() const;
int cursorBlockNumber() const;
int physicalCursorColumn() const; // as stored in the data
int logicalCursorColumn() const; // as visible on screen
int physicalToLogicalColumn(int physical, const QString &text) const;
@@ -1282,11 +1294,11 @@ public:
void scrollToLine(int line);
void scrollUp(int count);
void scrollDown(int count) { scrollUp(-count); }
void alignViewportToCursor(Qt::AlignmentFlag align, int line = -1,
bool moveToNonBlank = false);
CursorPosition cursorPosition() const
{ return CursorPosition(position(), firstVisibleLine()); }
void setCursorPosition(const CursorPosition &p)
{ setPosition(p.position); scrollToLine(p.scrollLine); }
void setCursorPosition(const CursorPosition &p);
void setCursorPosition(QTextCursor *tc, const CursorPosition &p);
// Helper functions for indenting/
bool isElectricCharacter(QChar c) const;
@@ -1522,9 +1534,9 @@ public:
VisualMode m_oldVisualMode;
// marks as lines
int mark(int code) const;
void setMark(int code, int position);
typedef QHashIterator<int, QTextCursor> MarksIterator;
CursorPosition mark(int code) const;
void setMark(int code, CursorPosition position);
typedef QHashIterator<int, CursorPosition> MarksIterator;
Marks m_marks;
// vi style configuration
@@ -1555,7 +1567,7 @@ public:
void jump(int distance);
QStack<CursorPosition> m_jumpListUndo;
QStack<CursorPosition> m_jumpListRedo;
int m_lastChangePosition;
CursorPosition m_lastChangePosition;
QList<QTextEdit::ExtraSelection> m_extraSelections;
QTextCursor m_searchCursor;
@@ -1565,7 +1577,6 @@ public:
QString m_lastSubstituteFlags;
QRegExp m_lastSubstitutePattern;
QString m_lastSubstituteReplacement;
QTextCursor m_lastSelectionCursor;
VisualMode m_lastSelectionMode;
bool handleExCommandHelper(ExCommand &cmd); // Returns success.
@@ -1677,7 +1688,6 @@ void FakeVimHandler::Private::init()
m_oldExternalAnchor = -1;
m_oldExternalPosition = -1;
m_oldPosition = -1;
m_lastChangePosition = -1;
m_breakEditBlock = false;
m_searchStartPosition = 0;
m_searchFromScreenLine = 0;
@@ -2224,8 +2234,8 @@ void FakeVimHandler::Private::setUndoPosition(bool overwrite)
m_redo.clear();
while (!m_undo.empty() && m_undo.top().revision >= rev)
m_undo.pop();
m_undo.push(State(rev, pos, lineForPosition(pos), m_marks));
m_lastChangePosition = pos;
m_lastChangePosition = CursorPosition(document(), pos);
m_undo.push(State(rev, m_lastChangePosition, m_marks));
}
void FakeVimHandler::Private::moveDown(int n)
@@ -2402,7 +2412,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
const int la = lineForPosition(anchor());
const int lp = lineForPosition(position());
if (m_register != '"') {
setPosition(mark(m_register));
setCursorPosition(mark(m_register));
moveToStartOfLine();
} else {
if (anchor() <= position())
@@ -2469,10 +2479,10 @@ void FakeVimHandler::Private::updateSelection()
for (MarksIterator it(m_marks); it.hasNext(); ) {
it.next();
QTextEdit::ExtraSelection sel;
const int pos = it.value().position();
sel.cursor = cursor();
sel.cursor.setPosition(pos, MoveAnchor);
sel.cursor.setPosition(pos + 1, KeepAnchor);
setCursorPosition(&sel.cursor, it.value());
sel.cursor.setPosition(sel.cursor.position(), MoveAnchor);
sel.cursor.movePosition(Right, KeepAnchor);
sel.format = cursor().blockCharFormat();
sel.format.setForeground(Qt::blue);
sel.format.setBackground(Qt::green);
@@ -2636,17 +2646,17 @@ EventResult FakeVimHandler::Private::handleCommandSubSubMode(const Input &input)
resetCommandMode();
}
} else if (m_subsubmode == MarkSubSubMode) {
setMark(input.asChar().unicode(), position());
setMark(input.asChar().unicode(), CursorPosition(cursor()));
m_subsubmode = NoSubSubMode;
} else if (m_subsubmode == BackTickSubSubMode
|| m_subsubmode == TickSubSubMode) {
ushort markChar = input.asChar().unicode();
int m = mark(markChar);
if (m != -1) {
CursorPosition m = mark(markChar);
if (m.isValid()) {
if (markChar == '\'' && !m_jumpListUndo.isEmpty())
m_jumpListUndo.pop();
recordJump();
setPosition(m);
setCursorPosition(m);
if (m_subsubmode == TickSubSubMode)
moveToFirstNonBlankOnLine();
finishMovement();
@@ -2770,29 +2780,20 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
} else if (m_submode == ZSubMode) {
//qDebug() << "Z_MODE " << cursorLine() << linesOnScreen();
bool foldMaybeClosed = false;
if (input.isReturn() || input.is('t')) {
// Cursor line to top of window.
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
scrollUp(- cursorLineOnScreen());
if (input.isReturn())
moveToFirstNonBlankOnLine();
finishMovement();
} else if (input.is('.') || input.is('z')) {
// Cursor line to center of window.
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
if (input.is('.'))
moveToFirstNonBlankOnLine();
finishMovement();
} else if (input.is('-') || input.is('b')) {
// Cursor line to bottom of window.
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
scrollUp(linesOnScreen() - cursorLineOnScreen());
if (input.is('-'))
moveToFirstNonBlankOnLine();
if (input.isReturn() || input.is('t')
|| input.is('-') || input.is('b')
|| input.is('.') || input.is('z')) {
// Cursor line to top/center/bottom of window.
Qt::AlignmentFlag align;
if (input.isReturn() || input.is('t'))
align = Qt::AlignTop;
else if (input.is('.') || input.is('z'))
align = Qt::AlignBottom;
else
align = Qt::AlignVCenter;
const bool moveToNonBlank = (input.is('.') || input.isReturn() || input.is('-'));
const int line = m_mvcount.isEmpty() ? -1 : firstPositionInLine(count());
alignViewportToCursor(align, line, moveToNonBlank);
finishMovement();
} else if (input.is('o') || input.is('c')) {
// Open/close current fold.
@@ -3013,6 +3014,7 @@ EventResult FakeVimHandler::Private::handleCommandMode1(const Input &input)
leaveVisualMode();
}
} else if (input.is('%')) {
recordJump();
if (count() == 1) {
moveToMatchingParanthesis();
} else {
@@ -3191,6 +3193,7 @@ EventResult FakeVimHandler::Private::handleCommandMode1(const Input &input)
m_gflag = true;
} else if (input.is('g') || input.is('G')) {
QString dotCommand = QString("%1G").arg(count());
recordJump();
if (input.is('G') && m_mvcount.isEmpty())
dotCommand = QString(QLatin1Char('G'));
if (input.is('g'))
@@ -3455,9 +3458,13 @@ EventResult FakeVimHandler::Private::handleCommandMode2(const Input &input)
scrollToLine(cursorLine() - sline);
finishMovement();
} else if (m_gflag && input.is('v')) {
if (m_lastSelectionCursor.hasSelection()) {
CursorPosition from = mark('<');
CursorPosition to = mark('>');
if (from.isValid() && to.isValid()) {
toggleVisualMode(m_lastSelectionMode);
setCursor(m_lastSelectionCursor);
setCursorPosition(from);
setAnchor();
setCursorPosition(to);
}
} else if (input.is('v')) {
toggleVisualMode(VisualCharMode);
@@ -3576,6 +3583,7 @@ EventResult FakeVimHandler::Private::handleCommandMode2(const Input &input)
|| (m_gflag && input.is('U') && !isVisualMode())) {
m_gflag = false;
m_movetype = MoveExclusive;
setUndoPosition();
if (atEndOfLine())
moveLeft();
setAnchor();
@@ -4005,7 +4013,7 @@ EventResult FakeVimHandler::Private::handleSearchSubSubMode(const Input &input)
return EventHandled;
}
// This uses 1 based line counting.
// This uses 0 based line counting (hidden lines included).
int FakeVimHandler::Private::readLineCode(QString &cmd)
{
//qDebug() << "CMD: " << cmd;
@@ -4015,48 +4023,38 @@ int FakeVimHandler::Private::readLineCode(QString &cmd)
cmd = cmd.mid(1);
if (c == '.') {
if (cmd.isEmpty())
return cursorLine() + 1;
return cursorBlockNumber();
QChar c1 = cmd.at(0);
if (c1 == '+' || c1 == '-') {
// Repeat for things like .+4
cmd = cmd.mid(1);
return cursorLine() + readLineCode(cmd);
return cursorBlockNumber() + readLineCode(cmd);
}
return cursorLine() + 1;
return cursorBlockNumber();
}
if (c == '$')
return linesInDocument();
return document()->blockCount() - 1;
if (c == '\'' && !cmd.isEmpty()) {
if (cmd.isEmpty()) {
showMessage(MessageError, msgMarkNotSet(QString()));
return -1;
}
int m = mark(cmd.at(0).unicode());
if (m == -1) {
CursorPosition m = mark(cmd.at(0).unicode());
if (!m.isValid()) {
showMessage(MessageError, msgMarkNotSet(cmd.at(0)));
cmd = cmd.mid(1);
return -1;
}
cmd = cmd.mid(1);
return lineForPosition(m);
return m.line;
}
if (c == '-') {
int n = readLineCode(cmd);
return cursorLine() + 1 - (n == -1 ? 1 : n);
return cursorBlockNumber() - (n == -1 ? 1 : n);
}
if (c == '+') {
int n = readLineCode(cmd);
return cursorLine() + 1 + (n == -1 ? 1 : n);
}
if (c == '\'' && !cmd.isEmpty()) {
int pos = mark(cmd.at(0).unicode());
if (pos == -1) {
showMessage(MessageError, msgMarkNotSet(cmd.at(0)));
cmd = cmd.mid(1);
return -1;
}
cmd = cmd.mid(1);
return lineForPosition(pos);
return cursorBlockNumber() + (n == -1 ? 1 : n);
}
if (c.isDigit()) {
int n = c.unicode() - '0';
@@ -4068,7 +4066,7 @@ int FakeVimHandler::Private::readLineCode(QString &cmd)
n = n * 10 + (c.unicode() - '0');
}
//qDebug() << "N: " << n;
return n;
return n - 1;
}
// Parsing failed.
cmd = c + cmd;
@@ -4084,9 +4082,9 @@ void FakeVimHandler::Private::setCurrentRange(const Range &range)
Range FakeVimHandler::Private::rangeFromCurrentLine() const
{
Range range;
int line = cursorLine() + 1;
range.beginPos = firstPositionInLine(line);
range.endPos = lastPositionInLine(line);
QTextBlock block = cursor().block();
range.beginPos = block.position();
range.endPos = range.beginPos + block.length() - 1;
return range;
}
@@ -4173,34 +4171,36 @@ bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
m_lastSubstituteReplacement = replacement;
}
int lastLine = -1;
int firstLine = -1;
QTextBlock lastBlock;
QTextBlock firstBlock;
const bool global = flags.contains('g');
for (int a = 0; a != count; ++a) {
const Range range = cmd.range.endPos <= 0 ? rangeFromCurrentLine() : cmd.range;
const int beginLine = lineForPosition(range.beginPos);
const int endLine = lineForPosition(range.endPos);
for (int line = endLine; line >= beginLine; --line) {
QString text = lineContents(line);
for (QTextBlock block = document()->findBlock(range.endPos);
block.isValid() && block.position() + block.length() > range.beginPos;
block = block.previous()) {
QString text = block.text();
if (substituteText(&text, pattern, replacement, global)) {
firstLine = line;
if (lastLine == -1) {
lastLine = line;
firstBlock = block;
if (!lastBlock.isValid()) {
lastBlock = block;
beginEditBlock();
}
setLineContents(line, text);
QTextCursor tc = cursor();
const int pos = block.position();
const int anchor = pos + block.length() - 1;
tc.setPosition(anchor);
tc.setPosition(pos, KeepAnchor);
tc.insertText(text);
}
}
}
if (lastLine != -1) {
if (lastBlock.isValid()) {
State &state = m_undo.top();
state.line = firstLine;
state.position = firstPositionInLine(firstLine);
state.position = CursorPosition(firstBlock.blockNumber(), 0);
QTextCursor tc = cursor();
tc.setPosition(firstPositionInLine(lastLine));
setCursor(tc);
setPosition(lastBlock.position());
moveToFirstNonBlankOnLine();
setTargetColumn();
@@ -4712,8 +4712,8 @@ bool FakeVimHandler::Private::handleExCommandHelper(ExCommand &cmd)
if (beginLine != -1 && endLine == -1)
endLine = beginLine;
if (beginLine != -1) {
const int beginPos = firstPositionInLine(beginLine);
const int endPos = lastPositionInLine(endLine);
const int beginPos = firstPositionInLine(beginLine + 1, false);
const int endPos = lastPositionInLine(endLine + 1, false);
cmd.range = Range(beginPos, endPos, RangeLineMode);
cmd.count = beginLine;
}
@@ -5234,6 +5234,11 @@ int FakeVimHandler::Private::cursorLine() const
return lineForPosition(position()) - 1;
}
int FakeVimHandler::Private::cursorBlockNumber() const
{
return cursor().block().blockNumber();
}
int FakeVimHandler::Private::physicalCursorColumn() const
{
return position() - block().position();
@@ -5323,6 +5328,44 @@ void FakeVimHandler::Private::scrollUp(int count)
scrollToLine(cursorLine() - cursorLineOnScreen() - count);
}
void FakeVimHandler::Private::alignViewportToCursor(AlignmentFlag align, int line,
bool moveToNonBlank)
{
if (line > 0)
setPosition(firstPositionInLine(line));
if (moveToNonBlank)
moveToFirstNonBlankOnLine();
if (align == Qt::AlignTop)
scrollUp(- cursorLineOnScreen());
else if (align == Qt::AlignVCenter)
scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
else if (align == Qt::AlignBottom)
scrollUp(linesOnScreen() - cursorLineOnScreen());
}
void FakeVimHandler::Private::setCursorPosition(const CursorPosition &p)
{
const int firstLine = firstVisibleLine();
const int firstBlock = document()->findBlockByLineNumber(firstLine).blockNumber();
const int lastBlock =
document()->findBlockByLineNumber(firstLine + linesOnScreen() - 2).blockNumber();
bool isLineVisible = firstBlock <= p.line && p.line <= lastBlock;
QTextCursor tc = cursor();
setCursorPosition(&tc, p);
setCursor(tc);
if (!isLineVisible)
alignViewportToCursor(Qt::AlignVCenter);
}
void FakeVimHandler::Private::setCursorPosition(QTextCursor *tc, const CursorPosition &p)
{
const int line = qMin(document()->blockCount() - 1, p.line);
QTextBlock block = document()->findBlockByNumber(line);
const int column = qMin(p.column, block.length() - 1);
tc->setPosition(block.position() + column, KeepAnchor);
}
int FakeVimHandler::Private::lastPositionInDocument(bool ignoreMode) const
{
return document()->characterCount()
@@ -5771,8 +5814,11 @@ int FakeVimHandler::Private::lastPositionInLine(int line, bool onlyVisibleLines)
} else {
block = document()->findBlockByNumber(line - 1);
}
return block.position() + qMax(2, block.length())
- (isVisualMode() || m_mode == InsertMode || m_mode == ReplaceMode ? 1 : 2);
const int position = block.position() + block.length() - 1;
if (block.length() > 1 && !isVisualMode() && m_mode != InsertMode && m_mode != ReplaceMode)
return position - 1;
return position;
}
int FakeVimHandler::Private::lineForPosition(int pos) const
@@ -5800,14 +5846,14 @@ void FakeVimHandler::Private::leaveVisualMode()
if (!isVisualMode())
return;
m_lastSelectionCursor = cursor();
QTextCursor tc = cursor();
m_lastSelectionMode = m_visualMode;
int from = m_lastSelectionCursor.anchor();
int to = m_lastSelectionCursor.position();
int from = tc.anchor();
int to = tc.position();
if (from > to)
qSwap(from, to);
setMark('<', from);
setMark('>', to);
setMark('<', CursorPosition(document(), from));
setMark('>', CursorPosition(document(), to));
if (isVisualLineMode())
m_movetype = MoveLineWise;
else if (isVisualCharMode())
@@ -5867,6 +5913,7 @@ void FakeVimHandler::Private::undo()
// FIXME: That's only an approximaxtion. The real solution might
// be to store marks and old userData with QTextBlock setUserData
// and retrieve them afterward.
CursorPosition lastPos(cursor());
const int current = revision();
EDITOR(undo());
const int rev = revision();
@@ -5886,7 +5933,8 @@ void FakeVimHandler::Private::undo()
if (state.revision == rev) {
m_lastChangePosition = state.position;
m_marks = state.marks;
setPosition(m_lastChangePosition);
setMark('\'', lastPos);
setCursorPosition(m_lastChangePosition);
state.revision = current;
m_redo.push(m_undo.pop());
}
@@ -5899,6 +5947,7 @@ void FakeVimHandler::Private::undo()
void FakeVimHandler::Private::redo()
{
CursorPosition lastPos(cursor());
const int current = revision();
EDITOR(redo());
const int rev = revision();
@@ -5916,12 +5965,10 @@ void FakeVimHandler::Private::redo()
if (!m_redo.empty()) {
State &state = m_redo.top();
if (state.revision == rev) {
int pos = qMin(document()->characterCount() - 1, state.position);
if (lineForPosition(pos) != state.line)
pos = lastPositionInLine(state.line);
m_lastChangePosition = pos;
m_lastChangePosition = state.position;
m_marks = state.marks;
setPosition(m_lastChangePosition);
setMark('\'', lastPos);
setCursorPosition(m_lastChangePosition);
state.revision = current;
m_undo.push(m_redo.pop());
}
@@ -5977,9 +6024,9 @@ void FakeVimHandler::Private::enterExMode()
void FakeVimHandler::Private::recordJump()
{
CursorPosition pos = cursorPosition();
setMark('\'', pos.position);
if (m_jumpListUndo.isEmpty() || m_jumpListUndo.top().position != pos.position)
CursorPosition pos(cursor());
setMark('\'', pos);
if (m_jumpListUndo.isEmpty() || m_jumpListUndo.top() != pos)
m_jumpListUndo.push(pos);
m_jumpListRedo.clear();
UNDO_DEBUG("jumps: " << m_jumpListUndo);
@@ -5990,9 +6037,10 @@ void FakeVimHandler::Private::jump(int distance)
QStack<CursorPosition> &from = (distance > 0) ? m_jumpListRedo : m_jumpListUndo;
QStack<CursorPosition> &to = (distance > 0) ? m_jumpListUndo : m_jumpListRedo;
int len = qMin(qAbs(distance), from.size());
setMark('\'', position());
CursorPosition m(cursor());
setMark('\'', m);
for (int i = 0; i < len; ++i) {
to.push(cursorPosition());
to.push(m);
setCursorPosition(from.top());
from.pop();
}
@@ -6278,28 +6326,25 @@ void FakeVimHandler::Private::selectQuotedStringTextObject(bool inner,
m_movetype = MoveInclusive;
}
int FakeVimHandler::Private::mark(int code) const
CursorPosition FakeVimHandler::Private::mark(int code) const
{
// FIXME: distinguish local and global marks.
//qDebug() << "MARK: " << code << m_marks.value(code, -1) << m_marks;
if (isVisualMode()) {
if (code == '<')
return position();
return CursorPosition(document(), qMin(anchor(), position()));
if (code == '>')
return anchor();
return CursorPosition(document(), qMax(anchor(), position()));
}
if (code == '.')
return m_lastChangePosition;
QTextCursor tc = m_marks.value(code);
return tc.isNull() ? -1 : tc.position();
return m_marks.value(code);
}
void FakeVimHandler::Private::setMark(int code, int position)
void FakeVimHandler::Private::setMark(int code, CursorPosition position)
{
// FIXME: distinguish local and global marks.
QTextCursor tc = cursor();
tc.setPosition(position, MoveAnchor);
m_marks[code] = tc;
m_marks[code] = position;
}
RangeMode FakeVimHandler::Private::registerRangeMode(int reg) const