From 1d2394068d3194ed7afd38ce1e636747efa3807d Mon Sep 17 00:00:00 2001 From: bog Date: Wed, 4 Oct 2023 11:38:14 +0200 Subject: [PATCH] :sparkles: add command executor. --- meson.build | 5 + src/commons.hpp | 2 +- src/core/Binding.cpp | 18 ++++ src/core/Binding.hpp | 28 ++++++ src/core/Command.cpp | 15 +++ src/core/Command.hpp | 24 +++++ src/core/Context.cpp | 15 +++ src/core/Context.hpp | 19 ++++ src/core/Executor.cpp | 50 ++++++++++ src/core/Executor.hpp | 30 ++++++ src/core/KeyMod.cpp | 88 ++++++++++++++++++ src/core/KeyMod.hpp | 7 ++ src/core/Shortcut.cpp | 82 ++++++---------- src/core/Shortcut.hpp | 3 + tests/Executor.cpp | 211 ++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 544 insertions(+), 53 deletions(-) create mode 100644 src/core/Binding.cpp create mode 100644 src/core/Binding.hpp create mode 100644 src/core/Command.cpp create mode 100644 src/core/Command.hpp create mode 100644 src/core/Context.cpp create mode 100644 src/core/Context.hpp create mode 100644 src/core/Executor.cpp create mode 100644 src/core/Executor.hpp create mode 100644 tests/Executor.cpp diff --git a/meson.build b/meson.build index 6ae1171..d690a49 100644 --- a/meson.build +++ b/meson.build @@ -11,6 +11,10 @@ twq_lib = static_library('tiwiq', sources: [ 'src/core/KeyMod.cpp', 'src/core/Shortcut.cpp', + 'src/core/Command.cpp', + 'src/core/Context.cpp', + 'src/core/Binding.cpp', + 'src/core/Executor.cpp', ], dependencies: [ dependency('ncursesw') @@ -34,6 +38,7 @@ executable('twq-tests', sources: [ 'tests/main.cpp', 'tests/Shortcut.cpp', + 'tests/Executor.cpp', ], dependencies: [ twq_dep, diff --git a/src/commons.hpp b/src/commons.hpp index ea1bc96..d13b796 100644 --- a/src/commons.hpp +++ b/src/commons.hpp @@ -25,7 +25,7 @@ } #define TWQ_ASSERT(COND, MSG) \ - if (! COND) { \ + if (! (COND) ) { \ std::cerr << MSG << std::endl; \ abort(); \ } diff --git a/src/core/Binding.cpp b/src/core/Binding.cpp new file mode 100644 index 0000000..9851883 --- /dev/null +++ b/src/core/Binding.cpp @@ -0,0 +1,18 @@ +#include "Binding.hpp" + +namespace twq +{ + namespace core + { + /*explicit*/ Binding::Binding(std::shared_ptr shortcut, + std::shared_ptr command) + : m_shortcut { shortcut } + , m_command { command } + { + } + + /*virtual*/ Binding::~Binding() + { + } + } +} diff --git a/src/core/Binding.hpp b/src/core/Binding.hpp new file mode 100644 index 0000000..a1be3c4 --- /dev/null +++ b/src/core/Binding.hpp @@ -0,0 +1,28 @@ +#ifndef twq_core_BINDING_HPP +#define twq_core_BINDING_HPP + +#include "Command.hpp" +#include "Shortcut.hpp" + +namespace twq +{ + namespace core + { + class Binding + { + public: + explicit Binding(std::shared_ptr shortcut, + std::shared_ptr command); + virtual ~Binding(); + + std::weak_ptr shortcut() const { return m_shortcut; } + std::weak_ptr command() const { return m_command; } + + private: + std::shared_ptr m_shortcut; + std::shared_ptr m_command; + }; + } +} + +#endif diff --git a/src/core/Command.cpp b/src/core/Command.cpp new file mode 100644 index 0000000..ca1a3e6 --- /dev/null +++ b/src/core/Command.cpp @@ -0,0 +1,15 @@ +#include "Command.hpp" + +namespace twq +{ + namespace core + { + /*explicit*/ Command::Command() + { + } + + /*virtual*/ Command::~Command() + { + } + } +} diff --git a/src/core/Command.hpp b/src/core/Command.hpp new file mode 100644 index 0000000..9b4ffde --- /dev/null +++ b/src/core/Command.hpp @@ -0,0 +1,24 @@ +#ifndef twq_core_COMMAND_HPP +#define twq_core_COMMAND_HPP + +#include "Context.hpp" + +namespace twq +{ + namespace core + { + class Command + { + public: + explicit Command(); + virtual ~Command(); + + virtual void execute(Context& context) = 0; + virtual void undo(Context& context) = 0; + + private: + }; + } +} + +#endif diff --git a/src/core/Context.cpp b/src/core/Context.cpp new file mode 100644 index 0000000..d323a11 --- /dev/null +++ b/src/core/Context.cpp @@ -0,0 +1,15 @@ +#include "Context.hpp" + +namespace twq +{ + namespace core + { + /*explicit*/ Context::Context() + { + } + + /*virtual*/ Context::~Context() + { + } + } +} diff --git a/src/core/Context.hpp b/src/core/Context.hpp new file mode 100644 index 0000000..a91b9d0 --- /dev/null +++ b/src/core/Context.hpp @@ -0,0 +1,19 @@ +#ifndef twq_core_CONTEXT_HPP +#define twq_core_CONTEXT_HPP + +namespace twq +{ + namespace core + { + class Context + { + public: + explicit Context(); + virtual ~Context(); + + private: + }; + } +} + +#endif diff --git a/src/core/Executor.cpp b/src/core/Executor.cpp new file mode 100644 index 0000000..7a6de76 --- /dev/null +++ b/src/core/Executor.cpp @@ -0,0 +1,50 @@ +#include "Executor.hpp" + +namespace twq +{ + namespace core + { + /*explicit*/ Executor::Executor() + { + } + + /*virtual*/ Executor::~Executor() + { + } + + void Executor::register_binding(Binding& binding) + { + m_entries.push_back({binding, 0}); + } + + void Executor::update(Context& context, KeyMod const& keymod) + { + for (auto& entry: m_entries) + { + auto shortcut = entry.binding.shortcut().lock(); + + if (shortcut->get(entry.progression).equals(keymod)) + { + entry.progression++; + } + else + { + entry.progression = 0; + } + + if (entry.progression >= + shortcut->count()) + { + entry.binding.command().lock()->execute(context); + + for (auto& entry: m_entries) + { + entry.progression = 0; + } + + return; + } + } + } + } +} diff --git a/src/core/Executor.hpp b/src/core/Executor.hpp new file mode 100644 index 0000000..5b6afe1 --- /dev/null +++ b/src/core/Executor.hpp @@ -0,0 +1,30 @@ +#ifndef twq_core_EXECUTOR_HPP +#define twq_core_EXECUTOR_HPP + +#include "Binding.hpp" + +namespace twq +{ + namespace core + { + struct Entry { + Binding binding; + size_t progression = 0; + }; + + class Executor + { + public: + explicit Executor(); + virtual ~Executor(); + + void register_binding(Binding& binding); + void update(Context& context, KeyMod const& keymod); + + private: + std::vector m_entries; + }; + } +} + +#endif diff --git a/src/core/KeyMod.cpp b/src/core/KeyMod.cpp index a85a24d..bb52dbf 100644 --- a/src/core/KeyMod.cpp +++ b/src/core/KeyMod.cpp @@ -10,6 +10,64 @@ namespace twq return KeyMod { key_type, mods, std::nullopt }; } + /*explicit*/ KeyMod::KeyMod(std::string const& repr) + { + std::vector mods; + + if (repr.find("C-") != std::string::npos) + { + mods.push_back(MOD_LCTRL); + } + + if (repr.find("A-") != std::string::npos) + { + mods.push_back(MOD_ALT); + } + + ssize_t k = repr.size() - 1; + while (k >= 0 && std::isspace(repr[k])) + { + k--; + } + + std::string value; + + while (k >= 0 + && !std::isspace(repr[k]) + && repr[k] != '-') + { + value = repr[k] + value; + k--; + } + + if (value.size() == 1) + { + m_key_type = KEY_TEXT; + m_mods = mods; + m_text = value.front(); + return; + } + else + { + for (size_t i=0; i const& mods) : m_key_type { KEY_TEXT } @@ -38,6 +96,36 @@ namespace twq std::find(std::begin(m_mods), std::end(m_mods), mod_type); } + bool KeyMod::equals(KeyMod const& rhs) const + { + if (m_key_type != rhs.m_key_type) + { + return false; + } + + if (m_key_type == KEY_TEXT + && *m_text != *rhs.m_text) + { + return false; + } + + if (m_mods.size() != rhs.m_mods.size()) + { + return false; + } + + for (size_t i=0; i const& mods={}); + explicit KeyMod(std::string const& repr); + explicit KeyMod(char text, std::vector const& mods={}); + explicit KeyMod(KeyType key_type, std::vector const& mods, std::optional text); @@ -41,6 +46,8 @@ namespace twq bool has_mod(ModType mod_type) const; + bool equals(KeyMod const& rhs) const; + std::string string() const; virtual ~KeyMod(); diff --git a/src/core/Shortcut.cpp b/src/core/Shortcut.cpp index fef9e31..8c4fc05 100644 --- a/src/core/Shortcut.cpp +++ b/src/core/Shortcut.cpp @@ -1,4 +1,5 @@ #include "Shortcut.hpp" +#include "src/commons.hpp" #include "src/core/KeyMod.hpp" namespace twq @@ -14,56 +15,6 @@ namespace twq std::string buffer; std::vector mods; - auto create_keymod = [&](){ - if (buffer.find("C-") != std::string::npos) - { - mods.push_back(MOD_LCTRL); - } - - if (buffer.find("A-") != std::string::npos) - { - mods.push_back(MOD_ALT); - } - - ssize_t k = buffer.size() - 1; - while (k >= 0 && std::isspace(buffer[k])) - { - k--; - } - - std::string value; - - while (k >= 0 - && !std::isspace(buffer[k]) - && buffer[k] != '-') - { - value = buffer[k] + value; - k--; - } - - if (value.size() == 1) - { - return KeyMod {KEY_TEXT, mods, value.front()}; - } - else - { - for (size_t i=0; i +#include "../src/core/Executor.hpp" + +using namespace twq::core; + +class ExecutorTest +{ +public: + explicit ExecutorTest() {} + virtual ~ExecutorTest() {} + +protected: +}; + +struct CommandMock: public Command +{ + int execute_count = 0; + + void execute(Context&) override + { + execute_count++; + } + + void undo(Context&) override + { + } +}; + +TEST_CASE_METHOD(ExecutorTest, "Executor_one_key") +{ + Context ctx; + auto cmd = std::make_shared(); + Binding b {std::make_shared("a"), cmd}; + + Executor exec; + exec.register_binding(b); + + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(1 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(2 == cmd->execute_count); +} + +TEST_CASE_METHOD(ExecutorTest, "Executor_two_same_keys") +{ + Context ctx; + auto cmd = std::make_shared(); + Binding b {std::make_shared("a a"), cmd}; + + Executor exec; + exec.register_binding(b); + + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(1 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(1 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(2 == cmd->execute_count); +} + +TEST_CASE_METHOD(ExecutorTest, "Executor_two_keys") +{ + Context ctx; + + auto cmd = std::make_shared(); + Binding b {std::make_shared("a b"), cmd}; + + Executor exec; + exec.register_binding(b); + + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"b"}); + REQUIRE(1 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(1 == cmd->execute_count); + exec.update(ctx, KeyMod {"b"}); + REQUIRE(2 == cmd->execute_count); +} + + +TEST_CASE_METHOD(ExecutorTest, "Executor_wrong_key") +{ + Context ctx; + + auto cmd = std::make_shared(); + Binding b {std::make_shared("a b"), cmd}; + + Executor exec; + exec.register_binding(b); + + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"c"}); + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"b"}); + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"a"}); + REQUIRE(0 == cmd->execute_count); + exec.update(ctx, KeyMod {"b"}); + REQUIRE(1 == cmd->execute_count); +} + + +TEST_CASE_METHOD(ExecutorTest, "Executor_two_commands") +{ + Context ctx; + + auto cmd0 = std::make_shared(); + Binding b0 {std::make_shared("C-a b"), cmd0}; + + auto cmd1 = std::make_shared(); + Binding b1 {std::make_shared("C-a c"), cmd1}; + + Executor exec; + exec.register_binding(b0); + exec.register_binding(b1); + + SECTION("execute first") + { + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"C-a"}); + + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"b"}); + + REQUIRE(1 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + } + + SECTION("execute second") + { + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"C-a"}); + + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"c"}); + + REQUIRE(0 == cmd0->execute_count); + REQUIRE(1 == cmd1->execute_count); + } + + SECTION("fail first, try but fail second") + { + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"C-a"}); + + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"d"}); + + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"b"}); + + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + } +} + +TEST_CASE_METHOD(ExecutorTest, "Executor_two_sequentials_commands") +{ + Context ctx; + + auto cmd0 = std::make_shared(); + Binding b0 {std::make_shared("C-a b"), cmd0}; + + auto cmd1 = std::make_shared(); + Binding b1 {std::make_shared("b c"), cmd1}; + + Executor exec; + exec.register_binding(b0); + exec.register_binding(b1); + + SECTION("execute sequentially") + { + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"C-a"}); + + REQUIRE(0 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"b"}); + + REQUIRE(1 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"c"}); + + REQUIRE(1 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"b"}); + + REQUIRE(1 == cmd0->execute_count); + REQUIRE(0 == cmd1->execute_count); + exec.update(ctx, KeyMod {"c"}); + + REQUIRE(1 == cmd0->execute_count); + REQUIRE(1 == cmd1->execute_count); + } +}