File indexing completed on 2024-04-28 15:31:18

0001 /*
0002     SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com>
0003     SPDX-FileCopyrightText: 2013 Simon St James <kdedevel@etotheipiplusone.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "globalstate.h"
0009 #include "katedocument.h"
0010 #include "kateglobal.h"
0011 #include "macrorecorder.h"
0012 #include "mappings.h"
0013 #include <vimode/inputmodemanager.h>
0014 #include <vimode/keymapper.h>
0015 #include <vimode/keyparser.h>
0016 
0017 #include <QTimer>
0018 
0019 using namespace KateVi;
0020 
0021 KeyMapper::KeyMapper(InputModeManager *kateViInputModeManager, KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view)
0022     : m_viInputModeManager(kateViInputModeManager)
0023     , m_doc(doc)
0024     , m_view(view)
0025 {
0026     m_mappingTimer = new QTimer(this);
0027     m_doNotExpandFurtherMappings = false;
0028     m_timeoutlen = 1000; // FIXME: make configurable
0029     m_doNotMapNextKeypress = false;
0030     m_numMappingsBeingExecuted = 0;
0031     m_isPlayingBackRejectedKeys = false;
0032     connect(m_mappingTimer, &QTimer::timeout, this, &KeyMapper::mappingTimerTimeOut);
0033 }
0034 
0035 void KeyMapper::executeMapping()
0036 {
0037     m_mappingKeys.clear();
0038     m_mappingTimer->stop();
0039     m_numMappingsBeingExecuted++;
0040     const QString mappedKeypresses =
0041         m_viInputModeManager->globalState()->mappings()->get(Mappings::mappingModeForCurrentViMode(m_viInputModeManager->inputAdapter()),
0042                                                              m_fullMappingMatch,
0043                                                              false,
0044                                                              true);
0045     if (!m_viInputModeManager->globalState()->mappings()->isRecursive(Mappings::mappingModeForCurrentViMode(m_viInputModeManager->inputAdapter()),
0046                                                                       m_fullMappingMatch)) {
0047         m_doNotExpandFurtherMappings = true;
0048     }
0049     m_doc->editBegin();
0050     m_viInputModeManager->feedKeyPresses(mappedKeypresses);
0051     m_doNotExpandFurtherMappings = false;
0052     m_doc->editEnd();
0053     m_numMappingsBeingExecuted--;
0054 }
0055 
0056 void KeyMapper::playBackRejectedKeys()
0057 {
0058     m_isPlayingBackRejectedKeys = true;
0059     const QString mappingKeys = m_mappingKeys;
0060     m_mappingKeys.clear();
0061     m_viInputModeManager->feedKeyPresses(mappingKeys);
0062     m_isPlayingBackRejectedKeys = false;
0063 }
0064 
0065 void KeyMapper::setMappingTimeout(int timeoutMS)
0066 {
0067     m_timeoutlen = timeoutMS;
0068 }
0069 
0070 void KeyMapper::mappingTimerTimeOut()
0071 {
0072     if (!m_fullMappingMatch.isNull()) {
0073         executeMapping();
0074     } else {
0075         playBackRejectedKeys();
0076     }
0077     m_mappingKeys.clear();
0078 }
0079 
0080 bool KeyMapper::handleKeypress(QChar key)
0081 {
0082     if (!m_doNotExpandFurtherMappings && !m_doNotMapNextKeypress && !m_isPlayingBackRejectedKeys) {
0083         m_mappingKeys.append(key);
0084 
0085         bool isPartialMapping = false;
0086         bool isFullMapping = false;
0087         m_fullMappingMatch.clear();
0088         const auto mappingMode = Mappings::mappingModeForCurrentViMode(m_viInputModeManager->inputAdapter());
0089         const auto mappings = m_viInputModeManager->globalState()->mappings()->getAll(mappingMode, false, true);
0090         for (const QString &mapping : mappings) {
0091             if (mapping.startsWith(m_mappingKeys)) {
0092                 if (mapping == m_mappingKeys) {
0093                     isFullMapping = true;
0094                     m_fullMappingMatch = mapping;
0095                 } else {
0096                     isPartialMapping = true;
0097                 }
0098             }
0099         }
0100         if (isFullMapping && !isPartialMapping) {
0101             // Great - m_mappingKeys is a mapping, and one that can't be extended to
0102             // a longer one - execute it immediately.
0103             executeMapping();
0104             return true;
0105         }
0106         if (isPartialMapping) {
0107             // Need to wait for more characters (or a timeout) before we decide what to
0108             // do with this.
0109             m_mappingTimer->start(m_timeoutlen);
0110             m_mappingTimer->setSingleShot(true);
0111             return true;
0112         }
0113         // We've been swallowing all the keypresses meant for m_keys for our mapping keys; now that we know
0114         // this cannot be a mapping, restore them.
0115         Q_ASSERT(!isPartialMapping && !isFullMapping);
0116         const bool isUserKeypress = !m_viInputModeManager->macroRecorder()->isReplaying() && !isExecutingMapping();
0117         if (isUserKeypress && m_mappingKeys.size() == 1) {
0118             // Ugh - unpleasant complication starting with Qt 5.5-ish - it is no
0119             // longer possible to replay QKeyEvents in such a way that shortcuts
0120             // are triggered, so if we want to correctly handle a shortcut (e.g.
0121             // ctrl+s for e.g. Save), we can no longer pop it into m_mappingKeys
0122             // then immediately playBackRejectedKeys() (as this will not trigger
0123             // the e.g. Save shortcut) - the best we can do is, if e.g. ctrl+s is
0124             // not part of any mapping, immediately return false, *not* calling
0125             // playBackRejectedKeys() and clearing m_mappingKeys ourselves.
0126             // If e.g. ctrl+s *is* part of a mapping, then if the mapping is
0127             // rejected, the played back e.g. ctrl+s does not trigger the e.g.
0128             // Save shortcut. Likewise, we can no longer have e.g. ctrl+s inside
0129             // mappings or macros - the e.g. Save shortcut will not be triggered!
0130             // Altogether, a pretty disastrous change from Qt's old behaviour -
0131             // either they "fix" it (although it could be argued that being able
0132             // to trigger shortcuts from QKeyEvents was never the desired behaviour)
0133             // or we try to emulate Shortcut-handling ourselves :(
0134             m_mappingKeys.clear();
0135             return false;
0136         } else {
0137             playBackRejectedKeys();
0138             return true;
0139         }
0140     }
0141     m_doNotMapNextKeypress = false;
0142     return false;
0143 }
0144 
0145 void KeyMapper::setDoNotMapNextKeypress()
0146 {
0147     m_doNotMapNextKeypress = true;
0148 }
0149 
0150 bool KeyMapper::isExecutingMapping() const
0151 {
0152     return m_numMappingsBeingExecuted > 0;
0153 }
0154 
0155 bool KeyMapper::isPlayingBackRejectedKeys() const
0156 {
0157     return m_isPlayingBackRejectedKeys;
0158 }
0159 
0160 #include "moc_keymapper.cpp"