1/*
2 SPDX-FileCopyrightText: KDE Developers
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "marks.h"
8#include "katedocument.h"
9#include "kateview.h"
10#include <vimode/inputmodemanager.h>
11#include <vimode/modes/normalvimode.h>
12
13#include <KLocalizedString>
14
15using namespace KateVi;
16
17namespace
18{
19const QChar BeginEditYanked = QLatin1Char('[');
20const QChar EndEditYanked = QLatin1Char(']');
21const QChar LastChange = QLatin1Char('.');
22const QChar InsertStopped = QLatin1Char('^');
23const QChar SelectionBegin = QLatin1Char('<');
24const QChar SelectionEnd = QLatin1Char('>');
25const QChar FirstUserMark = QLatin1Char('a');
26const QChar LastUserMark = QLatin1Char('z');
27const QChar BeforeJump = QLatin1Char('\'');
28const QChar BeforeJumpAlter = QLatin1Char('`');
29const QChar UserMarks[] = {QLatin1Char('a'), QLatin1Char('b'), QLatin1Char('c'), QLatin1Char('d'), QLatin1Char('e'), QLatin1Char('f'), QLatin1Char('g'),
30 QLatin1Char('h'), QLatin1Char('i'), QLatin1Char('j'), QLatin1Char('k'), QLatin1Char('l'), QLatin1Char('m'), QLatin1Char('n'),
31 QLatin1Char('o'), QLatin1Char('p'), QLatin1Char('q'), QLatin1Char('r'), QLatin1Char('s'), QLatin1Char('t'), QLatin1Char('u'),
32 QLatin1Char('v'), QLatin1Char('w'), QLatin1Char('x'), QLatin1Char('y'), QLatin1Char('z')};
33}
34
35Marks::Marks(InputModeManager *imm)
36 : m_inputModeManager(imm)
37 , m_doc(imm->view()->doc())
38 , m_settingMark(false)
39{
40 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::markChanged, context: this, slot: &Marks::markChanged);
41}
42
43void Marks::readSessionConfig(const KConfigGroup &config)
44{
45 QStringList marks = config.readEntry(key: "ViMarks", aDefault: QStringList());
46 for (int i = 0; i + 2 < marks.size(); i += 3) {
47 KTextEditor::Cursor c(marks.at(i: i + 1).toInt(), marks.at(i: i + 2).toInt());
48 setMark(mark: marks.at(i).at(i: 0), pos: c);
49 }
50
51 syncViMarksAndBookmarks();
52}
53
54void Marks::writeSessionConfig(KConfigGroup &config) const
55{
56 if (m_marks.isEmpty()) {
57 return;
58 }
59
60 QStringList l;
61 l.reserve(asize: m_marks.size());
62 for (const auto &[key, value] : m_marks.asKeyValueRange()) {
63 l << key << QString::number(value->line()) << QString::number(value->column());
64 }
65 config.writeEntry(key: "ViMarks", value: l);
66}
67
68void Marks::setMark(const QChar &_mark, const KTextEditor::Cursor pos)
69{
70 // move on insert is type based, this allows to reuse cursors!
71 // reuse is important for editing intensive things like replace-all
72 const bool moveoninsert = _mark != BeginEditYanked;
73
74 m_settingMark = true;
75
76 // ` and ' is the same register (position before jump)
77 const QChar mark = (_mark == BeforeJumpAlter) ? BeforeJump : _mark;
78
79 // if we have already a cursor for this type: adjust it
80 bool needToAdjustVisibleMark = true;
81 if (KTextEditor::MovingCursor *oldCursor = m_marks.value(key: mark)) {
82 // cleanup mark display only if line changes
83 needToAdjustVisibleMark = oldCursor->line() != pos.line();
84 if (needToAdjustVisibleMark) {
85 int number_of_marks = 0;
86 const auto keys = m_marks.keys();
87 for (QChar c : keys) {
88 if (m_marks.value(key: c)->line() == oldCursor->line()) {
89 number_of_marks++;
90 }
91 }
92 if (number_of_marks == 1) {
93 m_doc->removeMark(line: oldCursor->line(), markType: KTextEditor::Document::markType01);
94 }
95 }
96
97 // adjust position
98 oldCursor->setPosition(pos);
99 } else {
100 // if no old mark of that type, create new one
101 const KTextEditor::MovingCursor::InsertBehavior behavior =
102 moveoninsert ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert;
103 m_marks.insert(key: mark, value: m_doc->newMovingCursor(position: pos, insertBehavior: behavior));
104 }
105
106 // Showing what mark we set, can be skipped if we did not change the line
107 if (isShowable(mark)) {
108 if (needToAdjustVisibleMark && !(m_doc->mark(line: pos.line()) & KTextEditor::Document::markType01)) {
109 m_doc->addMark(line: pos.line(), markType: KTextEditor::Document::markType01);
110 }
111
112 // only show message for active view
113 if (m_inputModeManager->view()->viewInputMode() == KTextEditor::View::ViInputMode) {
114 if (m_doc->activeView() == m_inputModeManager->view()) {
115 m_inputModeManager->getViNormalMode()->message(i18n("Mark set: %1", mark));
116 }
117 }
118 }
119
120 m_settingMark = false;
121}
122
123KTextEditor::Cursor Marks::getMarkPosition(const QChar &mark) const
124{
125 if (m_marks.contains(key: mark)) {
126 KTextEditor::MovingCursor *c = m_marks.value(key: mark);
127 return KTextEditor::Cursor(c->line(), c->column());
128 }
129
130 return KTextEditor::Cursor::invalid();
131}
132
133void Marks::markChanged(KTextEditor::Document *doc, KTextEditor::Mark mark, KTextEditor::Document::MarkChangeAction action)
134{
135 Q_UNUSED(doc)
136
137 if (mark.type != KTextEditor::Document::Bookmark || m_settingMark) {
138 return;
139 }
140
141 if (action == KTextEditor::Document::MarkRemoved) {
142 const auto keys = m_marks.keys();
143 for (QChar markerChar : keys) {
144 if (m_marks.value(key: markerChar)->line() == mark.line) {
145 m_marks.remove(key: markerChar);
146 }
147 }
148 } else if (action == KTextEditor::Document::MarkAdded) {
149 bool freeMarkerCharFound = false;
150
151 for (const QChar &markerChar : UserMarks) {
152 if (!m_marks.value(key: markerChar)) {
153 setMark(mark: markerChar, pos: KTextEditor::Cursor(mark.line, 0));
154 freeMarkerCharFound = true;
155 break;
156 }
157 }
158
159 // only show error when we are in Vi input mode
160 if (!freeMarkerCharFound && m_inputModeManager->view()->viewInputMode() == KTextEditor::View::ViInputMode) {
161 m_inputModeManager->getViNormalMode()->error(i18n("There are no more chars for the next bookmark."));
162 }
163 }
164}
165
166void Marks::syncViMarksAndBookmarks()
167{
168 const QHash<int, KTextEditor::Mark *> &marks = m_doc->marks();
169
170 // Each bookmark should have a vi mark on the same line.
171 for (auto mark : marks) {
172 if (!(mark->type & KTextEditor::Document::markType01)) {
173 continue;
174 }
175
176 bool thereIsViMarkForThisLine = false;
177 for (auto cursor : std::as_const(t&: m_marks)) {
178 if (cursor->line() == mark->line) {
179 thereIsViMarkForThisLine = true;
180 break;
181 }
182 }
183
184 if (thereIsViMarkForThisLine) {
185 continue;
186 }
187
188 for (const QChar &markerChar : UserMarks) {
189 if (!m_marks.value(key: markerChar)) {
190 setMark(mark: markerChar, pos: KTextEditor::Cursor(mark->line, 0));
191 break;
192 }
193 }
194 }
195
196 // For showable vi mark a line should be bookmarked.
197 const auto keys = m_marks.keys();
198 for (QChar markChar : keys) {
199 if (!isShowable(mark: markChar)) {
200 continue;
201 }
202
203 bool thereIsKateMarkForThisLine = false;
204 for (auto mark : marks) {
205 if (!(mark->type & KTextEditor::Document::markType01)) {
206 continue;
207 }
208
209 if (m_marks.value(key: markChar)->line() == mark->line) {
210 thereIsKateMarkForThisLine = true;
211 break;
212 }
213 }
214
215 if (!thereIsKateMarkForThisLine) {
216 m_doc->addMark(line: m_marks.value(key: markChar)->line(), markType: KTextEditor::Document::markType01);
217 }
218 }
219}
220
221QString Marks::getMarksOnTheLine(int line) const
222{
223 QString res;
224 const auto keys = m_marks.keys();
225 for (QChar markerChar : keys) {
226 if (m_marks.value(key: markerChar)->line() == line) {
227 res += markerChar + QLatin1Char(':') + QString::number(m_marks.value(key: markerChar)->column()) + QLatin1Char(' ');
228 }
229 }
230
231 return res;
232}
233
234bool Marks::isShowable(const QChar &mark)
235{
236 return FirstUserMark <= mark && mark <= LastUserMark;
237}
238
239void Marks::setStartEditYanked(const KTextEditor::Cursor pos)
240{
241 setMark(mark: BeginEditYanked, pos);
242}
243
244void Marks::setFinishEditYanked(const KTextEditor::Cursor pos)
245{
246 setMark(mark: EndEditYanked, pos);
247}
248
249void Marks::setLastChange(const KTextEditor::Cursor pos)
250{
251 setMark(mark: LastChange, pos);
252}
253
254void Marks::setInsertStopped(const KTextEditor::Cursor pos)
255{
256 setMark(mark: InsertStopped, pos);
257}
258
259void Marks::setSelectionStart(const KTextEditor::Cursor pos)
260{
261 setMark(mark: SelectionBegin, pos);
262}
263
264void Marks::setSelectionFinish(const KTextEditor::Cursor pos)
265{
266 setMark(mark: SelectionEnd, pos);
267}
268
269void Marks::setUserMark(const QChar &mark, const KTextEditor::Cursor pos)
270{
271 Q_ASSERT(FirstUserMark <= mark && mark <= LastUserMark);
272 setMark(mark: mark, pos);
273}
274
275KTextEditor::Cursor Marks::getStartEditYanked() const
276{
277 return getMarkPosition(mark: BeginEditYanked);
278}
279
280KTextEditor::Cursor Marks::getFinishEditYanked() const
281{
282 return getMarkPosition(mark: EndEditYanked);
283}
284
285KTextEditor::Cursor Marks::getSelectionStart() const
286{
287 return getMarkPosition(mark: SelectionBegin);
288}
289
290KTextEditor::Cursor Marks::getSelectionFinish() const
291{
292 return getMarkPosition(mark: SelectionEnd);
293}
294
295KTextEditor::Cursor Marks::getLastChange() const
296{
297 return getMarkPosition(mark: LastChange);
298}
299
300KTextEditor::Cursor Marks::getInsertStopped() const
301{
302 return getMarkPosition(mark: InsertStopped);
303}
304

source code of ktexteditor/src/vimode/marks.cpp