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