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 | |
19 | using namespace KateVi; |
20 | |
21 | KeyMapper::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 | |
34 | void 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 | |
55 | void 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 | |
64 | void KeyMapper::setMappingTimeout(int timeoutMS) |
65 | { |
66 | m_timeoutlen = timeoutMS; |
67 | } |
68 | |
69 | void KeyMapper::mappingTimerTimeOut() |
70 | { |
71 | if (!m_fullMappingMatch.isNull()) { |
72 | executeMapping(); |
73 | } else { |
74 | playBackRejectedKeys(); |
75 | } |
76 | m_mappingKeys.clear(); |
77 | } |
78 | |
79 | bool 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 | |
144 | void KeyMapper::setDoNotMapNextKeypress() |
145 | { |
146 | m_doNotMapNextKeypress = true; |
147 | } |
148 | |
149 | bool KeyMapper::isExecutingMapping() const |
150 | { |
151 | return m_numMappingsBeingExecuted > 0; |
152 | } |
153 | |
154 | bool KeyMapper::isPlayingBackRejectedKeys() const |
155 | { |
156 | return m_isPlayingBackRejectedKeys; |
157 | } |
158 | |