can now quit Pywiq with 'C-c C-c'.

main
bog 2023-10-07 22:21:43 +02:00
parent 041ef9b9ed
commit 7b6f0b45f4
26 changed files with 3878 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
*~*
*\#*
build
doc
.cache

2822
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
.PHONY: build tests
.PHONY: build tests doc
build:
meson setup build
@ -9,3 +9,6 @@ tests: build
install: tests
meson install -C build
doc:
doxygen

View File

@ -9,8 +9,36 @@ project('pywiq',
pwq_lib = static_library('pywiq',
sources: [
# Terminal
# --------
'src/Term.cpp',
# Control
# -------
'src/KeyMod.cpp',
'src/Mode.cpp',
'src/Action.cpp',
'src/Binding.cpp',
'src/Executor.cpp',
'src/Pywiq.cpp',
'src/Controller.cpp',
# View
# ----
# Command
# -------
# Scripting
# ---------
# Actions
# -------
'src/actions/Quit.cpp',
],
dependencies: [
dependency('ncursesw')
])
pwq_dep = declare_dependency(
@ -30,6 +58,8 @@ executable('pwq',
executable('pwq-tests',
sources: [
'tests/main.cpp',
'tests/KeyMod.cpp',
'tests/Executor.cpp',
],
dependencies: [
pwq_dep,

18
src/Action.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "Action.hpp"
#include "Mode.hpp"
namespace pwq
{
/*explicit*/ Action::Action(Mode& mode,
std::string const& name,
std::string const& help)
: m_mode { mode }
, m_name { name }
, m_help { help }
{
}
/*virtual*/ Action::~Action()
{
}
}

39
src/Action.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef pwq_ACTION_HPP
#define pwq_ACTION_HPP
#include "commons.hpp"
namespace pwq
{
class Mode;
/**
* A process that can be executed from a command or a keystroke.
**/
class Action
{
public:
explicit Action(Mode& mode,
std::string const& name,
std::string const& help);
virtual ~Action();
std::string name() const { return m_name; }
std::string help() const { return m_help; }
virtual void execute() {}
virtual void undo() {}
protected:
Mode& m_mode;
private:
std::string m_name;
std::string m_help;
};
}
#endif

15
src/Binding.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "Binding.hpp"
namespace pwq
{
/*explicit*/ Binding::Binding(std::vector<KeyMod> const& keymods,
std::weak_ptr<Action> action)
: m_keymods { keymods}
, m_action { action }
{
}
/*virtual*/ Binding::~Binding()
{
}
}

33
src/Binding.hpp Normal file
View File

@ -0,0 +1,33 @@
#ifndef pwq_BINDING_HPP
#define pwq_BINDING_HPP
#include "commons.hpp"
#include "KeyMod.hpp"
#include "Action.hpp"
namespace pwq
{
/**
* Represente the association of a sequence of keys and
* an action.
* @see KeyMod
* @see Action
**/
class Binding
{
public:
explicit Binding(std::vector<KeyMod> const& keymods,
std::weak_ptr<Action> action);
virtual ~Binding();
size_t size() const { return m_keymods.size(); }
std::vector<KeyMod> const& keymods() const { return m_keymods; }
std::weak_ptr<Action> action() const { return m_action; }
private:
std::vector<KeyMod> m_keymods;
std::weak_ptr<Action> m_action;
};
}
#endif

12
src/Controller.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "Controller.hpp"
namespace pwq
{
/*explicit*/ Controller::Controller()
{
}
/*virtual*/ Controller::~Controller()
{
}
}

25
src/Controller.hpp Normal file
View File

@ -0,0 +1,25 @@
#ifndef pwq_CONTROLLER_HPP
#define pwq_CONTROLLER_HPP
#include "commons.hpp"
#include "KeyMod.hpp"
namespace pwq
{
/**
* Read keys from input.
* @see KeyMod
**/
class Controller
{
public:
explicit Controller();
virtual ~Controller();
virtual KeyMod wait_keymod() = 0;
private:
};
}
#endif

55
src/Executor.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "Executor.hpp"
namespace pwq
{
/*explicit*/ Executor::Executor()
{
}
/*virtual*/ Executor::~Executor()
{
}
void Executor::bind(Binding const& binding)
{
m_entries.push_back({binding, 0});
}
void Executor::update(KeyMod const& keymod)
{
for (auto& entry: m_entries)
{
size_t progress = entry.progression;
if (entry.binding.keymods()[progress].equals(keymod))
{
entry.progression++;
if (entry.progression >= entry.binding.size())
{
entry.progression = 0;
if (auto action = entry.binding.action().lock();
action)
{
action->execute();
}
if (entry.binding.keymods()[0].equals(keymod))
{
entry.progression++;
}
}
}
else
{
entry.progression = 0;
if (entry.binding.keymods()[0].equals(keymod))
{
entry.progression++;
}
}
}
}
}

34
src/Executor.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef pwq_EXECUTOR_HPP
#define pwq_EXECUTOR_HPP
#include "commons.hpp"
#include "Binding.hpp"
namespace pwq
{
struct ExecEntry {
Binding binding;
size_t progression = 0;
};
/**
* Update key bindings and execute corresponding actions.
* @see Action
* @see Binding
* @see KeyMod
**/
class Executor
{
public:
explicit Executor();
virtual ~Executor();
void bind(Binding const& binding);
void update(KeyMod const& keymod);
private:
std::vector<ExecEntry> m_entries;
};
}
#endif

125
src/KeyMod.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "KeyMod.hpp"
namespace pwq
{
/*explicit*/ KeyMod::KeyMod(std::wstring const& repr)
{
std::wstring buffer;
std::unordered_map<std::wstring, ModType> mod_types = {
{L"C", PWQ_MOD_CTRL},
{L"A", PWQ_MOD_ALT}
};
for (char c: repr)
{
if (c == '-')
{
m_mods.push_back(mod_types.at(buffer));
buffer.clear();
}
else if (!std::isspace(c))
{
buffer.push_back(c);
}
}
if (buffer.size() > 1)
{
int value = 0;
for (auto name: KeyTypeStr)
{
std::string name_str = name;
std::wstring wname (std::begin(name_str), std::end(name_str));
if (wname == L"PWQ_KEY_" + buffer)
{
m_key = (KeyType) value;
break;
}
value++;
}
m_text = std::nullopt;
}
else
{
m_key = PWQ_KEY_TEXT;
m_text = buffer[0];
}
}
/*explicit*/ KeyMod::KeyMod(KeyType key)
: KeyMod(key, std::nullopt)
{
}
/*explicit*/ KeyMod::KeyMod(KeyType key, std::optional<wchar_t> text)
: m_key { key }
, m_text { text }
{
}
/*virtual*/ KeyMod::~KeyMod()
{
}
KeyMod& KeyMod::mod(ModType type)
{
m_mods.push_back(type);
return *this;
}
bool KeyMod::has_mod(ModType type) const
{
return std::find(std::begin(m_mods),
std::end(m_mods),
type)
!= std::end(m_mods);
}
bool KeyMod::equals(KeyMod const& rhs) const
{
for (auto mod: m_mods)
{
if (!rhs.has_mod(mod))
{
return false;
}
}
return m_key == rhs.m_key
&& m_text == rhs.m_text
&& m_mods.size() == rhs.m_mods.size();
}
std::wstring KeyMod::string() const
{
std::wstringstream ss;
if (has_mod(PWQ_MOD_CTRL))
{
ss << L"C-";
}
if (has_mod(PWQ_MOD_ALT))
{
ss << L"A-";
}
if (m_key == PWQ_KEY_TEXT)
{
ss << std::wstring(1, *m_text);
}
else
{
std::string name_str = KeyTypeStr[m_key] + strlen("PWQ_KEY_");
std::wstring name (std::begin(name_str), std::end(name_str));
ss << name;
}
return ss.str();
}
}

54
src/KeyMod.hpp Normal file
View File

@ -0,0 +1,54 @@
#ifndef pwq_KEYMOD_HPP
#define pwq_KEYMOD_HPP
#include "commons.hpp"
#define PWQ_KEY_TYPES(G) \
G(PWQ_KEY_TEXT), \
G(PWQ_KEY_UP), \
G(PWQ_KEY_DOWN), \
G(PWQ_KEY_LEFT), \
G(PWQ_KEY_RIGHT),
#define PWQ_MOD_TYPES(G) \
G(PWQ_MOD_CTRL), \
G(PWQ_MOD_ALT)
namespace pwq
{
PWQ_ENUM(KeyType, PWQ_KEY_TYPES);
PWQ_ENUM(ModType, PWQ_MOD_TYPES);
/**
* Represents the combination of a keystroke with a modifier key
* like control or alt.
*
**/
class KeyMod
{
public:
explicit KeyMod(std::wstring const& repr);
explicit KeyMod(KeyType key);
explicit KeyMod(KeyType key, std::optional<wchar_t> text);
virtual ~KeyMod();
KeyType key() const { return m_key; }
std::vector<ModType> const& mods() const { return m_mods; }
std::optional<wchar_t> text() const { return m_text; }
KeyMod& mod(ModType type);
bool has_mod(ModType type) const;
bool equals(KeyMod const& rhs) const;
std::wstring string() const;
private:
KeyType m_key;
std::optional<wchar_t> m_text;
std::vector<ModType> m_mods;
};
}
#endif

40
src/Mode.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "Mode.hpp"
#include "Action.hpp"
#include "Binding.hpp"
#include "Executor.hpp"
namespace pwq
{
/*explicit*/ Mode::Mode(std::string const& name,
Pywiq& pywiq,
std::shared_ptr<Executor> executor)
: m_name { name }
, m_pywiq { pywiq }
, m_executor { executor }
{
}
/*virtual*/ Mode::~Mode()
{
}
Pywiq& Mode::pywiq()
{
return m_pywiq;
}
std::weak_ptr<Executor> Mode::executor()
{
return m_executor;
}
void Mode::bind_action(std::vector<KeyMod> keys,
std::shared_ptr<Action> action)
{
m_actions.push_back(action);
Binding binding {keys, action};
m_executor->bind(binding);
}
}

50
src/Mode.hpp Normal file
View File

@ -0,0 +1,50 @@
#ifndef pwq_MODE_HPP
#define pwq_MODE_HPP
#include "commons.hpp"
#include "src/KeyMod.hpp"
namespace pwq
{
class Executor;
class Pywiq;
class Action;
/**
* A configuration with a specific view and controls.
* eg. Like Vi mode.
**/
class Mode
{
public:
explicit Mode(std::string const& name,
Pywiq& pywiq,
std::shared_ptr<Executor> executor);
virtual ~Mode();
Pywiq& pywiq();
std::weak_ptr<Executor> executor();
void bind_action(std::vector<KeyMod> keys,
std::shared_ptr<Action> action);
template <typename T>
std::shared_ptr<T> make_action();
private:
std::string m_name;
Pywiq& m_pywiq;
std::shared_ptr<Executor> m_executor;
std::vector<std::shared_ptr<Action>> m_actions;
};
template <typename T>
std::shared_ptr<T> Mode::make_action()
{
auto action = std::make_shared<T>(*this);
return action;
}
}
#endif

43
src/Pywiq.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "Pywiq.hpp"
#include "Controller.hpp"
#include "KeyMod.hpp"
#include "Mode.hpp"
#include "Executor.hpp"
namespace pwq
{
/*explicit*/ Pywiq::Pywiq(Controller& controller)
: m_controller { controller }
{
}
/*virtual*/ Pywiq::~Pywiq()
{
}
void Pywiq::add_mode(std::shared_ptr<Mode> mode)
{
m_modes.push_back(mode);
}
bool Pywiq::exec()
{
KeyMod keymod = m_controller.wait_keymod();
for (auto mode: m_modes)
{
if (auto executor=mode->executor().lock();
executor)
{
executor->update(keymod);
}
}
return m_running;
}
void Pywiq::quit()
{
m_running = false;
}
}

32
src/Pywiq.hpp Normal file
View File

@ -0,0 +1,32 @@
#ifndef pwq_PYWIQ_HPP
#define pwq_PYWIQ_HPP
#include "commons.hpp"
namespace pwq
{
class Mode;
class Controller;
/**
* Manage the editor modes.
**/
class Pywiq
{
public:
explicit Pywiq(Controller& controller);
virtual ~Pywiq();
void add_mode(std::shared_ptr<Mode> mode);
bool exec();
void quit();
private:
Controller& m_controller;
std::vector<std::shared_ptr<Mode>> m_modes;
bool m_running = true;
};
}
#endif

84
src/Term.cpp Normal file
View File

@ -0,0 +1,84 @@
#include "Term.hpp"
#include <curses.h>
namespace pwq
{
/*explicit*/ Term::Term()
{
setlocale(LC_ALL, "");
initscr();
raw();
noecho();
keypad(stdscr, TRUE);
start_color();
curs_set(0);
m_mapping = {
{"KEY_UP", PWQ_KEY_UP},
{"KEY_DOWN", PWQ_KEY_DOWN},
{"KEY_LEFT", PWQ_KEY_LEFT},
{"KEY_RIGHT", PWQ_KEY_RIGHT},
};
}
/*virtual*/ Term::~Term()
{
endwin();
}
KeyMod Term::wait_keymod() /*override*/
{
wint_t c;
get_wch(&c);
bool alt = false;
bool ctrl = false;
// Detect ALT
if (c == 27)
{
get_wch(&c);
alt = true;
}
// Detect CTRL
{
auto kname = keyname(c);
ctrl = kname[0] == '^';
if (ctrl)
{
c = std::tolower(kname[1]);
}
}
std::optional<KeyMod> km;
// Detect Key Type
{
auto kname = keyname(c);
if (auto itr=m_mapping.find(kname);
itr != std::end(m_mapping))
{
km = KeyMod(itr->second);
}
else if(strlen(kname) == 1)
{
km = KeyMod(PWQ_KEY_TEXT, c);
}
}
if (ctrl)
{
km->mod(PWQ_MOD_CTRL);
}
if (alt)
{
km->mod(PWQ_MOD_ALT);
}
return *km;
}
}

28
src/Term.hpp Normal file
View File

@ -0,0 +1,28 @@
#ifndef pwq_TERM_HPP
#define pwq_TERM_HPP
#include <ncurses.h>
#include "commons.hpp"
#include "Controller.hpp"
namespace pwq
{
/**
* Abstraction over NCurses.
* @see Controller
**/
class Term: public Controller
{
public:
explicit Term();
virtual ~Term();
KeyMod wait_keymod() override;
private:
std::unordered_map<std::string, KeyType> m_mapping;
};
}
#endif

23
src/actions/Quit.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "Quit.hpp"
#include "../Pywiq.hpp"
#include "../Mode.hpp"
namespace pwq
{
namespace actions
{
/*explicit*/ Quit::Quit(Mode& mode)
: Action(mode, "quit", "quit Pywiq")
{
}
/*virtual*/ Quit::~Quit()
{
}
void Quit::execute() /*override*/
{
m_mode.pywiq().quit();
}
}
}

24
src/actions/Quit.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef pwq_actions_QUIT_HPP
#define pwq_actions_QUIT_HPP
#include "../commons.hpp"
#include "../Action.hpp"
namespace pwq
{
namespace actions
{
class Quit: public Action
{
public:
explicit Quit(Mode& mode);
virtual ~Quit();
void execute() override;
private:
};
}
}
#endif

25
src/commons.hpp Normal file
View File

@ -0,0 +1,25 @@
#ifndef pwq_COMMONS_HPP
#define pwq_COMMONS_HPP
#include <iostream>
#include <memory>
#include <vector>
#include <optional>
#include <sstream>
#include <algorithm>
#include <cstring>
#include <unordered_map>
#define PWQ_GEN_ENUM(X) X
#define PWQ_GEN_STRING(X) #X
#define PWQ_ENUM(PREFIX, TYPES) \
enum PREFIX { TYPES(PWQ_GEN_ENUM) }; \
constexpr char const* PREFIX ## Str [] { TYPES(PWQ_GEN_STRING) }
#define PWQ_ERROR(NAME) \
struct NAME : public std::runtime_error { \
NAME (std::string const& what): std::runtime_error(what) {} \
}
#endif

View File

@ -1,6 +1,31 @@
#include <iostream>
#include "KeyMod.hpp"
#include "Pywiq.hpp"
#include "Mode.hpp"
#include "Executor.hpp"
#include "Term.hpp"
#include "actions/Quit.hpp"
int main(int, char**)
{
pwq::Term term;
pwq::Pywiq pywiq {term};
auto executor = std::make_shared<pwq::Executor>();
auto default_mode = std::make_shared<pwq::Mode>("default",
pywiq,
executor);
auto quit = default_mode->make_action<pwq::actions::Quit>();
default_mode->bind_action({pwq::KeyMod(L"C-c"),
pwq::KeyMod(L"C-c")}, quit);
pywiq.add_mode(default_mode);
while (pywiq.exec())
{
}
return 0;
}

136
tests/Executor.cpp Normal file
View File

@ -0,0 +1,136 @@
#include <catch2/catch.hpp>
#include "../src/Mode.hpp"
#include "../src/Action.hpp"
#include "../src/Binding.hpp"
#include "../src/Executor.hpp"
#include "../src/Controller.hpp"
#include "../src/Pywiq.hpp"
using namespace pwq;
struct MockController : public Controller {
virtual KeyMod wait_keymod() override { return KeyMod(L"a"); }
};
class ExecutorTest
{
public:
explicit ExecutorTest() {}
virtual ~ExecutorTest() {}
protected:
std::shared_ptr<Executor> exec;
MockController m_ctrl;
Pywiq m_pywiq = Pywiq(m_ctrl);
Mode m_mode = Mode("executor_test", m_pywiq, exec);
};
struct ActionMock: public Action
{
int execute_counter = 0;
ActionMock(Mode& mode)
: Action(mode, "mock_action", "mock_doc")
{
}
void execute() override
{
execute_counter++;
}
};
TEST_CASE_METHOD(ExecutorTest, "Executor_one_action")
{
auto action = std::make_shared<ActionMock>(m_mode);
Executor exec;
exec.bind(Binding(std::vector<KeyMod>{KeyMod(L"C-a"),
KeyMod(L"C-c")},
action));
REQUIRE(0 == action->execute_counter);
exec.update(KeyMod(L"C-a"));
REQUIRE(0 == action->execute_counter);
exec.update(KeyMod(L"C-c"));
REQUIRE(1 == action->execute_counter);
exec.update(KeyMod(L"C-c"));
REQUIRE(1 == action->execute_counter);
exec.update(KeyMod(L"C-a"));
REQUIRE(1 == action->execute_counter);
exec.update(KeyMod(L"C-c"));
REQUIRE(2 == action->execute_counter);
}
TEST_CASE_METHOD(ExecutorTest, "Executor_two_actions")
{
auto first_action = std::make_shared<ActionMock>(m_mode);
auto second_action = std::make_shared<ActionMock>(m_mode);
Executor exec;
exec.bind(Binding(std::vector<KeyMod>{KeyMod(L"A-g"),
KeyMod(L"p")},
first_action));
exec.bind(Binding(std::vector<KeyMod>{KeyMod(L"z"),
KeyMod(L"z")},
second_action));
REQUIRE(0 == first_action->execute_counter);
REQUIRE(0 == second_action->execute_counter);
exec.update(KeyMod(L"A-g"));
REQUIRE(0 == first_action->execute_counter);
REQUIRE(0 == second_action->execute_counter);
exec.update(KeyMod(L"z"));
REQUIRE(0 == first_action->execute_counter);
REQUIRE(0 == second_action->execute_counter);
exec.update(KeyMod(L"z"));
REQUIRE(0 == first_action->execute_counter);
REQUIRE(1 == second_action->execute_counter);
exec.update(KeyMod(L"p"));
REQUIRE(0 == first_action->execute_counter);
REQUIRE(1 == second_action->execute_counter);
exec.update(KeyMod(L"A-g"));
REQUIRE(0 == first_action->execute_counter);
REQUIRE(1 == second_action->execute_counter);
exec.update(KeyMod(L"p"));
REQUIRE(1 == first_action->execute_counter);
REQUIRE(1 == second_action->execute_counter);
}
TEST_CASE_METHOD(ExecutorTest, "Executor_failed_but_start_another")
{
auto action = std::make_shared<ActionMock>(m_mode);
Executor exec;
exec.bind(Binding(std::vector<KeyMod>{KeyMod(L"a"),
KeyMod(L"b"),
KeyMod(L"c")},
action));
REQUIRE(0 == action->execute_counter);
exec.update(KeyMod(L"a"));
REQUIRE(0 == action->execute_counter);
exec.update(KeyMod(L"a"));
REQUIRE(0 == action->execute_counter);
exec.update(KeyMod(L"b"));
REQUIRE(0 == action->execute_counter);
exec.update(KeyMod(L"c"));
REQUIRE(1 == action->execute_counter);
}

101
tests/KeyMod.cpp Normal file
View File

@ -0,0 +1,101 @@
#include <catch2/catch.hpp>
#include "../src/KeyMod.hpp"
class KeyModTest
{
public:
explicit KeyModTest() {}
virtual ~KeyModTest() {}
protected:
};
TEST_CASE_METHOD(KeyModTest, "KeyMod_to_string")
{
SECTION("text, no mod")
{
pwq::KeyMod km {pwq::PWQ_KEY_TEXT, 'h'};
REQUIRE(L"h" == km.string());
}
SECTION("text, ctrl mod")
{
auto km = pwq::KeyMod(pwq::PWQ_KEY_TEXT, 'h')
.mod(pwq::PWQ_MOD_CTRL);
REQUIRE(L"C-h" == km.string());
}
SECTION("text, alt mod")
{
auto km = pwq::KeyMod(pwq::PWQ_KEY_TEXT, 'h')
.mod(pwq::PWQ_MOD_ALT);
REQUIRE(L"A-h" == km.string());
}
SECTION("text, ctrl + alt mod")
{
auto km = pwq::KeyMod(pwq::PWQ_KEY_TEXT, 'h')
.mod(pwq::PWQ_MOD_ALT)
.mod(pwq::PWQ_MOD_CTRL);
REQUIRE(L"C-A-h" == km.string());
}
SECTION("no text, no mod")
{
pwq::KeyMod km {pwq::PWQ_KEY_UP};
REQUIRE(L"UP" == km.string());
}
SECTION("no text, ctrl mod")
{
auto km = pwq::KeyMod(pwq::PWQ_KEY_DOWN)
.mod(pwq::PWQ_MOD_CTRL);
REQUIRE(L"C-DOWN" == km.string());
}
SECTION("no text, alt mod")
{
auto km = pwq::KeyMod(pwq::PWQ_KEY_LEFT)
.mod(pwq::PWQ_MOD_ALT);
REQUIRE(L"A-LEFT" == km.string());
}
SECTION("no text, alt mod")
{
auto km = pwq::KeyMod(pwq::PWQ_KEY_RIGHT)
.mod(pwq::PWQ_MOD_CTRL)
.mod(pwq::PWQ_MOD_ALT);
REQUIRE(L"C-A-RIGHT" == km.string());
}
}
TEST_CASE_METHOD(KeyModTest, "KeyMod_from_string")
{
REQUIRE(L"w" == pwq::KeyMod(L"w").string());
REQUIRE(L"t" == pwq::KeyMod(L" t ").string());
REQUIRE(L"C-w" == pwq::KeyMod(L"C-w ").string());
REQUIRE(L"A-w" == pwq::KeyMod(L" A-w").string());
REQUIRE(L"C-A-w" == pwq::KeyMod(L"C-A-w").string());
REQUIRE(L"C-A-w" == pwq::KeyMod(L" A-C-w ").string());
REQUIRE(L"UP" == pwq::KeyMod(L" UP ").string());
REQUIRE(L"C-DOWN" == pwq::KeyMod(L"C-DOWN ").string());
REQUIRE(L"A-LEFT" == pwq::KeyMod(L" A-LEFT ").string());
REQUIRE(L"C-A-RIGHT" == pwq::KeyMod(L"A-C-RIGHT").string());
REQUIRE(pwq::PWQ_KEY_TEXT == pwq::KeyMod(L" U ").key());
REQUIRE(pwq::PWQ_KEY_UP == pwq::KeyMod(L" UP ").key());
REQUIRE(pwq::PWQ_KEY_DOWN == pwq::KeyMod(L" DOWN ").key());
REQUIRE(pwq::PWQ_KEY_LEFT == pwq::KeyMod(L" LEFT ").key());
REQUIRE(pwq::PWQ_KEY_RIGHT == pwq::KeyMod(L" RIGHT ").key());
}