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(line: -1, column: -1);
25 m_mode = ViMode::VisualMode;
26
27 connect(sender: m_view, signal: &KTextEditor::ViewPrivate::selectionChanged, context: this, slot: &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(a: range.start().line(), b: range.end().line());
53 int eline = qMax(a: range.start().line(), b: range.end().line());
54 int ecol = m_view->doc()->lineLength(line: 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(c1: m_start, c2: 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(value1&: m_commandRange.endColumn, value2&: 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(range: KTextEditor::Range(m_start, c));
106 return;
107 }
108
109 // If visual mode is charwise
110 selectInclusive(c1: m_start, c2: 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(c: KTextEditor::Cursor(m_start.line(), 0));
130 m_stickyColumn = -1;
131 }
132 } else {
133 updateCursor(c: qMin(a: m_start, b: 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(line: -1, column: -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(range: 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}
389

source code of ktexteditor/src/vimode/modes/visualvimode.cpp