1/*
2 SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com>
3 SPDX-FileCopyrightText: 2009 Paul Gideon Dann <pdgiddie@gmail.com>
4 SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
5 SPDX-FileCopyrightText: 2012-2013 Simon St James <kdedevel@etotheipiplusone.com>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include <vimode/inputmodemanager.h>
11
12#include <QApplication>
13#include <QString>
14
15#include <KConfig>
16#include <KConfigGroup>
17#include <KLocalizedString>
18
19#include "completionrecorder.h"
20#include "completionreplayer.h"
21#include "definitions.h"
22#include "globalstate.h"
23#include "jumps.h"
24#include "kateconfig.h"
25#include "katedocument.h"
26#include "kateglobal.h"
27#include "katerenderer.h"
28#include "kateview.h"
29#include "kateviewinternal.h"
30#include "kateviinputmode.h"
31#include "lastchangerecorder.h"
32#include "macrorecorder.h"
33#include "macros.h"
34#include "marks.h"
35#include "registers.h"
36#include "searcher.h"
37#include "variable.h"
38#include <vimode/emulatedcommandbar/emulatedcommandbar.h>
39#include <vimode/keymapper.h>
40#include <vimode/keyparser.h>
41#include <vimode/modes/insertvimode.h>
42#include <vimode/modes/normalvimode.h>
43#include <vimode/modes/replacevimode.h>
44#include <vimode/modes/visualvimode.h>
45
46using namespace KateVi;
47
48InputModeManager::InputModeManager(KateViInputMode *inputAdapter, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal)
49 : m_inputAdapter(inputAdapter)
50{
51 m_currentViMode = ViMode::NormalMode;
52 m_previousViMode = ViMode::NormalMode;
53
54 m_viNormalMode = new NormalViMode(this, view, viewInternal);
55 m_viInsertMode = new InsertViMode(this, view, viewInternal);
56 m_viVisualMode = new VisualViMode(this, view, viewInternal);
57 m_viReplaceMode = new ReplaceViMode(this, view, viewInternal);
58
59 m_view = view;
60 m_viewInternal = viewInternal;
61
62 m_insideHandlingKeyPressCount = 0;
63
64 m_keyMapperStack.push(t: std::make_shared<KeyMapper>(args: this, args: m_view->doc()));
65
66 m_temporaryNormalMode = false;
67
68 m_jumps = new Jumps();
69 m_marks = new Marks(this);
70
71 m_searcher = new Searcher(this);
72 m_completionRecorder = new CompletionRecorder(this);
73 m_completionReplayer = new CompletionReplayer(this);
74
75 m_macroRecorder = new MacroRecorder(this);
76
77 m_lastChangeRecorder = new LastChangeRecorder(this);
78
79 // We have to do this outside of NormalMode, as we don't want
80 // VisualMode (which inherits from NormalMode) to respond
81 // to changes in the document as well.
82 m_viNormalMode->beginMonitoringDocumentChanges();
83}
84
85InputModeManager::~InputModeManager()
86{
87 delete m_viNormalMode;
88 delete m_viInsertMode;
89 delete m_viVisualMode;
90 delete m_viReplaceMode;
91 delete m_jumps;
92 delete m_marks;
93 delete m_searcher;
94 delete m_macroRecorder;
95 delete m_completionRecorder;
96 delete m_completionReplayer;
97 delete m_lastChangeRecorder;
98}
99
100bool InputModeManager::handleKeypress(const QKeyEvent *e)
101{
102 m_insideHandlingKeyPressCount++;
103 bool res = false;
104 bool keyIsPartOfMapping = false;
105 const bool isSyntheticSearchCompletedKeyPress = m_inputAdapter->viModeEmulatedCommandBar()->isSendingSyntheticSearchCompletedKeypress();
106
107 // With macros, we want to record the keypresses *before* they are mapped, but if they end up *not* being part
108 // of a mapping, we don't want to record them when they are played back by m_keyMapper, hence
109 // the "!isPlayingBackRejectedKeys()". And obviously, since we're recording keys before they are mapped, we don't
110 // want to also record the executed mapping, as when we replayed the macro, we'd get duplication!
111 if (m_macroRecorder->isRecording() && !m_macroRecorder->isReplaying() && !isSyntheticSearchCompletedKeyPress && !keyMapper()->isExecutingMapping()
112 && !keyMapper()->isPlayingBackRejectedKeys() && !lastChangeRecorder()->isReplaying()) {
113 m_macroRecorder->record(event: *e);
114 }
115
116 if (!m_lastChangeRecorder->isReplaying() && !isSyntheticSearchCompletedKeyPress) {
117 if (e->key() == Qt::Key_AltGr) {
118 return true; // do nothing
119 }
120
121 // Hand off to the key mapper, and decide if this key is part of a mapping.
122 if (e->key() != Qt::Key_Control && e->key() != Qt::Key_Shift && e->key() != Qt::Key_Alt && e->key() != Qt::Key_Meta) {
123 const QChar key = KeyParser::self()->KeyEventToQChar(keyEvent: *e);
124 if (keyMapper()->handleKeypress(key)) {
125 keyIsPartOfMapping = true;
126 res = true;
127 }
128 }
129 }
130
131 if (!keyIsPartOfMapping) {
132 if (!m_lastChangeRecorder->isReplaying() && !isSyntheticSearchCompletedKeyPress) {
133 // record key press so that it can be repeated via "."
134 m_lastChangeRecorder->record(event: *e);
135 }
136
137 if (m_inputAdapter->viModeEmulatedCommandBar()->isActive()) {
138 res = m_inputAdapter->viModeEmulatedCommandBar()->handleKeyPress(keyEvent: e);
139 } else {
140 res = getCurrentViModeHandler()->handleKeypress(e);
141 }
142 }
143
144 m_insideHandlingKeyPressCount--;
145 Q_ASSERT(m_insideHandlingKeyPressCount >= 0);
146
147 return res;
148}
149
150void InputModeManager::feedKeyPresses(const QString &keyPresses) const
151{
152 int key;
153 Qt::KeyboardModifiers mods;
154 QString text;
155
156 for (const QChar c : keyPresses) {
157 QString decoded = KeyParser::self()->decodeKeySequence(keys: QString(c));
158 key = -1;
159 mods = Qt::NoModifier;
160 text.clear();
161
162 if (decoded.length() > 1) { // special key
163
164 // remove the angle brackets
165 decoded.remove(i: 0, len: 1);
166 decoded.remove(i: decoded.indexOf(c: QLatin1Char('>')), len: 1);
167
168 // check if one or more modifier keys where used
169 if (decoded.indexOf(s: QLatin1String("s-")) != -1 || decoded.indexOf(s: QLatin1String("c-")) != -1 || decoded.indexOf(s: QLatin1String("m-")) != -1
170 || decoded.indexOf(s: QLatin1String("a-")) != -1) {
171 int s = decoded.indexOf(s: QLatin1String("s-"));
172 if (s != -1) {
173 mods |= Qt::ShiftModifier;
174 decoded.remove(i: s, len: 2);
175 }
176
177 int c = decoded.indexOf(s: QLatin1String("c-"));
178 if (c != -1) {
179 mods |= CONTROL_MODIFIER;
180 decoded.remove(i: c, len: 2);
181 }
182
183 int a = decoded.indexOf(s: QLatin1String("a-"));
184 if (a != -1) {
185 mods |= Qt::AltModifier;
186 decoded.remove(i: a, len: 2);
187 }
188
189 int m = decoded.indexOf(s: QLatin1String("m-"));
190 if (m != -1) {
191 mods |= META_MODIFIER;
192 decoded.remove(i: m, len: 2);
193 }
194
195 if (decoded.length() > 1) {
196 key = KeyParser::self()->vi2qt(keypress: decoded);
197 } else if (decoded.length() == 1) {
198 key = int(decoded.at(i: 0).toUpper().toLatin1());
199 text = decoded.at(i: 0);
200 }
201 } else { // no modifiers
202 key = KeyParser::self()->vi2qt(keypress: decoded);
203 }
204 } else {
205 key = decoded.at(i: 0).unicode();
206 text = decoded.at(i: 0);
207 }
208
209 if (key == -1) {
210 continue;
211 }
212
213 // We have to be clever about which widget we dispatch to, as we can trigger
214 // shortcuts if we're not careful (even if Vim mode is configured to steal shortcuts).
215 QKeyEvent k(QEvent::KeyPress, key, mods, text);
216 QWidget *destWidget = nullptr;
217 if (QApplication::activePopupWidget()) {
218 // According to the docs, the activePopupWidget, if present, takes all events.
219 destWidget = QApplication::activePopupWidget();
220 } else if (QApplication::focusWidget()) {
221 if (QApplication::focusWidget()->focusProxy()) {
222 destWidget = QApplication::focusWidget()->focusProxy();
223 } else {
224 destWidget = QApplication::focusWidget();
225 }
226 } else {
227 destWidget = m_view->focusProxy();
228 }
229 QApplication::sendEvent(receiver: destWidget, event: &k);
230 }
231}
232
233bool InputModeManager::isHandlingKeypress() const
234{
235 return m_insideHandlingKeyPressCount > 0;
236}
237
238void InputModeManager::storeLastChangeCommand()
239{
240 m_lastChange = m_lastChangeRecorder->encodedChanges();
241 m_lastChangeCompletionsLog = m_completionRecorder->currentChangeCompletionsLog();
242}
243
244void InputModeManager::repeatLastChange()
245{
246 m_lastChangeRecorder->replay(commands: m_lastChange, completions: m_lastChangeCompletionsLog);
247}
248
249void InputModeManager::clearCurrentChangeLog()
250{
251 m_lastChangeRecorder->clear();
252 m_completionRecorder->clearCurrentChangeCompletionsLog();
253}
254
255void InputModeManager::doNotLogCurrentKeypress()
256{
257 m_macroRecorder->dropLast();
258 m_lastChangeRecorder->dropLast();
259}
260
261void InputModeManager::changeViMode(ViMode newMode)
262{
263 m_previousViMode = m_currentViMode;
264 m_currentViMode = newMode;
265}
266
267ViMode InputModeManager::getCurrentViMode() const
268{
269 return m_currentViMode;
270}
271
272KTextEditor::View::ViewMode InputModeManager::getCurrentViewMode() const
273{
274 switch (m_currentViMode) {
275 case ViMode::InsertMode:
276 return KTextEditor::View::ViModeInsert;
277 case ViMode::VisualMode:
278 return KTextEditor::View::ViModeVisual;
279 case ViMode::VisualLineMode:
280 return KTextEditor::View::ViModeVisualLine;
281 case ViMode::VisualBlockMode:
282 return KTextEditor::View::ViModeVisualBlock;
283 case ViMode::ReplaceMode:
284 return KTextEditor::View::ViModeReplace;
285 case ViMode::NormalMode:
286 default:
287 return KTextEditor::View::ViModeNormal;
288 }
289}
290
291ViMode InputModeManager::getPreviousViMode() const
292{
293 return m_previousViMode;
294}
295
296bool InputModeManager::isAnyVisualMode() const
297{
298 return ((m_currentViMode == ViMode::VisualMode) || (m_currentViMode == ViMode::VisualLineMode) || (m_currentViMode == ViMode::VisualBlockMode));
299}
300
301::ModeBase *InputModeManager::getCurrentViModeHandler() const
302{
303 switch (m_currentViMode) {
304 case ViMode::NormalMode:
305 return m_viNormalMode;
306 case ViMode::InsertMode:
307 return m_viInsertMode;
308 case ViMode::VisualMode:
309 case ViMode::VisualLineMode:
310 case ViMode::VisualBlockMode:
311 return m_viVisualMode;
312 case ViMode::ReplaceMode:
313 return m_viReplaceMode;
314 }
315 return nullptr;
316}
317
318void InputModeManager::viEnterNormalMode()
319{
320 bool moveCursorLeft = (m_currentViMode == ViMode::InsertMode || m_currentViMode == ViMode::ReplaceMode) && m_viewInternal->cursorPosition().column() > 0;
321
322 if (!m_lastChangeRecorder->isReplaying() && (m_currentViMode == ViMode::InsertMode || m_currentViMode == ViMode::ReplaceMode)) {
323 // '^ is the insert mark and "^ is the insert register,
324 // which holds the last inserted text
325 KTextEditor::Range r(m_view->cursorPosition(), m_marks->getInsertStopped());
326
327 if (r.isValid()) {
328 QString insertedText = m_view->doc()->text(range: r);
329 m_inputAdapter->globalState()->registers()->setInsertStopped(insertedText);
330 }
331
332 m_marks->setInsertStopped(KTextEditor::Cursor(m_view->cursorPosition()));
333 }
334
335 changeViMode(newMode: ViMode::NormalMode);
336
337 if (moveCursorLeft) {
338 m_viewInternal->cursorPrevChar();
339 }
340 m_inputAdapter->setCaretStyle(KTextEditor::caretStyles::Block);
341 m_viewInternal->update();
342}
343
344void InputModeManager::viEnterInsertMode()
345{
346 changeViMode(newMode: ViMode::InsertMode);
347 m_marks->setInsertStopped(KTextEditor::Cursor(m_view->cursorPosition()));
348 if (getTemporaryNormalMode()) {
349 // Ensure the key log contains a request to re-enter Insert mode, else the keystrokes made
350 // after returning from temporary normal mode will be treated as commands!
351 m_lastChangeRecorder->record(event: QKeyEvent(QEvent::KeyPress, Qt::Key_I, Qt::NoModifier, QStringLiteral("i")));
352 }
353 m_inputAdapter->setCaretStyle(KTextEditor::caretStyles::Line);
354 setTemporaryNormalMode(false);
355 m_viewInternal->update();
356}
357
358void InputModeManager::viEnterVisualMode(ViMode mode)
359{
360 changeViMode(newMode: mode);
361
362 // If the selection is inclusive, the caret should be a block.
363 // If the selection is exclusive, the caret should be a line.
364 m_inputAdapter->setCaretStyle(KTextEditor::caretStyles::Block);
365 m_viewInternal->update();
366 getViVisualMode()->setVisualModeType(mode);
367 getViVisualMode()->init();
368}
369
370void InputModeManager::viEnterReplaceMode()
371{
372 changeViMode(newMode: ViMode::ReplaceMode);
373 m_marks->setStartEditYanked(KTextEditor::Cursor(m_view->cursorPosition()));
374 m_inputAdapter->setCaretStyle(KTextEditor::caretStyles::Underline);
375 m_viewInternal->update();
376}
377
378NormalViMode *InputModeManager::getViNormalMode()
379{
380 return m_viNormalMode;
381}
382
383InsertViMode *InputModeManager::getViInsertMode()
384{
385 return m_viInsertMode;
386}
387
388VisualViMode *InputModeManager::getViVisualMode()
389{
390 return m_viVisualMode;
391}
392
393ReplaceViMode *InputModeManager::getViReplaceMode()
394{
395 return m_viReplaceMode;
396}
397
398const QString InputModeManager::getVerbatimKeys() const
399{
400 QString cmd;
401
402 switch (getCurrentViMode()) {
403 case ViMode::NormalMode:
404 cmd = m_viNormalMode->getVerbatimKeys();
405 break;
406 case ViMode::InsertMode:
407 case ViMode::ReplaceMode:
408 // ...
409 break;
410 case ViMode::VisualMode:
411 case ViMode::VisualLineMode:
412 case ViMode::VisualBlockMode:
413 cmd = m_viVisualMode->getVerbatimKeys();
414 break;
415 }
416
417 return cmd;
418}
419
420void InputModeManager::readSessionConfig(const KConfigGroup &config)
421{
422 m_jumps->readSessionConfig(config);
423 m_marks->readSessionConfig(config);
424}
425
426void InputModeManager::writeSessionConfig(KConfigGroup &config)
427{
428 m_jumps->writeSessionConfig(config);
429 m_marks->writeSessionConfig(config);
430}
431
432void InputModeManager::reset()
433{
434 if (m_viVisualMode) {
435 m_viVisualMode->reset();
436 }
437}
438
439KeyMapper *InputModeManager::keyMapper()
440{
441 return m_keyMapperStack.top().get();
442}
443
444void InputModeManager::updateCursor(const KTextEditor::Cursor c)
445{
446 m_inputAdapter->updateCursor(newCursor: c);
447}
448
449GlobalState *InputModeManager::globalState() const
450{
451 return m_inputAdapter->globalState();
452}
453
454KTextEditor::ViewPrivate *InputModeManager::view() const
455{
456 return m_view;
457}
458
459void InputModeManager::pushKeyMapper(std::shared_ptr<KeyMapper> mapper)
460{
461 m_keyMapperStack.push(t: mapper);
462}
463
464void InputModeManager::popKeyMapper()
465{
466 m_keyMapperStack.pop();
467}
468

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