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 | |
19 | using namespace KateVi; |
20 | |
21 | VisualViMode::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 | |
30 | void 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 | |
39 | void 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 | |
50 | void 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 | |
59 | void 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 | |
113 | void 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 | |
156 | void 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 | |
166 | void 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 | |
181 | void VisualViMode::setVisualModeType(ViMode mode) |
182 | { |
183 | Q_ASSERT(mode == ViMode::VisualMode || mode == ViMode::VisualLineMode || mode == ViMode::VisualBlockMode); |
184 | m_mode = mode; |
185 | } |
186 | |
187 | void 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 | |
197 | void VisualViMode::goToPos(const KTextEditor::Cursor c) |
198 | { |
199 | Range r(c, InclusiveMotion); |
200 | goToPos(r); |
201 | } |
202 | |
203 | void 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 | |
240 | const 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 | |
291 | const 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 | |