KTextEditor

keymapper.cpp
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(m_mappingTimer, &QTimer::timeout, this, &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(Mappings::mappingModeForCurrentViMode(m_viInputModeManager->inputAdapter()),
41 m_fullMappingMatch,
42 false,
43 true);
44 if (!m_viInputModeManager->globalState()->mappings()->isRecursive(Mappings::mappingModeForCurrentViMode(m_viInputModeManager->inputAdapter()),
45 m_fullMappingMatch)) {
46 m_doNotExpandFurtherMappings = true;
47 }
48 m_doc->editStart();
49 m_viInputModeManager->feedKeyPresses(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(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(key);
83
84 bool isPartialMapping = false;
85 bool isFullMapping = false;
86 m_fullMappingMatch.clear();
87 const auto mappingMode = Mappings::mappingModeForCurrentViMode(m_viInputModeManager->inputAdapter());
88 const auto mappings = m_viInputModeManager->globalState()->mappings()->getAll(mappingMode, false, true);
89 for (const QString &mapping : mappings) {
90 if (mapping.startsWith(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(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}
QString & append(QChar ch)
void clear()
bool isNull() const const
int size() const const
void setSingleShot(bool singleShot)
void start(int msec)
void stop()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Feb 24 2024 20:00:58 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.