mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
vim-mode: support simple marks(a-z)
Different behaviors from Vim: after deleting the line with a mark set, VNote could not detect if this mark is set or not. VNote just simply jumps to the same line.
This commit is contained in:
parent
878264b8fc
commit
e305024a58
@ -419,6 +419,33 @@ bool VVim::handleKeyPressEvent(int key, int modifiers)
|
||||
goto clear_accept;
|
||||
}
|
||||
|
||||
if (expectingMarkName()) {
|
||||
// Expecting a mark name to create a mark.
|
||||
if (keyInfo.isAlphabet() && modifiers == Qt::NoModifier) {
|
||||
m_keys.clear();
|
||||
m_marks.setMark(keyToChar(key, modifiers), m_editor->textCursor());
|
||||
}
|
||||
|
||||
goto clear_accept;
|
||||
}
|
||||
|
||||
if (expectingMarkTarget()) {
|
||||
// Expecting a mark name as the target.
|
||||
Movement mm = Movement::Invalid;
|
||||
const Key &aKey = m_keys.first();
|
||||
if (aKey == Key(Qt::Key_Apostrophe)) {
|
||||
mm = Movement::MarkJumpLine;
|
||||
} else {
|
||||
Q_ASSERT(aKey == Key(Qt::Key_QuoteLeft));
|
||||
mm = Movement::MarkJump;
|
||||
}
|
||||
|
||||
tryAddMoveAction();
|
||||
addMovementToken(mm, keyInfo);
|
||||
processCommand(m_tokens);
|
||||
goto clear_accept;
|
||||
}
|
||||
|
||||
if (expectingCharacterTarget()) {
|
||||
// Expecting a target character for f/F/t/T.
|
||||
Movement mm = Movement::Invalid;
|
||||
@ -437,8 +464,6 @@ bool VVim::handleKeyPressEvent(int key, int modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
V_ASSERT(mm != Movement::Invalid);
|
||||
|
||||
tryAddMoveAction();
|
||||
addMovementToken(mm, keyInfo);
|
||||
m_lastFindToken = m_tokens.last();
|
||||
@ -625,7 +650,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers)
|
||||
setMode(VimMode::Insert);
|
||||
} else if (modifiers == Qt::ControlModifier) {
|
||||
// Ctrl+I, jump to next location.
|
||||
if (!m_tokens.isEmpty()) {
|
||||
if (!m_tokens.isEmpty()
|
||||
|| !checkMode(VimMode::Normal)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -729,7 +755,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers)
|
||||
break;
|
||||
} else if (modifiers == Qt::ControlModifier) {
|
||||
// Ctrl+O, jump to previous location.
|
||||
if (!m_tokens.isEmpty()) {
|
||||
if (!m_tokens.isEmpty()
|
||||
|| !checkMode(VimMode::Normal)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1565,6 +1592,52 @@ bool VVim::handleKeyPressEvent(int key, int modifiers)
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_M:
|
||||
{
|
||||
if (modifiers == Qt::NoModifier) {
|
||||
if (m_keys.isEmpty() && m_tokens.isEmpty()) {
|
||||
// m, creating a mark.
|
||||
// We can create marks in Visual mode, too.
|
||||
m_keys.append(keyInfo);
|
||||
goto accept;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_Apostrophe:
|
||||
{
|
||||
if (modifiers == Qt::NoModifier) {
|
||||
// ', jump to the start of line of a mark.
|
||||
// Repeat is useless.
|
||||
tryGetRepeatToken(m_keys, m_tokens);
|
||||
|
||||
if (m_keys.isEmpty()) {
|
||||
m_keys.append(keyInfo);
|
||||
goto accept;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_QuoteLeft:
|
||||
{
|
||||
if (modifiers == Qt::NoModifier) {
|
||||
// `, jump to a mark.
|
||||
// Repeat is useless.
|
||||
tryGetRepeatToken(m_keys, m_tokens);
|
||||
|
||||
if (m_keys.isEmpty()) {
|
||||
m_keys.append(keyInfo);
|
||||
goto accept;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -2238,6 +2311,40 @@ handle_target:
|
||||
break;
|
||||
}
|
||||
|
||||
case Movement::MarkJump:
|
||||
// Fall through.
|
||||
case Movement::MarkJumpLine:
|
||||
{
|
||||
// repeat is useless here.
|
||||
const Key &key = p_token.m_key;
|
||||
QChar target = keyToChar(key.m_key, key.m_modifiers);
|
||||
Location loc = m_marks.getMarkLocation(target);
|
||||
if (loc.isValid()) {
|
||||
if (loc.m_blockNumber >= p_doc->blockCount()) {
|
||||
// Invalid block number.
|
||||
message(tr("Mark not set"));
|
||||
m_marks.clearMark(target);
|
||||
break;
|
||||
}
|
||||
|
||||
// Different from Vim:
|
||||
// We just use the block number for mark, so if we delete the line
|
||||
// where the mark locates, we could not detect if it is set or not.
|
||||
QTextBlock block = p_doc->findBlockByNumber(loc.m_blockNumber);
|
||||
p_cursor.setPosition(block.position(), p_moveMode);
|
||||
if (p_token.m_movement == Movement::MarkJump) {
|
||||
setCursorPositionInBlock(p_cursor, loc.m_positionInBlock, p_moveMode);
|
||||
} else {
|
||||
// Jump to the first non-space character.
|
||||
VEditUtils::moveCursorFirstNonSpaceCharacter(p_cursor, p_moveMode);
|
||||
}
|
||||
|
||||
hasMoved = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -3475,6 +3582,17 @@ bool VVim::expectingLeaderSequence() const
|
||||
return m_keys.first() == Key(Qt::Key_Backslash);
|
||||
}
|
||||
|
||||
bool VVim::expectingMarkName() const
|
||||
{
|
||||
return checkPendingKey(Key(Qt::Key_M)) && m_tokens.isEmpty();
|
||||
}
|
||||
|
||||
bool VVim::expectingMarkTarget() const
|
||||
{
|
||||
return checkPendingKey(Key(Qt::Key_Apostrophe))
|
||||
|| checkPendingKey(Key(Qt::Key_QuoteLeft));
|
||||
}
|
||||
|
||||
QChar VVim::keyToRegisterName(const Key &p_key) const
|
||||
{
|
||||
if (p_key.isAlphabet()) {
|
||||
@ -3816,6 +3934,11 @@ const QMap<QChar, VVim::Register> &VVim::getRegisters() const
|
||||
return m_registers;
|
||||
}
|
||||
|
||||
const VVim::Marks &VVim::getMarks() const
|
||||
{
|
||||
return m_marks;
|
||||
}
|
||||
|
||||
QChar VVim::getCurrentRegisterName() const
|
||||
{
|
||||
return m_regName;
|
||||
@ -4026,7 +4149,7 @@ bool VVim::processLeaderSequence(const Key &p_key)
|
||||
}
|
||||
|
||||
VVim::LocationStack::LocationStack(int p_maximum)
|
||||
: c_maximumLocations(p_maximum < 10 ? 10 : p_maximum), m_pointer(0)
|
||||
: m_pointer(0), c_maximumLocations(p_maximum < 10 ? 10 : p_maximum)
|
||||
{
|
||||
}
|
||||
|
||||
@ -4102,3 +4225,69 @@ const VVim::Location &VVim::LocationStack::nextLocation()
|
||||
|
||||
return m_locations.at(m_pointer);
|
||||
}
|
||||
|
||||
VVim::Marks::Marks()
|
||||
{
|
||||
for (char ch = 'a'; ch <= 'z'; ++ch) {
|
||||
m_marks[QChar(ch)] = Mark();
|
||||
}
|
||||
}
|
||||
|
||||
void VVim::Marks::setMark(QChar p_name, const QTextCursor &p_cursor)
|
||||
{
|
||||
auto it = m_marks.find(p_name);
|
||||
if (it == m_marks.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
it->m_location.m_blockNumber = p_cursor.block().blockNumber();
|
||||
it->m_location.m_positionInBlock = p_cursor.positionInBlock();
|
||||
it->m_text = p_cursor.block().text().trimmed();
|
||||
it->m_name = p_name;
|
||||
|
||||
m_lastUsedMark = p_name;
|
||||
|
||||
qDebug() << QString("set mark %1 to (%2,%3)")
|
||||
.arg(p_name)
|
||||
.arg(it->m_location.m_blockNumber)
|
||||
.arg(it->m_location.m_positionInBlock);
|
||||
}
|
||||
|
||||
void VVim::Marks::clearMark(QChar p_name)
|
||||
{
|
||||
auto it = m_marks.find(p_name);
|
||||
if (it == m_marks.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
it->m_location = Location();
|
||||
it->m_text.clear();
|
||||
|
||||
if (m_lastUsedMark == p_name) {
|
||||
m_lastUsedMark = QChar();
|
||||
}
|
||||
}
|
||||
|
||||
VVim::Location VVim::Marks::getMarkLocation(QChar p_name)
|
||||
{
|
||||
auto it = m_marks.find(p_name);
|
||||
if (it == m_marks.end()) {
|
||||
return Location();
|
||||
}
|
||||
|
||||
if (it->m_location.isValid()) {
|
||||
m_lastUsedMark = p_name;
|
||||
}
|
||||
|
||||
return it->m_location;
|
||||
}
|
||||
|
||||
const QMap<QChar, VVim::Mark> &VVim::Marks::getMarks() const
|
||||
{
|
||||
return m_marks;
|
||||
}
|
||||
|
||||
QChar VVim::Marks::getLastUsedMark() const
|
||||
{
|
||||
return m_lastUsedMark;
|
||||
}
|
||||
|
@ -28,6 +28,30 @@ class VVim : public QObject
|
||||
public:
|
||||
explicit VVim(VEdit *p_editor);
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
struct Register
|
||||
{
|
||||
Register(QChar p_name, const QString &p_value)
|
||||
@ -91,6 +115,37 @@ public:
|
||||
bool m_append;
|
||||
};
|
||||
|
||||
struct Mark
|
||||
{
|
||||
QChar m_name;
|
||||
Location m_location;
|
||||
QString m_text;
|
||||
};
|
||||
|
||||
// We only support simple local marks a-z.
|
||||
class Marks
|
||||
{
|
||||
public:
|
||||
Marks();
|
||||
|
||||
// Set mark @p_name to point to @p_cursor.
|
||||
void setMark(QChar p_name, const QTextCursor &p_cursor);
|
||||
|
||||
void clearMark(QChar p_name);
|
||||
|
||||
// Return the location of mark @p_name.
|
||||
Location getMarkLocation(QChar p_name);
|
||||
|
||||
const QMap<QChar, VVim::Mark> &getMarks() const;
|
||||
|
||||
QChar getLastUsedMark() const;
|
||||
|
||||
private:
|
||||
QMap<QChar, Mark> m_marks;
|
||||
|
||||
QChar m_lastUsedMark;
|
||||
};
|
||||
|
||||
// Handle key press event.
|
||||
// Returns true if the event is consumed and need no more handling.
|
||||
bool handleKeyPressEvent(QKeyEvent *p_event);
|
||||
@ -113,6 +168,9 @@ public:
|
||||
// Turn m_pendingKeys to a string.
|
||||
QString getPendingKeys() const;
|
||||
|
||||
// Get m_marks.
|
||||
const VVim::Marks &getMarks() const;
|
||||
|
||||
signals:
|
||||
// Emit when current mode has been changed.
|
||||
void modeChanged(VimMode p_mode);
|
||||
@ -237,6 +295,8 @@ private:
|
||||
FindBackward,
|
||||
TillForward,
|
||||
TillBackward,
|
||||
MarkJump,
|
||||
MarkJumpLine,
|
||||
Invalid
|
||||
};
|
||||
|
||||
@ -350,30 +410,6 @@ 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.
|
||||
@ -492,6 +528,12 @@ private:
|
||||
// Check if we are in a leader sequence.
|
||||
bool expectingLeaderSequence() const;
|
||||
|
||||
// Check m_keys to see if we are expecting a mark name to create a mark.
|
||||
bool expectingMarkName() const;
|
||||
|
||||
// Check m_keys to see if we are expecting a mark name as the target.
|
||||
bool expectingMarkTarget() const;
|
||||
|
||||
// Return the corresponding register name of @p_key.
|
||||
// If @p_key is not a valid register name, return a NULL QChar.
|
||||
QChar keyToRegisterName(const Key &p_key) const;
|
||||
@ -633,6 +675,8 @@ private:
|
||||
|
||||
LocationStack m_locations;
|
||||
|
||||
Marks m_marks;
|
||||
|
||||
static const QChar c_unnamedRegister;
|
||||
static const QChar c_blackHoleRegister;
|
||||
static const QChar c_selectionRegister;
|
||||
|
@ -31,7 +31,6 @@ void VVimIndicator::setupUI()
|
||||
QTreeWidget *regTree = new QTreeWidget(this);
|
||||
regTree->setColumnCount(2);
|
||||
regTree->header()->setStretchLastSection(true);
|
||||
regTree->header()->setProperty("RegisterTree", true);
|
||||
QStringList headers;
|
||||
headers << tr("Register") << tr("Value");
|
||||
regTree->setHeaderLabels(headers);
|
||||
@ -39,6 +38,21 @@ void VVimIndicator::setupUI()
|
||||
connect(m_regBtn, &VButtonWithWidget::popupWidgetAboutToShow,
|
||||
this, &VVimIndicator::updateRegistersTree);
|
||||
|
||||
m_markBtn = new VButtonWithWidget(QIcon(":/resources/icons/arrow_dropup.svg"),
|
||||
"[]",
|
||||
this);
|
||||
m_markBtn->setProperty("StatusBtn", true);
|
||||
m_markBtn->setFocusPolicy(Qt::NoFocus);
|
||||
QTreeWidget *markTree = new QTreeWidget(this);
|
||||
markTree->setColumnCount(4);
|
||||
markTree->header()->setStretchLastSection(true);
|
||||
headers.clear();
|
||||
headers << tr("Mark") << tr("Line") << tr("Column") << tr("Text");
|
||||
markTree->setHeaderLabels(headers);
|
||||
m_markBtn->setPopupWidget(markTree);
|
||||
connect(m_markBtn, &VButtonWithWidget::popupWidgetAboutToShow,
|
||||
this, &VVimIndicator::updateMarksTree);
|
||||
|
||||
m_keyLabel = new QLabel(this);
|
||||
QFontMetrics metric(font());
|
||||
m_keyLabel->setMinimumWidth(metric.width('A') * 5);
|
||||
@ -46,6 +60,7 @@ void VVimIndicator::setupUI()
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->addWidget(m_modeLabel);
|
||||
mainLayout->addWidget(m_regBtn);
|
||||
mainLayout->addWidget(m_markBtn);
|
||||
mainLayout->addWidget(m_keyLabel);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
@ -148,6 +163,7 @@ void VVimIndicator::update(const VVim *p_vim)
|
||||
|
||||
VimMode mode = p_vim->getMode();
|
||||
QChar curRegName = p_vim->getCurrentRegisterName();
|
||||
QChar lastUsedMark = p_vim->getMarks().getLastUsedMark();
|
||||
|
||||
QString style = QString("QLabel { padding: 0px 2px 0px 2px; font: bold; background-color: %1; }")
|
||||
.arg(modeBackgroundColor(mode));
|
||||
@ -156,6 +172,10 @@ void VVimIndicator::update(const VVim *p_vim)
|
||||
|
||||
m_regBtn->setText(curRegName);
|
||||
|
||||
QString markText = QString("[%1]")
|
||||
.arg(lastUsedMark.isNull() ? QChar(' ') : lastUsedMark);
|
||||
m_markBtn->setText(markText);
|
||||
|
||||
QString keyText = QString("<span style=\"font-weight:bold;\">%1</span>")
|
||||
.arg(p_vim->getPendingKeys());
|
||||
m_keyLabel->setText(keyText);
|
||||
@ -172,3 +192,37 @@ void VVimIndicator::updateRegistersTree(QWidget *p_widget)
|
||||
const QMap<QChar, VVim::Register> ®s = m_vim->getRegisters();
|
||||
fillTreeItemsWithRegisters(regTree, regs);
|
||||
}
|
||||
|
||||
static void fillTreeItemsWithMarks(QTreeWidget *p_tree,
|
||||
const QMap<QChar, VVim::Mark> &p_marks)
|
||||
{
|
||||
p_tree->clear();
|
||||
for (auto const &mark : p_marks) {
|
||||
if (!mark.m_location.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList itemStr;
|
||||
itemStr << mark.m_name << QString::number(mark.m_location.m_blockNumber + 1)
|
||||
<< QString::number(mark.m_location.m_positionInBlock) << mark.m_text;
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem(p_tree, itemStr);
|
||||
item->setFlags(item->flags() | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||
}
|
||||
|
||||
p_tree->resizeColumnToContents(0);
|
||||
p_tree->resizeColumnToContents(1);
|
||||
p_tree->resizeColumnToContents(2);
|
||||
p_tree->resizeColumnToContents(3);
|
||||
}
|
||||
|
||||
void VVimIndicator::updateMarksTree(QWidget *p_widget)
|
||||
{
|
||||
QTreeWidget *markTree = dynamic_cast<QTreeWidget *>(p_widget);
|
||||
if (!m_vim) {
|
||||
markTree->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const QMap<QChar, VVim::Mark> &marks = m_vim->getMarks().getMarks();
|
||||
fillTreeItemsWithMarks(markTree, marks);
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ public slots:
|
||||
private slots:
|
||||
void updateRegistersTree(QWidget *p_widget);
|
||||
|
||||
void updateMarksTree(QWidget *p_widget);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
@ -31,6 +33,9 @@ private:
|
||||
// Indicate the registers.
|
||||
VButtonWithWidget *m_regBtn;
|
||||
|
||||
// Indicate the marks.
|
||||
VButtonWithWidget *m_markBtn;
|
||||
|
||||
// Indicate the pending keys.
|
||||
QLabel *m_keyLabel;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user