diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6cca10d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.cache +*~* +*\#* +build diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d9a4e8a --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: build tests + +build: + meson setup build + meson compile -C build + +tests: build + build/twq-tests + +install: tests + meson install -C build diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..6ae1171 --- /dev/null +++ b/meson.build @@ -0,0 +1,41 @@ +project('tiwiq', + 'cpp', + version: '0.0.0', + default_options: [ + 'prefix=/usr', + 'warning_level=3', + 'cpp_std=c++17', + ]) + +twq_lib = static_library('tiwiq', + sources: [ + 'src/core/KeyMod.cpp', + 'src/core/Shortcut.cpp', + ], + dependencies: [ + dependency('ncursesw') + ]) + +twq_dep = declare_dependency( + link_with: twq_lib, + include_directories: ['src/'] +) + +executable('twq', + sources: [ + 'src/main.cpp' + ], + dependencies: [ + twq_dep + ], + install: true) + +executable('twq-tests', + sources: [ + 'tests/main.cpp', + 'tests/Shortcut.cpp', + ], + dependencies: [ + twq_dep, + dependency('catch2') + ]) diff --git a/src/commons.hpp b/src/commons.hpp new file mode 100644 index 0000000..ea1bc96 --- /dev/null +++ b/src/commons.hpp @@ -0,0 +1,33 @@ +#ifndef kwq_COMMONS_HPP +#define kwq_COMMONS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GEN_ENUM(X) X +#define GEN_STRING(X) #X + +#define TWQ_ENUM(PREFIX, TYPES) \ + enum PREFIX { TYPES(GEN_ENUM) }; \ + constexpr char const* PREFIX ## Str [] = { TYPES(GEN_STRING) } + +#define TWQ_ERROR(NAME) \ + struct NAME : public std::runtime_error { \ + NAME (std::string const& what): std::runtime_error {what} {} \ + } + +#define TWQ_ASSERT(COND, MSG) \ + if (! COND) { \ + std::cerr << MSG << std::endl; \ + abort(); \ + } + +#endif diff --git a/src/core/KeyMod.cpp b/src/core/KeyMod.cpp new file mode 100644 index 0000000..a85a24d --- /dev/null +++ b/src/core/KeyMod.cpp @@ -0,0 +1,67 @@ +#include "KeyMod.hpp" + +namespace twq +{ + namespace core + { + /*static*/ KeyMod KeyMod::key(KeyType key_type, + std::vector const& mods) + { + return KeyMod { key_type, mods, std::nullopt }; + } + + /*explicit*/ KeyMod::KeyMod(char text, + std::vector const& mods) + : m_key_type { KEY_TEXT } + , m_mods { mods } + , m_text { text } + { + } + + /*explicit*/ KeyMod::KeyMod(KeyType key_type, + std::vector const& mods, + std::optional text) + : m_key_type { key_type } + , m_mods { mods } + , m_text { text } + { + } + + /*virtual*/ KeyMod::~KeyMod() + { + } + + bool KeyMod::has_mod(ModType mod_type) const + { + return + std::end(m_mods) != + std::find(std::begin(m_mods), std::end(m_mods), mod_type); + } + + std::string KeyMod::string() const + { + std::stringstream ss; + + if (has_mod(MOD_LCTRL)) + { + ss << "C-"; + } + + if (has_mod(MOD_ALT)) + { + ss << "A-"; + } + + if (m_key_type == KEY_TEXT) + { + ss << *m_text; + } + else + { + ss << (KeyTypeStr[m_key_type] + strlen("KEY_")); + } + + return ss.str(); + } + } +} diff --git a/src/core/KeyMod.hpp b/src/core/KeyMod.hpp new file mode 100644 index 0000000..41d0e8f --- /dev/null +++ b/src/core/KeyMod.hpp @@ -0,0 +1,56 @@ +#ifndef twq_core_KEYMOD_HPP +#define twq_core_KEYMOD_HPP + +#include "../commons.hpp" + +#define KEY_TYPES(G) \ + G(KEY_TEXT), \ + G(KEY_UP), \ + G(KEY_DOWN), \ + G(KEY_LEFT), \ + G(KEY_RIGHT), \ + G(KEY_COUNT), + +#define MOD_TYPES(G)\ + G(MOD_LCTRL), \ + G(MOD_ALT), \ + G(MOD_COUNT), + +namespace twq +{ + namespace core + { + TWQ_ENUM(KeyType, KEY_TYPES); + TWQ_ENUM(ModType, MOD_TYPES); + + class KeyMod + { + public: + static KeyMod key(KeyType key_type, + std::vector const& mods={}); + + explicit KeyMod(char text, std::vector const& mods={}); + + explicit KeyMod(KeyType key_type, + std::vector const& mods, + std::optional text); + + KeyType key_type() const { return m_key_type; } + std::vector mods() const { return m_mods; } + std::optional text() const { return m_text; } + + bool has_mod(ModType mod_type) const; + + std::string string() const; + + virtual ~KeyMod(); + + private: + KeyType m_key_type; + std::vector m_mods; + std::optional m_text; + }; + } +} + +#endif diff --git a/src/core/Shortcut.cpp b/src/core/Shortcut.cpp new file mode 100644 index 0000000..fef9e31 --- /dev/null +++ b/src/core/Shortcut.cpp @@ -0,0 +1,116 @@ +#include "Shortcut.hpp" +#include "src/core/KeyMod.hpp" + +namespace twq +{ + namespace core + { + /*explicit*/ Shortcut::Shortcut() + { + } + + /*explicit*/ Shortcut::Shortcut(std::string const& repr) + { + 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 m_keymods; + }; + } +} + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a72903a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int, char**) +{ + return 0; +} diff --git a/tests/Shortcut.cpp b/tests/Shortcut.cpp new file mode 100644 index 0000000..48c5d78 --- /dev/null +++ b/tests/Shortcut.cpp @@ -0,0 +1,106 @@ +#include +#include "../src/core/Shortcut.hpp" + +using namespace twq::core; + +class ShortcutTest +{ +public: + explicit ShortcutTest() {} + virtual ~ShortcutTest() {} + + void test_to_string(std::string const& oracle, + std::vector keymods) + { + Shortcut sc; + + for (auto km: keymods) + { + sc.push(km); + } + + INFO("Expected '" << oracle << "' got '" << sc.string() << "'"); + REQUIRE(oracle == sc.string()); + } + + void test_from_string(std::string const& oracle, + std::string const& shortcut) + { + Shortcut sc { shortcut }; + INFO("Expected '" << oracle << "' got '" << sc.string() << "'"); + REQUIRE(oracle == sc.string()); + } + +protected: +}; + +TEST_CASE_METHOD(ShortcutTest, "Shortcut_string") +{ + test_to_string("f", {KeyMod {'f'}}); + + test_to_string("a b c", { + KeyMod {'a'}, + KeyMod {'b'}, + KeyMod {'c'}, + }); + + test_to_string("C-g", {KeyMod {'g', {MOD_LCTRL}}}); + test_to_string("A-g", {KeyMod {'g', {MOD_ALT}}}); + test_to_string("C-A-g", {KeyMod {'g', {MOD_LCTRL, MOD_ALT}}}); + + test_to_string("A-a C-A-b C-c d", { + KeyMod {'a', {MOD_ALT}}, + KeyMod {'b', {MOD_LCTRL, MOD_ALT}}, + KeyMod {'c', {MOD_LCTRL}}, + KeyMod {'d'}, + }); + + test_to_string("UP", { + KeyMod::key(KEY_UP) + }); + + test_to_string("DOWN", { + KeyMod::key(KEY_DOWN) + }); + + test_to_string("LEFT", { + KeyMod::key(KEY_LEFT) + }); + + test_to_string("RIGHT", { + KeyMod::key(KEY_RIGHT) + }); + + test_to_string("C-UP", { + KeyMod::key(KEY_UP, {MOD_LCTRL}) + }); + + test_to_string("A-LEFT", { + KeyMod::key(KEY_LEFT, {MOD_ALT}), + }); + + test_to_string("C-A-RIGHT", { + KeyMod::key(KEY_RIGHT, {MOD_LCTRL, MOD_ALT}), + }); + + test_from_string("", ""); + + test_from_string("a", "a"); + test_from_string("a", " a"); + test_from_string("a", "a "); + + test_from_string("C-a", "C-a"); + test_from_string("C-a", " C-a "); + test_from_string("A-a", "A-a"); + test_from_string("A-a", " A-a "); + + test_from_string("C-A-a", " A-C-a "); + test_from_string("C-A-a", " C-A-a "); + + test_from_string("C-A-a b C-c", " C-A-a b C-c"); + + test_from_string("C-A-LEFT RIGHT C-UP", + " C-A-LEFT RIGHT C-UP"); + + REQUIRE_THROWS_AS(Shortcut { "C-A-DUCK" }, invalid_shortcut_error); +} diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..4ed06df --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include