Configurable key bindings
This commit is contained in:
parent
86b520006c
commit
6f66aa3c06
137
sdn.cpp
137
sdn.cpp
|
@ -26,6 +26,7 @@
|
||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
@ -293,6 +294,10 @@ struct entry {
|
||||||
enum action { ACTIONS(XX) ACTION_COUNT };
|
enum action { ACTIONS(XX) ACTION_COUNT };
|
||||||
#undef XX
|
#undef XX
|
||||||
|
|
||||||
|
#define XX(name) #name,
|
||||||
|
static const char *g_action_names[] = {ACTIONS(XX)};
|
||||||
|
#undef XX
|
||||||
|
|
||||||
static map<wint_t, action> g_normal_actions = {
|
static map<wint_t, action> g_normal_actions = {
|
||||||
{ALT | '\r', ACTION_CHOOSE_FULL}, {ALT | KEY (ENTER), ACTION_CHOOSE_FULL},
|
{ALT | '\r', ACTION_CHOOSE_FULL}, {ALT | KEY (ENTER), ACTION_CHOOSE_FULL},
|
||||||
{'\r', ACTION_CHOOSE}, {KEY (ENTER), ACTION_CHOOSE},
|
{'\r', ACTION_CHOOSE}, {KEY (ENTER), ACTION_CHOOSE},
|
||||||
|
@ -315,6 +320,9 @@ static map<wint_t, action> g_input_actions = {
|
||||||
{L'\r', ACTION_INPUT_CONFIRM}, {KEY (ENTER), ACTION_INPUT_CONFIRM},
|
{L'\r', ACTION_INPUT_CONFIRM}, {KEY (ENTER), ACTION_INPUT_CONFIRM},
|
||||||
{KEY (BACKSPACE), ACTION_INPUT_B_DELETE},
|
{KEY (BACKSPACE), ACTION_INPUT_B_DELETE},
|
||||||
};
|
};
|
||||||
|
static const map<string, map<wint_t, action>*> g_binding_contexts = {
|
||||||
|
{"normal", &g_normal_actions}, {"input", &g_input_actions},
|
||||||
|
};
|
||||||
|
|
||||||
#define LS(XX) XX(NORMAL, "no") XX(FILE, "fi") XX(RESET, "rs") \
|
#define LS(XX) XX(NORMAL, "no") XX(FILE, "fi") XX(RESET, "rs") \
|
||||||
XX(DIRECTORY, "di") XX(SYMLINK, "ln") XX(MULTIHARDLINK, "mh") \
|
XX(DIRECTORY, "di") XX(SYMLINK, "ln") XX(MULTIHARDLINK, "mh") \
|
||||||
|
@ -331,6 +339,14 @@ enum { LS(XX) LS_COUNT };
|
||||||
static const char *g_ls_colors[] = {LS(XX)};
|
static const char *g_ls_colors[] = {LS(XX)};
|
||||||
#undef XX
|
#undef XX
|
||||||
|
|
||||||
|
struct stringcaseless {
|
||||||
|
bool operator () (const string &a, const string &b) const {
|
||||||
|
const auto &c = locale::classic();
|
||||||
|
return lexicographical_compare (begin (a), end (a), begin (b), end (b),
|
||||||
|
[&](char m, char n) { return tolower (m, c) < tolower (n, c); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
string cwd; ///< Current working directory
|
string cwd; ///< Current working directory
|
||||||
string start_dir; ///< Starting directory
|
string start_dir; ///< Starting directory
|
||||||
|
@ -356,6 +372,8 @@ static struct {
|
||||||
map<string, chtype> ls_exts; ///< LS_COLORS file extensions
|
map<string, chtype> ls_exts; ///< LS_COLORS file extensions
|
||||||
bool ls_symlink_as_target; ///< ln=target in dircolors
|
bool ls_symlink_as_target; ///< ln=target in dircolors
|
||||||
|
|
||||||
|
map<string, wint_t, stringcaseless> key_names;
|
||||||
|
|
||||||
// Refreshed by reload():
|
// Refreshed by reload():
|
||||||
|
|
||||||
map<uid_t, string> unames; ///< User names by UID
|
map<uid_t, string> unames; ///< User names by UID
|
||||||
|
@ -834,14 +852,16 @@ fun load_ls_colors (vector<string> colors) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load_configuration () {
|
fun load_colors () {
|
||||||
auto config = xdg_config_find ("/" PROJECT_NAME "/look");
|
|
||||||
if (!config)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Bail out on dumb terminals, there's not much one can do about them
|
// Bail out on dumb terminals, there's not much one can do about them
|
||||||
if (!has_colors () || start_color () == ERR || use_default_colors () == ERR)
|
if (!has_colors () || start_color () == ERR || use_default_colors () == ERR)
|
||||||
return;
|
return;
|
||||||
|
if (const char *colors = getenv ("LS_COLORS"))
|
||||||
|
load_ls_colors (split (colors, ":"));
|
||||||
|
|
||||||
|
auto config = xdg_config_find ("/" PROJECT_NAME "/look");
|
||||||
|
if (!config)
|
||||||
|
return;
|
||||||
|
|
||||||
string line;
|
string line;
|
||||||
while (getline (*config, line)) {
|
while (getline (*config, line)) {
|
||||||
|
@ -853,9 +873,6 @@ fun load_configuration () {
|
||||||
if (name == g.attr_names[i])
|
if (name == g.attr_names[i])
|
||||||
g.attrs[i] = decode_attrs (tokens);
|
g.attrs[i] = decode_attrs (tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const char *colors = getenv ("LS_COLORS"))
|
|
||||||
load_ls_colors (split (colors, ":"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun read_key (wint_t &c) -> bool {
|
fun read_key (wint_t &c) -> bool {
|
||||||
|
@ -871,6 +888,104 @@ fun read_key (wint_t &c) -> bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun parse_key (const string &key_name) -> wint_t {
|
||||||
|
wint_t c{};
|
||||||
|
auto p = key_name.c_str ();
|
||||||
|
if (!strncmp (p, "M-", 2)) {
|
||||||
|
c |= ALT;
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strncmp (p, "C-", 2)) {
|
||||||
|
p += 2;
|
||||||
|
if (*p < 32) {
|
||||||
|
cerr << "bindings: invalid combination: " << key_name << endl;
|
||||||
|
return WEOF;
|
||||||
|
}
|
||||||
|
c |= CTRL *p;
|
||||||
|
p += 1;
|
||||||
|
} else if (g.key_names.count (p)) {
|
||||||
|
c |= g.key_names.at (p);
|
||||||
|
p += strlen (p);
|
||||||
|
} else {
|
||||||
|
wchar_t w; mbstate_t mb {};
|
||||||
|
auto len = strlen (p) + 1, res = mbrtowc (&w, p, len, &mb);
|
||||||
|
if (res == 0) {
|
||||||
|
cerr << "bindings: missing key name: " << key_name << endl;
|
||||||
|
return WEOF;
|
||||||
|
}
|
||||||
|
if (res == size_t (-1) || res == size_t (-2)) {
|
||||||
|
cerr << "bindings: invalid encoding: " << key_name << endl;
|
||||||
|
return WEOF;
|
||||||
|
}
|
||||||
|
c |= w;
|
||||||
|
p += res;
|
||||||
|
}
|
||||||
|
if (*p) {
|
||||||
|
cerr << "key name has unparsable trailing part: " << key_name << endl;
|
||||||
|
return WEOF;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load_bindings () {
|
||||||
|
g.key_names["space"] = ' ';
|
||||||
|
for (int kc = KEY_MIN; kc < KEY_MAX; kc++) {
|
||||||
|
const char *name = keyname (kc);
|
||||||
|
if (!name)
|
||||||
|
continue;
|
||||||
|
if (!strncmp (name, "KEY_", 4))
|
||||||
|
name += 4;
|
||||||
|
string filtered;
|
||||||
|
for (; *name; name++) {
|
||||||
|
if (*name != '(' && *name != ')')
|
||||||
|
filtered += *name;
|
||||||
|
}
|
||||||
|
g.key_names[filtered] = kc;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto config = xdg_config_find ("/" PROJECT_NAME "/bindings");
|
||||||
|
if (!config)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Stringization in the preprocessor is a bit limited, we want lisp-case
|
||||||
|
map<string, action> actions;
|
||||||
|
int a;
|
||||||
|
for (auto p : g_action_names) {
|
||||||
|
string name;
|
||||||
|
for (; *p; p++)
|
||||||
|
name += *p == '_' ? '-' : *p + 'a' - 'A';
|
||||||
|
actions[name] = action (a++);
|
||||||
|
}
|
||||||
|
|
||||||
|
string line;
|
||||||
|
while (getline (*config, line)) {
|
||||||
|
auto tokens = split (line, " ");
|
||||||
|
if (tokens.empty () || line.front () == '#')
|
||||||
|
continue;
|
||||||
|
if (tokens.size () < 3) {
|
||||||
|
cerr << "bindings: expected: context binding action";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto context = tokens[0], key_name = tokens[1], action = tokens[2];
|
||||||
|
auto m = g_binding_contexts.find (context);
|
||||||
|
if (m == g_binding_contexts.end ()) {
|
||||||
|
cerr << "bindings: invalid context: " << context << endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
wint_t c = parse_key (key_name);
|
||||||
|
if (c == WEOF)
|
||||||
|
continue;
|
||||||
|
auto i = actions.find (action);
|
||||||
|
if (i == actions.end ()) {
|
||||||
|
cerr << "bindings: invalid action: " << action << endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
(*m->second)[c] = i->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main (int argc, char *argv[]) {
|
int main (int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
|
@ -892,12 +1007,14 @@ int main (int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
locale::global (locale (""));
|
locale::global (locale (""));
|
||||||
|
load_bindings ();
|
||||||
|
|
||||||
if (!initscr () || cbreak () == ERR || noecho () == ERR || nonl () == ERR) {
|
if (!initscr () || cbreak () == ERR || noecho () == ERR || nonl () == ERR) {
|
||||||
cerr << "cannot initialize screen" << endl;
|
cerr << "cannot initialize screen" << endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
load_configuration ();
|
load_colors ();
|
||||||
reload ();
|
reload ();
|
||||||
g.start_dir = g.cwd;
|
g.start_dir = g.cwd;
|
||||||
update ();
|
update ();
|
||||||
|
@ -906,7 +1023,7 @@ int main (int argc, char *argv[]) {
|
||||||
// which would worsen start-up flickering
|
// which would worsen start-up flickering
|
||||||
if (halfdelay (1) == ERR || keypad (stdscr, TRUE) == ERR) {
|
if (halfdelay (1) == ERR || keypad (stdscr, TRUE) == ERR) {
|
||||||
endwin ();
|
endwin ();
|
||||||
cerr << "cannot initialize screen" << endl;
|
cerr << "cannot configure input" << endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue