vim-mode: support location jump with Ctrl+O and Ctrl+I

This commit is contained in:
Le Tan 2017-06-20 19:37:52 +08:00
parent 10a9447b96
commit 878264b8fc
2 changed files with 241 additions and 0 deletions

View File

@ -623,6 +623,21 @@ bool VVim::handleKeyPressEvent(int key, int modifiers)
m_editor->setTextCursor(cursor);
setMode(VimMode::Insert);
} else if (modifiers == Qt::ControlModifier) {
// Ctrl+I, jump to next location.
if (!m_tokens.isEmpty()) {
break;
}
tryGetRepeatToken(m_keys, m_tokens);
if (!m_keys.isEmpty()) {
break;
}
addActionToken(Action::JumpNextLocation);
processCommand(m_tokens);
break;
}
break;
@ -711,6 +726,21 @@ bool VVim::handleKeyPressEvent(int key, int modifiers)
setMode(VimMode::Insert);
}
break;
} else if (modifiers == Qt::ControlModifier) {
// Ctrl+O, jump to previous location.
if (!m_tokens.isEmpty()) {
break;
}
tryGetRepeatToken(m_keys, m_tokens);
if (!m_keys.isEmpty()) {
break;
}
addActionToken(Action::JumpPreviousLocation);
processCommand(m_tokens);
break;
}
@ -1655,6 +1685,14 @@ void VVim::processCommand(QList<Token> &p_tokens)
processRedrawLineAction(p_tokens, 2);
break;
case Action::JumpPreviousLocation:
processJumpLocationAction(p_tokens, false);
break;
case Action::JumpNextLocation:
processJumpLocationAction(p_tokens, true);
break;
default:
p_tokens.clear();
break;
@ -1966,6 +2004,9 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
// Jump to the first non-space character of @p_repeat line (block).
V_ASSERT(p_repeat > 0);
// Record current location.
m_locations.addLocation(p_cursor);
// @p_repeat starts from 1 while block number starts from 0.
QTextBlock block = p_doc->findBlockByNumber(p_repeat - 1);
if (block.isValid()) {
@ -1984,6 +2025,10 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
{
// Jump to the first non-space character of the start of the document.
V_ASSERT(p_repeat == -1);
// Record current location.
m_locations.addLocation(p_cursor);
p_cursor.movePosition(QTextCursor::Start, p_moveMode, 1);
VEditUtils::moveCursorFirstNonSpaceCharacter(p_cursor, p_moveMode);
hasMoved = true;
@ -1994,6 +2039,10 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
{
// Jump to the first non-space character of the end of the document.
V_ASSERT(p_repeat == -1);
// Record current location.
m_locations.addLocation(p_cursor);
p_cursor.movePosition(QTextCursor::End, p_moveMode, 1);
VEditUtils::moveCursorFirstNonSpaceCharacter(p_cursor, p_moveMode);
hasMoved = true;
@ -3287,6 +3336,51 @@ void VVim::processRedrawLineAction(QList<Token> &p_tokens, int p_dest)
VEditUtils::scrollBlockInPage(m_editor, repeat, p_dest);
}
void VVim::processJumpLocationAction(QList<Token> &p_tokens, bool p_next)
{
int repeat = 1;
if (!p_tokens.isEmpty()) {
Token to = p_tokens.takeFirst();
if (!p_tokens.isEmpty() || !to.isRepeat()) {
p_tokens.clear();
return;
}
repeat = to.m_repeat;
}
QTextCursor cursor = m_editor->textCursor();
Location loc;
if (p_next) {
while (m_locations.hasNext() && repeat > 0) {
--repeat;
loc = m_locations.nextLocation();
}
} else {
while (m_locations.hasPrevious() && repeat > 0) {
--repeat;
loc = m_locations.previousLocation(cursor);
}
}
if (loc.isValid()) {
QTextDocument *doc = m_editor->document();
if (loc.m_blockNumber >= doc->blockCount()) {
message(tr("Mark has invalid line number"));
return;
}
QTextBlock block = doc->findBlockByNumber(loc.m_blockNumber);
int pib = loc.m_positionInBlock;
if (pib >= block.length()) {
pib = block.length() - 1;
}
cursor.setPosition(block.position() + pib);
m_editor->setTextCursor(cursor);
}
}
bool VVim::clearSelection()
{
QTextCursor cursor = m_editor->textCursor();
@ -3930,3 +4024,81 @@ bool VVim::processLeaderSequence(const Key &p_key)
return validSequence;
}
VVim::LocationStack::LocationStack(int p_maximum)
: c_maximumLocations(p_maximum < 10 ? 10 : p_maximum), m_pointer(0)
{
}
bool VVim::LocationStack::hasPrevious() const
{
return m_pointer > 0;
}
bool VVim::LocationStack::hasNext() const
{
return m_pointer < m_locations.size() - 1;
}
void VVim::LocationStack::addLocation(const QTextCursor &p_cursor)
{
int blockNumber = p_cursor.block().blockNumber();
int pib = p_cursor.positionInBlock();
// Remove older location with the same block number.
for (auto it = m_locations.begin(); it != m_locations.end();) {
if (it->m_blockNumber == blockNumber) {
it = m_locations.erase(it);
break;
} else {
++it;
}
}
if (m_locations.size() >= c_maximumLocations) {
m_locations.removeFirst();
}
m_locations.append(Location(blockNumber, pib));
m_pointer = m_locations.size();
qDebug() << QString("add location (%1,%2), pointer=%3")
.arg(blockNumber).arg(pib).arg(m_pointer);
}
const VVim::Location &VVim::LocationStack::previousLocation(const QTextCursor &p_cursor)
{
V_ASSERT(hasPrevious());
if (m_pointer == m_locations.size()) {
// Add current location to the stack.
addLocation(p_cursor);
m_pointer = m_locations.size() - 1;
}
// Jump to previous location.
if (m_pointer > 0) {
--m_pointer;
}
qDebug() << QString("previous location (%1,%2), pointer=%3, size=%4")
.arg(m_locations.at(m_pointer).m_blockNumber)
.arg(m_locations.at(m_pointer).m_positionInBlock)
.arg(m_pointer).arg(m_locations.size());
return m_locations.at(m_pointer);
}
const VVim::Location &VVim::LocationStack::nextLocation()
{
V_ASSERT(hasNext());
++m_pointer;
qDebug() << QString("next location (%1,%2), pointer=%3, size=%4")
.arg(m_locations.at(m_pointer).m_blockNumber)
.arg(m_locations.at(m_pointer).m_positionInBlock)
.arg(m_pointer).arg(m_locations.size());
return m_locations.at(m_pointer);
}

View File

@ -201,6 +201,8 @@ private:
RedrawAtTop,
RedrawAtCenter,
RedrawAtBottom,
JumpPreviousLocation,
JumpNextLocation,
Invalid
};
@ -348,6 +350,68 @@ private:
Key m_key;
};
// Struct for a location.
struct Location
{
Location() : m_blockNumber(-1), m_positionInBlock(0)
{
}
Location(int p_blockNumber, int p_positionInBlock)
: m_blockNumber(p_blockNumber), m_positionInBlock(p_positionInBlock)
{
}
bool isValid() const
{
return m_blockNumber > -1;
}
// Block number of the location, based on 0.
int m_blockNumber;
// Position in block, based on 0.
int m_positionInBlock;
};
// Stack for all the jump locations.
// When we execute a jump action, we push current location to the stack and
// remove older location with the same block number.
// Ctrl+O is also a jum action. If m_pointer points to the top of the stack,
// Ctrl+O will insert a location to the stack.
class LocationStack
{
public:
LocationStack(int p_maximum = 100);
// Add @p_cursor's location to stack.
// Need to delete all older locations with the same block number.
void addLocation(const QTextCursor &p_cursor);
// Go up through the stack. Need to add current location if we are at
// the top of the stack currently.
const Location &previousLocation(const QTextCursor &p_cursor);
// Go down through the stack.
const Location &nextLocation();
bool hasPrevious() const;
bool hasNext() const;
private:
// A stack containing locations.
QList<Location> m_locations;
// Pointer to current element in the stack.
// If we are not in the history of the locations, it points to the next
// element to the top element.
int m_pointer;
// Maximum number of locations in stack.
const int c_maximumLocations;
};
// Returns true if the event is consumed and need no more handling.
bool handleKeyPressEvent(int key, int modifiers);
@ -397,6 +461,9 @@ private:
// @p_dest: 0 for top, 1 for center, 2 for bottom.
void processRedrawLineAction(QList<Token> &p_tokens, int p_dest);
// Action::JumpPreviousLocation and Action::JumpNextLocation action.
void processJumpLocationAction(QList<Token> &p_tokens, bool p_next);
// Clear selection if there is any.
// Returns true if there is selection.
bool clearSelection();
@ -564,6 +631,8 @@ private:
// this actual sequence, m_leaderSequence will be true.
bool m_replayLeaderSequence;
LocationStack m_locations;
static const QChar c_unnamedRegister;
static const QChar c_blackHoleRegister;
static const QChar c_selectionRegister;