KTextEditor

visualvimode.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Erlend Hamberg <ehamberg@gmail.com>
3 SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
4 SPDX-FileCopyrightText: 2012-2013 Simon St James <kdedevel@etotheipiplusone.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "kateview.h"
10#include <document/katedocument.h>
11#include <inputmode/kateviinputmode.h>
12
13#include <vimode/inputmodemanager.h>
14#include <vimode/marks.h>
15#include <vimode/modes/visualvimode.h>
16#include <vimode/motion.h>
17#include <vimode/range.h>
18
19using namespace KateVi;
20
21VisualViMode::VisualViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal)
22 : NormalViMode(viInputModeManager, view, viewInternal)
23{
24 m_start.setPosition(-1, -1);
25 m_mode = ViMode::VisualMode;
26
27 connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &VisualViMode::updateSelection);
28}
29
30void VisualViMode::selectInclusive(const KTextEditor::Cursor c1, const KTextEditor::Cursor c2)
31{
32 if (c1 >= c2) {
33 m_view->setSelection(KTextEditor::Range(c1.line(), c1.column() + 1, c2.line(), c2.column()));
34 } else {
35 m_view->setSelection(KTextEditor::Range(c1.line(), c1.column(), c2.line(), c2.column() + 1));
36 }
37}
38
39void VisualViMode::selectBlockInclusive(const KTextEditor::Cursor c1, const KTextEditor::Cursor c2)
40{
41 m_view->setBlockSelection(true);
42
43 if (c1.column() >= c2.column()) {
44 m_view->setSelection(KTextEditor::Range(c1.line(), c1.column() + 1, c2.line(), c2.column()));
45 } else {
46 m_view->setSelection(KTextEditor::Range(c1.line(), c1.column(), c2.line(), c2.column() + 1));
47 }
48}
49
50void VisualViMode::selectLines(KTextEditor::Range range)
51{
52 int sline = qMin(range.start().line(), range.end().line());
53 int eline = qMax(range.start().line(), range.end().line());
54 int ecol = m_view->doc()->lineLength(eline) + 1;
55
56 m_view->setSelection(KTextEditor::Range(KTextEditor::Cursor(sline, 0), KTextEditor::Cursor(eline, ecol)));
57}
58
59void VisualViMode::goToPos(const Range &r)
60{
61 KTextEditor::Cursor c = m_view->cursorPosition();
62
63 if (r.startLine != -1 && r.startColumn != -1 && c == m_start) {
64 m_start.setLine(r.startLine);
65 m_start.setColumn(r.startColumn);
66 c.setLine(r.endLine);
67 c.setColumn(r.endColumn);
68 } else if (r.startLine != -1 && r.startColumn != -1 && m_motionCanChangeWholeVisualModeSelection) {
69 const KTextEditor::Cursor textObjectBegin(r.startLine, r.startColumn);
70 if (textObjectBegin < m_start) {
71 m_start.setLine(r.startLine);
72 m_start.setColumn(r.startColumn);
73 c.setLine(r.endLine);
74 c.setColumn(r.endColumn);
75 }
76 } else {
77 c.setLine(r.endLine);
78 c.setColumn(r.endColumn);
79 }
80
81 if (c.line() >= doc()->lines()) {
82 c.setLine(doc()->lines() - 1);
83 }
84
85 updateCursor(c);
86
87 // Setting range for a command
88 m_commandRange = Range(m_start, c, m_commandRange.motionType);
89
90 // If visual mode is blockwise
91 if (isVisualBlock()) {
92 selectBlockInclusive(m_start, c);
93
94 // Need to correct command range to make it inclusive.
95 if ((c.line() < m_start.line() && c.column() > m_start.column()) || (c.line() > m_start.line() && c.column() < m_start.column())) {
96 qSwap(m_commandRange.endColumn, m_commandRange.startColumn);
97 }
98 return;
99 } else {
100 m_view->setBlockSelection(false);
101 }
102
103 // If visual mode is linewise
104 if (isVisualLine()) {
105 selectLines(KTextEditor::Range(m_start, c));
106 return;
107 }
108
109 // If visual mode is charwise
110 selectInclusive(m_start, c);
111}
112
113void VisualViMode::reset()
114{
115 m_mode = ViMode::VisualMode;
116
117 // only switch to normal mode if still in visual mode. commands like c, s, ...
118 // can have switched to insert mode
119 if (m_viInputModeManager->isAnyVisualMode()) {
120 saveRangeMarks();
121 m_lastVisualMode = m_viInputModeManager->getCurrentViMode();
122
123 // Return the cursor back to start of selection after.
124 if (!m_pendingResetIsDueToExit) {
125 KTextEditor::Cursor c = m_view->cursorPosition();
126 if (m_start.line() != -1 && m_start.column() != -1) {
127 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) {
128 if (m_start.line() < c.line()) {
129 updateCursor(KTextEditor::Cursor(m_start.line(), 0));
130 m_stickyColumn = -1;
131 }
132 } else {
133 updateCursor(qMin(m_start, c));
134 m_stickyColumn = -1;
135 }
136 }
137 }
138
139 if (m_viInputModeManager->getPreviousViMode() == ViMode::InsertMode) {
140 startInsertMode();
141 } else {
142 startNormalMode();
143 }
144 }
145
146 if (!m_commandShouldKeepSelection) {
147 m_view->removeSelection();
148 } else {
149 m_commandShouldKeepSelection = false;
150 }
151
152 m_start.setPosition(-1, -1);
153 m_pendingResetIsDueToExit = false;
154}
155
156void VisualViMode::saveRangeMarks()
157{
158 // DO NOT save these marks if the
159 // action that exited visual mode deleted the selection
160 if (m_deleteCommand == false) {
161 m_viInputModeManager->marks()->setSelectionStart(m_start);
162 m_viInputModeManager->marks()->setSelectionFinish(m_view->cursorPosition());
163 }
164}
165
166void VisualViMode::init()
167{
168 // when using "gv" we already have a start position
169 if (!m_start.isValid()) {
170 m_start = m_view->cursorPosition();
171 }
172
173 if (isVisualLine()) {
174 KTextEditor::Cursor c = m_view->cursorPosition();
175 selectLines(KTextEditor::Range(c, c));
176 }
177
178 m_commandRange = Range(m_start, m_start, m_commandRange.motionType);
179}
180
181void VisualViMode::setVisualModeType(ViMode mode)
182{
183 Q_ASSERT(mode == ViMode::VisualMode || mode == ViMode::VisualLineMode || mode == ViMode::VisualBlockMode);
184 m_mode = mode;
185}
186
187void VisualViMode::switchStartEnd()
188{
189 KTextEditor::Cursor c = m_start;
190 m_start = m_view->cursorPosition();
191
192 updateCursor(c);
193
194 m_stickyColumn = -1;
195}
196
197void VisualViMode::goToPos(const KTextEditor::Cursor c)
198{
199 Range r(c, InclusiveMotion);
200 goToPos(r);
201}
202
203void VisualViMode::updateSelection()
204{
205 if (!m_viInputModeManager->inputAdapter()->isActive()) {
206 return;
207 }
208 if (m_viInputModeManager->isHandlingKeypress() && !m_isUndo) {
209 return;
210 }
211
212 // If we are there it's already not VisualBlock mode.
213 m_view->setBlockSelection(false);
214
215 // If not valid going back to normal mode
216 KTextEditor::Range r = m_view->selectionRange();
217 if (!r.isValid()) {
218 // Don't screw up the cursor's position. See BUG #337286.
219 m_pendingResetIsDueToExit = true;
220 reset();
221 return;
222 }
223
224 // If already not in visual mode, it's time to go there.
225 if (m_viInputModeManager->getCurrentViMode() != ViMode::VisualMode) {
226 commandEnterVisualMode();
227 }
228
229 // Set range for commands
230 m_start = (m_view->cursorPosition() == r.start()) ? r.end() : r.start();
231 m_commandRange = Range(r.start(), r.end(), m_commandRange.motionType);
232 // The end of the range seems to be one space forward of where it should be.
233 m_commandRange.endColumn--;
234}
235
236#define ADDCMD(STR, FUNC, FLGS) Command(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
237
238#define ADDMOTION(STR, FUNC, FLGS) Motion(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
239
240const std::vector<Command> &VisualViMode::commands()
241{
242 // init once, is expensive
243 static std::vector<Command> global{
244 ADDCMD("J", commandJoinLines, IS_CHANGE),
245 ADDCMD("c", commandChange, IS_CHANGE),
246 ADDCMD("s", commandChange, IS_CHANGE),
247 ADDCMD("C", commandChangeToEOL, IS_CHANGE),
248 ADDCMD("S", commandChangeToEOL, IS_CHANGE),
249 ADDCMD("d", commandDelete, IS_CHANGE),
250 ADDCMD("<delete>", commandDelete, IS_CHANGE),
251 ADDCMD("D", commandDeleteToEOL, IS_CHANGE),
252 ADDCMD("x", commandDeleteChar, IS_CHANGE),
253 ADDCMD("X", commandDeleteCharBackward, IS_CHANGE),
254 ADDCMD("gu", commandMakeLowercase, IS_CHANGE),
255 ADDCMD("u", commandMakeLowercase, IS_CHANGE),
256 ADDCMD("gU", commandMakeUppercase, IS_CHANGE),
257 ADDCMD("g~", commandChangeCaseRange, IS_CHANGE),
258 ADDCMD("U", commandMakeUppercase, IS_CHANGE),
259 ADDCMD("y", commandYank, 0),
260 ADDCMD("Y", commandYankToEOL, 0),
261 ADDCMD("p", commandPaste, IS_CHANGE),
262 ADDCMD("P", commandPasteBefore, IS_CHANGE),
263 ADDCMD("r.", commandReplaceCharacter, IS_CHANGE | REGEX_PATTERN),
264 ADDCMD(":", commandSwitchToCmdLine, SHOULD_NOT_RESET),
265 ADDCMD("m.", commandSetMark, REGEX_PATTERN | SHOULD_NOT_RESET),
266 ADDCMD(">", commandIndentLines, IS_CHANGE),
267 ADDCMD("<", commandUnindentLines, IS_CHANGE),
268 ADDCMD("<c-c>", commandAbort, 0),
269 ADDCMD("<c-[>", commandAbort, 0),
270 ADDCMD("ga", commandPrintCharacterCode, SHOULD_NOT_RESET),
271 ADDCMD("v", commandEnterVisualMode, SHOULD_NOT_RESET),
272 ADDCMD("V", commandEnterVisualLineMode, SHOULD_NOT_RESET),
273 ADDCMD("o", commandToOtherEnd, SHOULD_NOT_RESET | CAN_LAND_INSIDE_FOLDING_RANGE),
274 ADDCMD("=", commandAlignLines, SHOULD_NOT_RESET),
275 ADDCMD("~", commandChangeCase, IS_CHANGE),
276 ADDCMD("I", commandPrependToBlock, IS_CHANGE),
277 ADDCMD("A", commandAppendToBlock, IS_CHANGE),
278 ADDCMD("gq", commandFormatLines, IS_CHANGE),
279 ADDCMD("q.", commandStartRecordingMacro, REGEX_PATTERN | SHOULD_NOT_RESET),
280 ADDCMD("@.", commandReplayMacro, REGEX_PATTERN | SHOULD_NOT_RESET),
281 ADDCMD("z.", commandCenterViewOnNonBlank, 0),
282 ADDCMD("zz", commandCenterViewOnCursor, 0),
283 ADDCMD("z<return>", commandTopViewOnNonBlank, 0),
284 ADDCMD("zt", commandTopViewOnCursor, 0),
285 ADDCMD("z-", commandBottomViewOnNonBlank, 0),
286 ADDCMD("zb", commandBottomViewOnCursor, 0),
287 };
288 return global;
289}
290
291const std::vector<Motion> &VisualViMode::motions()
292{
293 // init once, is expensive
294 static std::vector<Motion> global{
295 // regular motions
296 ADDMOTION("h", motionLeft, 0),
297 ADDMOTION("<left>", motionLeft, 0),
298 ADDMOTION("<backspace>", motionLeft, 0),
299 ADDMOTION("j", motionDown, 0),
300 ADDMOTION("<down>", motionDown, 0),
301 ADDMOTION("k", motionUp, 0),
302 ADDMOTION("<up>", motionUp, 0),
303 ADDMOTION("l", motionRight, 0),
304 ADDMOTION("<right>", motionRight, 0),
305 ADDMOTION(" ", motionRight, 0),
306 ADDMOTION("$", motionToEOL, 0),
307 ADDMOTION("<end>", motionToEOL, 0),
308 ADDMOTION("g_", motionToLastNonBlank, 0),
309 ADDMOTION("0", motionToColumn0, 0),
310 ADDMOTION("<home>", motionToColumn0, 0),
311 ADDMOTION("^", motionToFirstCharacterOfLine, 0),
312 ADDMOTION("f.", motionFindChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
313 ADDMOTION("F.", motionFindCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
314 ADDMOTION("t.", motionToChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
315 ADDMOTION("T.", motionToCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
316 ADDMOTION(";", motionRepeatlastTF, CAN_LAND_INSIDE_FOLDING_RANGE),
317 ADDMOTION(",", motionRepeatlastTFBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
318 ADDMOTION("n", motionFindNext, CAN_LAND_INSIDE_FOLDING_RANGE),
319 ADDMOTION("N", motionFindPrev, CAN_LAND_INSIDE_FOLDING_RANGE),
320 ADDMOTION("gg", motionToLineFirst, 0),
321 ADDMOTION("G", motionToLineLast, 0),
322 ADDMOTION("w", motionWordForward, CAN_LAND_INSIDE_FOLDING_RANGE),
323 ADDMOTION("W", motionWORDForward, CAN_LAND_INSIDE_FOLDING_RANGE),
324 ADDMOTION("<c-right>", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
325 ADDMOTION("<c-left>", motionWordBackward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
326 ADDMOTION("b", motionWordBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
327 ADDMOTION("B", motionWORDBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
328 ADDMOTION("e", motionToEndOfWord, CAN_LAND_INSIDE_FOLDING_RANGE),
329 ADDMOTION("E", motionToEndOfWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
330 ADDMOTION("ge", motionToEndOfPrevWord, CAN_LAND_INSIDE_FOLDING_RANGE),
331 ADDMOTION("gE", motionToEndOfPrevWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
332 ADDMOTION("|", motionToScreenColumn, 0),
333 ADDMOTION("%", motionToMatchingItem, CAN_LAND_INSIDE_FOLDING_RANGE),
334 ADDMOTION("`.", motionToMark, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
335 ADDMOTION("'.", motionToMarkLine, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
336 ADDMOTION("[[", motionToPreviousBraceBlockStart, CAN_LAND_INSIDE_FOLDING_RANGE),
337 ADDMOTION("]]", motionToNextBraceBlockStart, CAN_LAND_INSIDE_FOLDING_RANGE),
338 ADDMOTION("[]", motionToPreviousBraceBlockEnd, CAN_LAND_INSIDE_FOLDING_RANGE),
339 ADDMOTION("][", motionToNextBraceBlockEnd, CAN_LAND_INSIDE_FOLDING_RANGE),
340 ADDMOTION("*", motionToNextOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
341 ADDMOTION("#", motionToPrevOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
342 ADDMOTION("<c-f>", motionPageDown, 0),
343 ADDMOTION("<pagedown>", motionPageDown, 0),
344 ADDMOTION("<c-b>", motionPageUp, 0),
345 ADDMOTION("<pageup>", motionPageUp, 0),
346 ADDMOTION("gj", motionToNextVisualLine, 0),
347 ADDMOTION("g<down>", motionToNextVisualLine, 0),
348 ADDMOTION("gk", motionToPrevVisualLine, 0),
349 ADDMOTION("g<up>", motionToPrevVisualLine, 0),
350 ADDMOTION("(", motionToPreviousSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
351 ADDMOTION(")", motionToNextSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
352 ADDMOTION("{", motionToBeforeParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
353 ADDMOTION("}", motionToAfterParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
354 ADDMOTION("<c-u>", motionHalfPageUp, 0),
355 ADDMOTION("<c-d>", motionHalfPageDown, 0),
356
357 // text objects
358 ADDMOTION("iw", textObjectInnerWord, 0),
359 ADDMOTION("aw", textObjectAWord, 0),
360 ADDMOTION("iW", textObjectInnerWORD, 0),
361 ADDMOTION("aW", textObjectAWORD, IS_NOT_LINEWISE),
362 ADDMOTION("is", textObjectInnerSentence, IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
363 ADDMOTION("as", textObjectASentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE | CAN_LAND_INSIDE_FOLDING_RANGE),
364 ADDMOTION("ip", textObjectInnerParagraph, IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
365 ADDMOTION("ap", textObjectAParagraph, IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
366 ADDMOTION("i\"", textObjectInnerQuoteDouble, CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
367 ADDMOTION("a\"", textObjectAQuoteDouble, CAN_LAND_INSIDE_FOLDING_RANGE),
368 ADDMOTION("i'", textObjectInnerQuoteSingle, CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
369 ADDMOTION("a'", textObjectAQuoteSingle, CAN_LAND_INSIDE_FOLDING_RANGE),
370 ADDMOTION("i[()b]", textObjectInnerParen, REGEX_PATTERN | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
371 ADDMOTION("a[()b]", textObjectAParen, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
372 ADDMOTION("i[{}B]",
373 textObjectInnerCurlyBracket,
374 REGEX_PATTERN | IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
375 ADDMOTION("a[{}B]", textObjectACurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
376 ADDMOTION("i[><]",
377 textObjectInnerInequalitySign,
378 REGEX_PATTERN | IS_NOT_LINEWISE | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
379 ADDMOTION("i[\\[\\]]", textObjectInnerBracket, REGEX_PATTERN | CAN_CHANGE_WHOLE_VISUAL_MODE_SELECTION | CAN_LAND_INSIDE_FOLDING_RANGE),
380 ADDMOTION("a[\\[\\]]", textObjectABracket, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
381 ADDMOTION("i,", textObjectInnerComma, CAN_LAND_INSIDE_FOLDING_RANGE),
382 ADDMOTION("a,", textObjectAComma, CAN_LAND_INSIDE_FOLDING_RANGE),
383
384 ADDMOTION("/<enter>", motionToIncrementalSearchMatch, CAN_LAND_INSIDE_FOLDING_RANGE),
385 ADDMOTION("?<enter>", motionToIncrementalSearchMatch, CAN_LAND_INSIDE_FOLDING_RANGE),
386 };
387 return global;
388}
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
void setColumn(int column) noexcept
Set the cursor column to column.
Definition cursor.h:201
void setPosition(Cursor position) noexcept
Set the current cursor position to position.
Definition cursor.h:150
constexpr bool isValid() const noexcept
Returns whether the current position of this cursor is a valid position (line + column must both be >...
Definition cursor.h:102
void setLine(int line) noexcept
Set the cursor line to line.
Definition cursor.h:183
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
constexpr bool isValid() const noexcept
Validity check.
void selectionChanged(KTextEditor::View *view)
This signal is emitted whenever the view's selection changes.
Commands for the vi normal mode.
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:44 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.