✨ add command executor.
parent
83cf9af94e
commit
1d2394068d
|
@ -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,
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
}
|
||||
|
||||
#define TWQ_ASSERT(COND, MSG) \
|
||||
if (! COND) { \
|
||||
if (! (COND) ) { \
|
||||
std::cerr << MSG << std::endl; \
|
||||
abort(); \
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
#include "Binding.hpp"
|
||||
|
||||
namespace twq
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
/*explicit*/ Binding::Binding(std::shared_ptr<Shortcut> shortcut,
|
||||
std::shared_ptr<Command> command)
|
||||
: m_shortcut { shortcut }
|
||||
, m_command { command }
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ Binding::~Binding()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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> shortcut,
|
||||
std::shared_ptr<Command> command);
|
||||
virtual ~Binding();
|
||||
|
||||
std::weak_ptr<Shortcut> shortcut() const { return m_shortcut; }
|
||||
std::weak_ptr<Command> command() const { return m_command; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Shortcut> m_shortcut;
|
||||
std::shared_ptr<Command> m_command;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,15 @@
|
|||
#include "Command.hpp"
|
||||
|
||||
namespace twq
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
/*explicit*/ Command::Command()
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ Command::~Command()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,15 @@
|
|||
#include "Context.hpp"
|
||||
|
||||
namespace twq
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
/*explicit*/ Context::Context()
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ Context::~Context()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Entry> m_entries;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -10,6 +10,64 @@ namespace twq
|
|||
return KeyMod { key_type, mods, std::nullopt };
|
||||
}
|
||||
|
||||
/*explicit*/ KeyMod::KeyMod(std::string const& repr)
|
||||
{
|
||||
std::vector<ModType> 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<KEY_COUNT; i++)
|
||||
{
|
||||
std::string val = KeyTypeStr[i] + strlen("KEY_");
|
||||
|
||||
if (val == value)
|
||||
{
|
||||
m_key_type = (KeyType) i;
|
||||
m_mods = mods;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw invalid_keymod_error {"cannot convert '"
|
||||
+ repr
|
||||
+ "' to keymod."};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*explicit*/ KeyMod::KeyMod(char text,
|
||||
std::vector<ModType> 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<m_mods.size(); i++)
|
||||
{
|
||||
if (m_mods[i] != rhs.m_mods[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string KeyMod::string() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace twq
|
|||
{
|
||||
namespace core
|
||||
{
|
||||
TWQ_ERROR(invalid_keymod_error);
|
||||
|
||||
TWQ_ENUM(KeyType, KEY_TYPES);
|
||||
TWQ_ENUM(ModType, MOD_TYPES);
|
||||
|
||||
|
@ -29,8 +31,11 @@ namespace twq
|
|||
static KeyMod key(KeyType key_type,
|
||||
std::vector<ModType> const& mods={});
|
||||
|
||||
explicit KeyMod(std::string const& repr);
|
||||
|
||||
explicit KeyMod(char text, std::vector<ModType> const& mods={});
|
||||
|
||||
|
||||
explicit KeyMod(KeyType key_type,
|
||||
std::vector<ModType> const& mods,
|
||||
std::optional<char> 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();
|
||||
|
|
|
@ -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<ModType> 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<KEY_COUNT; i++)
|
||||
{
|
||||
std::string val = KeyTypeStr[i] + strlen("KEY_");
|
||||
|
||||
if (val == value)
|
||||
{
|
||||
return KeyMod {(KeyType) i, mods, std::nullopt};
|
||||
}
|
||||
}
|
||||
|
||||
throw invalid_shortcut_error {"cannot convert '"
|
||||
+ repr
|
||||
+ "' to shortcut."};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i=0; i<repr.size(); i++)
|
||||
{
|
||||
char c = repr[i];
|
||||
|
@ -72,7 +23,16 @@ namespace twq
|
|||
{
|
||||
if (!buffer.empty())
|
||||
{
|
||||
push(create_keymod());
|
||||
try
|
||||
{
|
||||
push(KeyMod {buffer});
|
||||
}
|
||||
catch (invalid_keymod_error const& err)
|
||||
{
|
||||
throw invalid_shortcut_error {"cannot convert '"
|
||||
+ repr
|
||||
+ "' to shortcut"};
|
||||
}
|
||||
}
|
||||
|
||||
mods.clear();
|
||||
|
@ -86,7 +46,16 @@ namespace twq
|
|||
|
||||
if (!buffer.empty())
|
||||
{
|
||||
push(create_keymod());
|
||||
try
|
||||
{
|
||||
push(KeyMod {buffer});
|
||||
}
|
||||
catch (invalid_keymod_error const& err)
|
||||
{
|
||||
throw invalid_shortcut_error {"cannot convert '"
|
||||
+ repr
|
||||
+ "' to shortcut"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,6 +63,15 @@ namespace twq
|
|||
{
|
||||
}
|
||||
|
||||
KeyMod Shortcut::get(size_t index) const
|
||||
{
|
||||
TWQ_ASSERT(index < count(), "cannot get shortcut at index '"
|
||||
+ std::to_string(index)
|
||||
+ "'");
|
||||
|
||||
return m_keymods[index];
|
||||
}
|
||||
|
||||
void Shortcut::push(KeyMod const& keymod)
|
||||
{
|
||||
m_keymods.push_back(keymod);
|
||||
|
|
|
@ -16,6 +16,9 @@ namespace twq
|
|||
explicit Shortcut(std::string const& repr);
|
||||
virtual ~Shortcut();
|
||||
|
||||
size_t count() const { return m_keymods.size(); }
|
||||
KeyMod get(size_t index) const;
|
||||
|
||||
void push(KeyMod const& keymod);
|
||||
std::string string() const;
|
||||
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
#include <catch2/catch.hpp>
|
||||
#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<CommandMock>();
|
||||
Binding b {std::make_shared<Shortcut>("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<CommandMock>();
|
||||
Binding b {std::make_shared<Shortcut>("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<CommandMock>();
|
||||
Binding b {std::make_shared<Shortcut>("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<CommandMock>();
|
||||
Binding b {std::make_shared<Shortcut>("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<CommandMock>();
|
||||
Binding b0 {std::make_shared<Shortcut>("C-a b"), cmd0};
|
||||
|
||||
auto cmd1 = std::make_shared<CommandMock>();
|
||||
Binding b1 {std::make_shared<Shortcut>("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<CommandMock>();
|
||||
Binding b0 {std::make_shared<Shortcut>("C-a b"), cmd0};
|
||||
|
||||
auto cmd1 = std::make_shared<CommandMock>();
|
||||
Binding b1 {std::make_shared<Shortcut>("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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue