summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2017-01-11 10:26:57 +0100
committertomsmeding <tom.smeding@gmail.com>2017-01-11 10:26:57 +0100
commit7ee15dee3eb9b8d0fc49206b9fd9a56e84bd0a1a (patch)
tree471956f76f34a2ed98720efe312c9ac0e6ae86ac
parent6d6f45c1e266ffb3206498301a7a6a1b1b22ec3c (diff)
Multi-key keybindings
-rw-r--r--Makefile1
-rw-r--r--buffer.cpp1
-rw-r--r--command.h1
-rw-r--r--config.cpp86
-rw-r--r--config.h33
-rw-r--r--either.h43
-rw-r--r--manager.cpp11
7 files changed, 158 insertions, 18 deletions
diff --git a/Makefile b/Makefile
index 1db70d1..aff6045 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@ all: $(TARGET)
clean:
rm -f *.o $(TARGET)
+ rm -rf *.dSYM
remake: clean
make all
diff --git a/buffer.cpp b/buffer.cpp
index afb1c93..05bbffa 100644
--- a/buffer.cpp
+++ b/buffer.cpp
@@ -63,6 +63,7 @@ void Buffer::handleCommand(const Command &cmd){
cursor.x=tb.lineLen(cursor.line);
tb.erase(cursor.line,cursor.x);
}
+ relayoutScreen();
if(findCursorInScreen().x==-1){
scrollUp();
} else {
diff --git a/command.h b/command.h
index 5ee7d24..f0b708d 100644
--- a/command.h
+++ b/command.h
@@ -21,6 +21,7 @@ public:
template <typename... A>
Command(string cmd,A... a)
:cmd(cmd),args{a...}{}
+ Command(const Command &other) = default;
const string& command() const;
const vector<string>& arguments() const;
diff --git a/config.cpp b/config.cpp
index 76cf3a2..8bd86f7 100644
--- a/config.cpp
+++ b/config.cpp
@@ -1,25 +1,87 @@
+#include <cassert>
#include <termio.h>
#include "command.h"
#include "config.h"
+#include "throw.h"
using namespace std;
-Keybindings global_keybindings={
- {KEY_CTRL+'Q',{"quit_app"}},
- {KEY_BACKSPACE,{"delete_backward"}},
- {KEY_DELETE,{"delete_forward"}},
- {'\n',{"insert_newline"}},
- {'\t',{"insert_char","\t"}},
- {KEY_RIGHT,{"move_forward"}},
- {KEY_LEFT,{"move_backward"}},
- {KEY_DOWN,{"move_downward"}},
- {KEY_UP,{"move_upward"}},
-};
+KeyInputMachine global_keyinput({
+ {{KEY_CTRL+'X',KEY_CTRL+'Q'},{"quit_app"}},
+ {{KEY_BACKSPACE},{"delete_backward"}},
+ {{KEY_DELETE},{"delete_forward"}},
+ {{'\n'},{"insert_newline"}},
+ {{'\t'},{"insert_char","\t"}},
+ {{KEY_RIGHT},{"move_forward"}},
+ {{KEY_LEFT},{"move_backward"}},
+ {{KEY_DOWN},{"move_downward"}},
+ {{KEY_UP},{"move_upward"}},
+});
class Init{public: Init(){
for(int i=32;i<127;i++){
- global_keybindings.emplace(i,Command("insert_char",string(1,(char)i)));
+ global_keyinput.insert({i},Command("insert_char",string(1,(char)i)));
}
}} init_object;
+
+
+KeyInputMachine::TreeNode::~TreeNode(){
+ if(cmd)delete cmd;
+ if(m){
+ for(const pair<int,TreeNode*> &p : *m){
+ if(p.second!=nullptr)delete p.second;
+ }
+ }
+}
+
+KeyInputMachine::KeyInputMachine(){}
+
+KeyInputMachine::KeyInputMachine(const vector<pair<vector<int>,Command>> &list){
+ for(const pair<vector<int>,Command> &p : list){
+ insert(p.first,p.second);
+ }
+}
+
+void KeyInputMachine::insert(const vector<int> &keys,const Command &cmd){
+ assert(keys.size()>0);
+ TreeNode *node=&root;
+ for(int key : keys){
+ if(node->m==nullptr){
+ if(node->cmd){
+ THROW("Ambiguous keybinding!");
+ }
+ node->m=new unordered_map<int,TreeNode*>;
+ }
+ TreeNode *newnode=new TreeNode;
+ (*node->m)[key]=newnode;
+ node=newnode;
+ }
+ if(node->cmd!=nullptr)delete node->cmd;
+ node->cmd=new Command(cmd);
+}
+
+Either<bool,Command> KeyInputMachine::input(int key){
+ if(current->m==nullptr){
+ current=&root;
+ return {Left(),false};
+ }
+ auto it=current->m->find(key);
+ if(it==current->m->end()){
+ current=&root;
+ return {Left(),false};
+ }
+ current=it->second;
+ if(current->cmd!=nullptr){
+ Command *cmd=current->cmd;
+ current=&root;
+ return {Right(),*cmd};
+ } else {
+ return {Left(),true};
+ }
+}
+
+void KeyInputMachine::cancel(){
+ current=&root;
+}
diff --git a/config.h b/config.h
index 0f9ab5d..f9de97a 100644
--- a/config.h
+++ b/config.h
@@ -1,12 +1,41 @@
#pragma once
#include <unordered_map>
+#include <vector>
#include "command.h"
+#include "either.h"
+
using namespace std;
-using Keybindings = unordered_map<int,Command>;
+class KeyInputMachine{
+ struct TreeNode{
+ Command *cmd=nullptr;
+ unordered_map<int,TreeNode*> *m=nullptr;
+
+ ~TreeNode();
+ };
+
+ TreeNode root;
+ TreeNode *current=&root;
+
+public:
+ KeyInputMachine();
+ KeyInputMachine(const vector<pair<vector<int>,Command>> &list);
+ KeyInputMachine(const KeyInputMachine&) = delete;
+
+ //Overwrites if already existent; ambiguous-prefix bindings not permitted
+ //at the moment
+ void insert(const vector<int> &keys,const Command &cmd);
+
+ //If Left, then true indicates 'still possibilities'; false indicates 'no more
+ //possible sequences left, error'
+ Either<bool,Command> input(int key);
+
+ //Cancel the current input combination
+ void cancel();
+};
-extern Keybindings global_keybindings;
+extern KeyInputMachine global_keyinput;
diff --git a/either.h b/either.h
new file mode 100644
index 0000000..33808a0
--- /dev/null
+++ b/either.h
@@ -0,0 +1,43 @@
+#pragma once
+
+struct Left{};
+struct Right{};
+
+template <typename T,typename U>
+class Either{
+ T *left;
+ U *right;
+
+public:
+ Either(Left,const T &l)
+ :left(new T(l)),right(nullptr){}
+
+ Either(Right,const U &r)
+ :left(nullptr),right(new U(r)){}
+
+ Either(const Either<T,U> &other){
+ if(other.left)left=new T(*other.left);
+ if(other.right)right=new U(*other.right);
+ }
+
+ ~Either(void){
+ if(left)delete left;
+ if(right)delete right;
+ }
+
+ T fromLeft(void) const {
+ return *left;
+ }
+
+ U fromRight(void) const {
+ return *right;
+ }
+
+ bool isLeft(void) const {
+ return (bool)left;
+ }
+
+ bool isRight(void) const {
+ return (bool)right;
+ }
+};
diff --git a/manager.cpp b/manager.cpp
index 7f9fa89..951d84b 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -137,11 +137,14 @@ int Manager::io(){
int key=tgetkey();
- auto it=global_keybindings.find(key);
- if(it!=global_keybindings.end()){
- receive(it->second);
+ Either<bool,Command> ret=global_keyinput.input(key);
+ if(ret.isLeft()){
+ if(!ret.fromLeft()){
+ bel();
+ receive({"error","Unbound key sequence"});
+ }
} else {
- receive({"error","Unbound key: "+to_string(key)});
+ receive(ret.fromRight());
}
if(should_quit)break;
}