diff options
| -rw-r--r-- | TODO.org | 11 | ||||
| -rw-r--r-- | include/InputMediator.hpp | 7 | ||||
| -rw-r--r-- | include/keymap/KeymapEvaluator.hpp | 62 | ||||
| -rw-r--r-- | include/utils.hpp | 6 | ||||
| -rw-r--r-- | include/widgets/MainWindow.hpp | 2 | ||||
| -rw-r--r-- | spec/KeymapEvaluatorSpec.cpp | 121 | ||||
| -rw-r--r-- | spec/testUtils.h | 2 | ||||
| -rw-r--r-- | src/InputMediator.cpp | 8 | ||||
| -rw-r--r-- | src/keymap/KeymapEvaluator.cpp | 53 | ||||
| -rw-r--r-- | src/widgets/MainWindow.cpp | 24 |
10 files changed, 232 insertions, 64 deletions
@@ -1,17 +1,10 @@ ** Current -- [X] Multi tabs -- [X] Open in new tab/new window -- [X] Lua for command input -- [X] Simpler shell-ey/vimscript-ey language for command input -- [X] Command completion -- [X] Command/URL input executor -- [X] Refactor MainWindow -- [X] Buffer list/search -- [ ] Multi-window +- [ ] Process spawning lua (+ stdio) - [ ] Keybindings (lua api) - [ ] Tab history navigation - [ ] Press tab for fill highlighted item in completion - [ ] Assign ID to each tab (reference in lua api) +- [ ] Multi-window ** Next - [ ] Modal keys diff --git a/include/InputMediator.hpp b/include/InputMediator.hpp index 4aaa6c1..7194ff9 100644 --- a/include/InputMediator.hpp +++ b/include/InputMediator.hpp @@ -4,6 +4,7 @@ #include <QtCore> #include "LuaRuntime.hpp" +#include "keymap/KeymapEvaluator.hpp" #include "utils.hpp" #include "widgets/WebViewStack.hpp" @@ -11,13 +12,16 @@ class InputMediator : public QObject { Q_OBJECT public: - InputMediator(WebViewStack *webViewStack, LuaRuntime *luaRuntime); + InputMediator(WebViewStack *webViewStack, LuaRuntime *luaRuntime, + KeymapEvaluator *keymapEvaluator); + ~InputMediator(); DELEGATE(webViewStack, openUrl, openUrl) DELEGATE(webViewStack, currentUrl, currentUrl) DELEGATE(webViewStack, next, nextWebView) DELEGATE(webViewStack, previous, previousWebView) DELEGATE(webViewStack, closeCurrent, closeCurrentWebView) + DELEGATE(keymapEvaluator, evaluate, evaluateKeymap) private: void evaluateCommand(QString command); @@ -25,4 +29,5 @@ private: private: WebViewStack *webViewStack; LuaRuntime *luaRuntime; + KeymapEvaluator *keymapEvaluator; }; diff --git a/include/keymap/KeymapEvaluator.hpp b/include/keymap/KeymapEvaluator.hpp index 54409b1..db93900 100644 --- a/include/keymap/KeymapEvaluator.hpp +++ b/include/keymap/KeymapEvaluator.hpp @@ -1,70 +1,42 @@ #pragma once -#include "keymap/KeySeqParser.hpp" #include <QWidget> #include <QtCore/qmap.h> #include <QtCore/qnamespace.h> #include <QtCore> +#include <functional> + +#include "keymap/KeySeqParser.hpp" +#include "utils.hpp" + +typedef std::function<void()> KeyAction; struct KeyMap { KeySequence keySequence; - QString action; // TODO: string for testing + KeyAction action; }; +enum KeyMode { Normal, Insert }; + class KeymapEvaluator : public QObject { Q_OBJECT public: KeymapEvaluator(); - void addKeymap(QString mode, QString key, QString action) { - if (!modalKeys.contains(mode)) - modalKeys.insert(mode, {}); - - auto keySeq = keySeqParser.parse(key); - // TODO: Add actions - modalKeys[mode].append((KeyMap){.keySequence = keySeq, .action = action}); - } - - void evaluate(Qt::KeyboardModifiers modifiers, Qt::Key key) { - if (key == Qt::Key_Control || key == Qt::Key_Shift || key == Qt::Key_Meta || - key == Qt::Key_Alt) - return; + void addKeymap(KeyMode mode, QString key, KeyAction action); + bool evaluate(Qt::KeyboardModifiers modifiers, Qt::Key key); - auto keymaps = currentModeKeys(); - auto foundPendingMatches = false; - - activeKeySequence.append((KeyChord){.mod = modifiers, .key = key}); - - for (auto &keymap : *keymaps) { - auto matchType = - KeySeqParser::keySequenceMatch(keymap.keySequence, activeKeySequence); - - if (matchType == KeyMatchType::Match) { - qDebug() << "Matched!" << keymap.action; - activeKeySequence.clear(); - return; - } else if (matchType == KeyMatchType::Pending) { - foundPendingMatches = true; - } - } - - if (!foundPendingMatches) - activeKeySequence.clear(); - } + DEFINE_SETTER(setCurrentMode, currentMode) + DEFINE_GETTER(getCurrentMode, currentMode) private: - const QList<KeyMap> *currentModeKeys() { - if (!modalKeys.contains(currentMode)) - return new QList<KeyMap>(); - - return &modalKeys[currentMode]; - } + const QList<KeyMap> *currentModeKeys(); + bool isInsertableMode(); private: - QMap<QString, QList<KeyMap>> modalKeys; + QMap<KeyMode, QList<KeyMap>> modalKeys; KeySeqParser keySeqParser; - QString currentMode = "default"; - + KeyMode currentMode = KeyMode::Normal; KeySequence activeKeySequence; }; diff --git a/include/utils.hpp b/include/utils.hpp index 65667f7..0650ac2 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -3,3 +3,9 @@ template <typename... Args> decltype(auto) METHOD_AS(Args &&...args) { \ return OBJ->METHOD(std::forward<Args>(args)...); \ } + +#define DEFINE_SETTER(METHOD, PROPERTY) \ + template <typename Arg> void METHOD(Arg val) { PROPERTY = val; } + +#define DEFINE_GETTER(METHOD, EXPR) \ + template <typename Arg> decltype(auto) METHOD() { return EXPR; } diff --git a/include/widgets/MainWindow.hpp b/include/widgets/MainWindow.hpp index 3e3a6d6..2bbc42e 100644 --- a/include/widgets/MainWindow.hpp +++ b/include/widgets/MainWindow.hpp @@ -4,7 +4,6 @@ #include "Configuration.hpp" #include "InputMediator.hpp" -#include "keymap/KeymapEvaluator.hpp" class MainWindow : public QMainWindow { public: @@ -17,5 +16,4 @@ private: private: InputMediator *inputMediator; Configuration configuration; - KeymapEvaluator keymapEvaluator; }; diff --git a/spec/KeymapEvaluatorSpec.cpp b/spec/KeymapEvaluatorSpec.cpp new file mode 100644 index 0000000..4c19dfd --- /dev/null +++ b/spec/KeymapEvaluatorSpec.cpp @@ -0,0 +1,121 @@ +#include "testUtils.h" +#include <QtCore> + +#include "keymap/KeymapEvaluator.hpp" + +class KeymapEvaluatorSpec : public QObject { + Q_OBJECT + +private slots: + void testEvaluateSingleKeyChord() { + context("when the key sequence is mapped"); + it("calls mapping") { + int keymapWasCalled = false; + KeymapEvaluator evaluator; + evaluator.addKeymap(KeyMode::Normal, "<c-t>", + [&keymapWasCalled]() { keymapWasCalled = true; }); + + evaluator.evaluate(Qt::ControlModifier, Qt::Key_T); + + QVERIFY(keymapWasCalled); + } + + context("when the key sequence is not mapped"); + it("does not call mapping") { + int keymapWasCalled = false; + KeymapEvaluator evaluator; + evaluator.addKeymap(KeyMode::Normal, "<c-t>", + [&keymapWasCalled]() { keymapWasCalled = true; }); + + evaluator.evaluate(Qt::ControlModifier, Qt::Key_K); + + QVERIFY(NOT keymapWasCalled); + } + } + + void testEvaluateMultiKeySequence() { + context("when the full key sequence is mapped"); + it("calls mapping") { + int keymapWasCalled = false; + KeymapEvaluator evaluator; + evaluator.addKeymap(KeyMode::Normal, "<c-t>a", + [&keymapWasCalled]() { keymapWasCalled = true; }); + + evaluator.evaluate(Qt::ControlModifier, Qt::Key_T); + evaluator.evaluate(Qt::NoModifier, Qt::Key_A); + + QVERIFY(keymapWasCalled); + } + + context("when only part of a mapped key sequence is entered"); + it("does not call mapping") { + int keymapWasCalled = false; + KeymapEvaluator evaluator; + evaluator.addKeymap(KeyMode::Normal, "<c-t>a", + [&keymapWasCalled]() { keymapWasCalled = true; }); + + evaluator.evaluate(Qt::ControlModifier, Qt::Key_T); + + QVERIFY(NOT keymapWasCalled); + } + + context("when the key sequence is not mapped"); + it("does not call mapping") { + int keymapWasCalled = false; + KeymapEvaluator evaluator; + evaluator.addKeymap(KeyMode::Normal, "<c-t>a", + [&keymapWasCalled]() { keymapWasCalled = true; }); + + evaluator.evaluate(Qt::ControlModifier, Qt::Key_K); + + QVERIFY(NOT keymapWasCalled); + } + + context("when the key sequence is not mapped"); + it("does not call mapping") { + int keymapWasCalled = false; + KeymapEvaluator evaluator; + evaluator.addKeymap(KeyMode::Normal, "<c-t>a", + [&keymapWasCalled]() { keymapWasCalled = true; }); + + evaluator.evaluate(Qt::ControlModifier, Qt::Key_T); + evaluator.evaluate(Qt::NoModifier, Qt::Key_B); + + QVERIFY(NOT keymapWasCalled); + } + + context("when an incorrect sequence is entered before the correct one"); + it("calls mapping") { + int keymapWasCalled = false; + KeymapEvaluator evaluator; + evaluator.addKeymap(KeyMode::Normal, "<c-t>a", + [&keymapWasCalled]() { keymapWasCalled = true; }); + + evaluator.evaluate(Qt::ControlModifier, Qt::Key_T); + evaluator.evaluate(Qt::NoModifier, Qt::Key_B); + evaluator.evaluate(Qt::ControlModifier, Qt::Key_T); + evaluator.evaluate(Qt::NoModifier, Qt::Key_A); + + QVERIFY(keymapWasCalled); + } + + // TODO: maybe fix this behavior + context( + "when partial mapped sequence is entered before an entire sequence"); + it("does not call mapping") { + int keymapWasCalled = false; + KeymapEvaluator evaluator; + evaluator.addKeymap(KeyMode::Normal, "<c-t>a", + [&keymapWasCalled]() { keymapWasCalled = true; }); + + evaluator.evaluate(Qt::ControlModifier, Qt::Key_T); + evaluator.evaluate(Qt::ControlModifier, Qt::Key_T); + evaluator.evaluate(Qt::NoModifier, Qt::Key_A); + + QVERIFY(NOT keymapWasCalled); + } + } +}; + +QTEST_REGISTER(KeymapEvaluatorSpec) +#include "KeymapEvaluatorSpec.moc" diff --git a/spec/testUtils.h b/spec/testUtils.h index 20b3ba8..fd0c30b 100644 --- a/spec/testUtils.h +++ b/spec/testUtils.h @@ -4,6 +4,8 @@ #include <QtTest/qtestcase.h> #include <cstdio> +#define NOT ! + #define ANSI_BOLD "\x1b[1m" #define COLOR_CONTEXT "\x1b[32m" ANSI_BOLD #define COLOR_IT "\x1b[36m" ANSI_BOLD diff --git a/src/InputMediator.cpp b/src/InputMediator.cpp index 350d8ac..f935ad2 100644 --- a/src/InputMediator.cpp +++ b/src/InputMediator.cpp @@ -7,8 +7,10 @@ #include "LuaRuntime.hpp" #include "widgets/WebViewStack.hpp" -InputMediator::InputMediator(WebViewStack *webViewStack, LuaRuntime *luaRuntime) - : QObject(), webViewStack(webViewStack), luaRuntime(luaRuntime) { +InputMediator::InputMediator(WebViewStack *webViewStack, LuaRuntime *luaRuntime, + KeymapEvaluator *keymapEvaluator) + : QObject(), webViewStack(webViewStack), luaRuntime(luaRuntime), + keymapEvaluator(keymapEvaluator) { connect(luaRuntime, &LuaRuntime::urlOpened, webViewStack, &WebViewStack::openUrl); } @@ -40,3 +42,5 @@ void InputMediator::evaluateCommand(QString command) { break; } } + +InputMediator::~InputMediator() { delete webViewStack; } diff --git a/src/keymap/KeymapEvaluator.cpp b/src/keymap/KeymapEvaluator.cpp index cca10e8..baae819 100644 --- a/src/keymap/KeymapEvaluator.cpp +++ b/src/keymap/KeymapEvaluator.cpp @@ -4,3 +4,56 @@ #include "keymap/KeymapEvaluator.hpp" KeymapEvaluator::KeymapEvaluator() : QObject() {} + +// TODO: Clear mapping after some time + +void KeymapEvaluator::addKeymap(KeyMode mode, QString key, KeyAction action) { + if (!modalKeys.contains(mode)) + modalKeys.insert(mode, {}); + + auto keySeq = keySeqParser.parse(key); + modalKeys[mode].append((KeyMap){.keySequence = keySeq, .action = action}); +} + +bool KeymapEvaluator::evaluate(Qt::KeyboardModifiers modifiers, Qt::Key key) { + if (key == Qt::Key_Control || key == Qt::Key_Shift || key == Qt::Key_Meta || + key == Qt::Key_Alt) + return true; + + auto keymaps = currentModeKeys(); + auto foundPendingMatches = false; + + activeKeySequence.append((KeyChord){.mod = modifiers, .key = key}); + + for (auto &keymap : *keymaps) { + auto matchType = + KeySeqParser::keySequenceMatch(keymap.keySequence, activeKeySequence); + + if (matchType == KeyMatchType::Match) { + keymap.action(); + activeKeySequence.clear(); + return true; + } else if (matchType == KeyMatchType::Pending) { + foundPendingMatches = true; + } + } + + if (!foundPendingMatches) + activeKeySequence.clear(); + + if (isInsertableMode()) + return foundPendingMatches; + + return true; +} + +bool KeymapEvaluator::isInsertableMode() { + return currentMode == KeyMode::Insert; +} + +const QList<KeyMap> *KeymapEvaluator::currentModeKeys() { + if (!modalKeys.contains(currentMode)) + return new QList<KeyMap>(); + + return &modalKeys[currentMode]; +} diff --git a/src/widgets/MainWindow.cpp b/src/widgets/MainWindow.cpp index 327a77c..32bde30 100644 --- a/src/widgets/MainWindow.cpp +++ b/src/widgets/MainWindow.cpp @@ -6,6 +6,7 @@ #include <QtWidgets/qapplication.h> #include "InputMediator.hpp" +#include "keymap/KeymapEvaluator.hpp" #include "widgets/MainWindow.hpp" #include "widgets/WebViewStack.hpp" @@ -34,11 +35,23 @@ MainWindow::MainWindow() { webViewStack->openUrl(QUrl("https://github.com/trending"), OpenType::OpenUrlInBgTab); - inputMediator = new InputMediator(webViewStack, LuaRuntime::instance()); + auto keymapEvaluator = new KeymapEvaluator; + + inputMediator = + new InputMediator(webViewStack, LuaRuntime::instance(), keymapEvaluator); // TODO: remove - keymapEvaluator.addKeymap("default", "<c-t>a", "Stuff"); - keymapEvaluator.addKeymap("default", "<c-t>b", "Other stuff"); + keymapEvaluator->addKeymap(KeyMode::Normal, "i", [keymapEvaluator]() { + keymapEvaluator->setCurrentMode(KeyMode::Insert); + }); + keymapEvaluator->addKeymap(KeyMode::Insert, "<esc>", [keymapEvaluator]() { + keymapEvaluator->setCurrentMode(KeyMode::Normal); + }); + + keymapEvaluator->addKeymap(KeyMode::Normal, "<c-t>a", + []() { qDebug() << "Stuff"; }); + keymapEvaluator->addKeymap(KeyMode::Normal, "<c-t>b", + []() { qDebug() << "Else"; }); } void MainWindow::keyPressEvent(QKeyEvent *event) { @@ -61,7 +74,8 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) { return false; auto keyEvent = static_cast<QKeyEvent *>(event); - keymapEvaluator.evaluate(keyEvent->modifiers(), (Qt::Key)keyEvent->key()); + bool shouldSkip = inputMediator->evaluateKeymap(keyEvent->modifiers(), + (Qt::Key)keyEvent->key()); - return true; + return shouldSkip; } |
