1/*
2 SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com>
3 SPDX-FileCopyrightText: 2013 Simon St James <kdedevel@etotheipiplusone.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "globalstate.h"
9#include "katedocument.h"
10#include "kateglobal.h"
11#include "macrorecorder.h"
12#include "mappings.h"
13#include <vimode/inputmodemanager.h>
14#include <vimode/keymapper.h>
15#include <vimode/keyparser.h>
16
17#include <QTimer>
18
19using namespace KateVi;
20
21KeyMapper::KeyMapper(InputModeManager *kateViInputModeManager, KTextEditor::DocumentPrivate *doc)
22 : m_viInputModeManager(kateViInputModeManager)
23 , m_doc(doc)
24{
25 m_mappingTimer = new QTimer(this);
26 m_doNotExpandFurtherMappings = false;
27 m_timeoutlen = 1000; // FIXME: make configurable
28 m_doNotMapNextKeypress = false;
29 m_numMappingsBeingExecuted = 0;
30 m_isPlayingBackRejectedKeys = false;
31 connect(sender: m_mappingTimer, signal: &QTimer::timeout, context: this, slot: &KeyMapper::mappingTimerTimeOut);
32}
33
34void KeyMapper::executeMapping()
35{
36 m_mappingKeys.clear();
37 m_mappingTimer->stop();
38 m_numMappingsBeingExecuted++;
39 const QString mappedKeypresses =
40 m_viInputModeManager->globalState()->mappings()->get(mode: Mappings::mappingModeForCurrentViMode(viInputMode: m_viInputModeManager->inputAdapter()),
41 from: m_fullMappingMatch,
42 decode: false,
43 includeTemporary: true);
44 if (!m_viInputModeManager->globalState()->mappings()->isRecursive(mode: Mappings::mappingModeForCurrentViMode(viInputMode: m_viInputModeManager->inputAdapter()),
45 from: m_fullMappingMatch)) {
46 m_doNotExpandFurtherMappings = true;
47 }
48 m_doc->editStart();
49 m_viInputModeManager->feedKeyPresses(keyPresses: mappedKeypresses);
50 m_doNotExpandFurtherMappings = false;
51 m_doc->editEnd();
52 m_numMappingsBeingExecuted--;
53}
54
55void KeyMapper::playBackRejectedKeys()
56{
57 m_isPlayingBackRejectedKeys = true;
58 const QString mappingKeys = m_mappingKeys;
59 m_mappingKeys.clear();
60 m_viInputModeManager->feedKeyPresses(keyPresses: mappingKeys);
61 m_isPlayingBackRejectedKeys = false;
62}
63
64void KeyMapper::setMappingTimeout(int timeoutMS)
65{
66 m_timeoutlen = timeoutMS;
67}
68
69void KeyMapper::mappingTimerTimeOut()
70{
71 if (!m_fullMappingMatch.isNull()) {
72 executeMapping();
73 } else {
74 playBackRejectedKeys();
75 }
76 m_mappingKeys.clear();
77}
78
79bool KeyMapper::handleKeypress(QChar key)
80{
81 if (!m_doNotExpandFurtherMappings && !m_doNotMapNextKeypress && !m_isPlayingBackRejectedKeys) {
82 m_mappingKeys.append(c: key);
83
84 bool isPartialMapping = false;
85 bool isFullMapping = false;
86 m_fullMappingMatch.clear();
87 const auto mappingMode = Mappings::mappingModeForCurrentViMode(viInputMode: m_viInputModeManager->inputAdapter());
88 const auto mappings = m_viInputModeManager->globalState()->mappings()->getAll(mode: mappingMode, decode: false, includeTemporary: true);
89 for (const QString &mapping : mappings) {
90 if (mapping.startsWith(s: m_mappingKeys)) {
91 if (mapping == m_mappingKeys) {
92 isFullMapping = true;
93 m_fullMappingMatch = mapping;
94 } else {
95 isPartialMapping = true;
96 }
97 }
98 }
99 if (isFullMapping && !isPartialMapping) {
100 // Great - m_mappingKeys is a mapping, and one that can't be extended to
101 // a longer one - execute it immediately.
102 executeMapping();
103 return true;
104 }
105 if (isPartialMapping) {
106 // Need to wait for more characters (or a timeout) before we decide what to
107 // do with this.
108 m_mappingTimer->start(msec: m_timeoutlen);
109 m_mappingTimer->setSingleShot(true);
110 return true;
111 }
112 // We've been swallowing all the keypresses meant for m_keys for our mapping keys; now that we know
113 // this cannot be a mapping, restore them.
114 Q_ASSERT(!isPartialMapping && !isFullMapping);
115 const bool isUserKeypress = !m_viInputModeManager->macroRecorder()->isReplaying() && !isExecutingMapping();
116 if (isUserKeypress && m_mappingKeys.size() == 1) {
117 // Ugh - unpleasant complication starting with Qt 5.5-ish - it is no
118 // longer possible to replay QKeyEvents in such a way that shortcuts
119 // are triggered, so if we want to correctly handle a shortcut (e.g.
120 // ctrl+s for e.g. Save), we can no longer pop it into m_mappingKeys
121 // then immediately playBackRejectedKeys() (as this will not trigger
122 // the e.g. Save shortcut) - the best we can do is, if e.g. ctrl+s is
123 // not part of any mapping, immediately return false, *not* calling
124 // playBackRejectedKeys() and clearing m_mappingKeys ourselves.
125 // If e.g. ctrl+s *is* part of a mapping, then if the mapping is
126 // rejected, the played back e.g. ctrl+s does not trigger the e.g.
127 // Save shortcut. Likewise, we can no longer have e.g. ctrl+s inside
128 // mappings or macros - the e.g. Save shortcut will not be triggered!
129 // Altogether, a pretty disastrous change from Qt's old behaviour -
130 // either they "fix" it (although it could be argued that being able
131 // to trigger shortcuts from QKeyEvents was never the desired behaviour)
132 // or we try to emulate Shortcut-handling ourselves :(
133 m_mappingKeys.clear();
134 return false;
135 } else {
136 playBackRejectedKeys();
137 return true;
138 }
139 }
140 m_doNotMapNextKeypress = false;
141 return false;
142}
143
144void KeyMapper::setDoNotMapNextKeypress()
145{
146 m_doNotMapNextKeypress = true;
147}
148
149bool KeyMapper::isExecutingMapping() const
150{
151 return m_numMappingsBeingExecuted > 0;
152}
153
154bool KeyMapper::isPlayingBackRejectedKeys() const
155{
156 return m_isPlayingBackRejectedKeys;
157}
158

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