From 4bb9b11fed7091ed6a3e0219181526fc0a84c737 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Mon, 26 Jun 2017 07:28:42 +0200
Subject: Customizable key bindings
---
nncmpp.c | 274 +++++++++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 186 insertions(+), 88 deletions(-)
diff --git a/nncmpp.c b/nncmpp.c
index 4f6dd31..a6b7085 100644
--- a/nncmpp.c
+++ b/nncmpp.c
@@ -1612,6 +1612,23 @@ g_actions[] =
#undef XX
};
+/// Accept a more human format of action-name instead of ACTION_NAME
+static int action_toupper (int c) { return c == '-' ? '_' : toupper_ascii (c); }
+
+static int
+action_resolve (const char *name)
+{
+ const unsigned char *s = (const unsigned char *) name;
+ for (int i = 0; i < ACTION_COUNT; i++)
+ {
+ const char *target = g_actions[i].name;
+ for (size_t k = 0; action_toupper (s[k]) == target[k]; k++)
+ if (!s[k] && !target[k])
+ return i;
+ }
+ return -1;
+}
+
// --- Line editor -------------------------------------------------------------
// TODO: move the editor out as a component to liberty-tui.c
@@ -2068,111 +2085,175 @@ app_process_mouse (termo_mouse_event_t type, int line, int column, int button,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct binding
+{
+ termo_key_t decoded; ///< Decoded key definition
+ enum action action; ///< Action to take
+ int order; ///< Order for stable sorting
+}
+*g_normal_bindings, *g_editor_bindings;
+static size_t g_normal_bindings_len, g_editor_bindings_len;
+
+static struct binding_default
{
const char *key; ///< Key definition
enum action action; ///< Action to take
- termo_key_t decoded; ///< Decoded key definition
}
-g_default_bindings[] =
-{
- { "Escape", ACTION_QUIT, {}},
- { "q", ACTION_QUIT, {}},
- { "C-l", ACTION_REDRAW, {}},
- { "M-Tab", ACTION_LAST_TAB, {}},
- { "F1", ACTION_HELP_TAB, {}},
-
- { "Home", ACTION_GOTO_TOP, {}},
- { "End", ACTION_GOTO_BOTTOM, {}},
- { "M-<", ACTION_GOTO_TOP, {}},
- { "M->", ACTION_GOTO_BOTTOM, {}},
- { "S-Up", ACTION_MOVE_UP, {}},
- { "S-Down", ACTION_MOVE_DOWN, {}},
- { "Up", ACTION_GOTO_ITEM_PREVIOUS, {}},
- { "Down", ACTION_GOTO_ITEM_NEXT, {}},
- { "k", ACTION_GOTO_ITEM_PREVIOUS, {}},
- { "j", ACTION_GOTO_ITEM_NEXT, {}},
- { "PageUp", ACTION_GOTO_PAGE_PREVIOUS, {}},
- { "PageDown", ACTION_GOTO_PAGE_NEXT, {}},
- { "C-p", ACTION_GOTO_ITEM_PREVIOUS, {}},
- { "C-n", ACTION_GOTO_ITEM_NEXT, {}},
- { "C-b", ACTION_GOTO_PAGE_PREVIOUS, {}},
- { "C-f", ACTION_GOTO_PAGE_NEXT, {}},
-
- { "H", ACTION_GOTO_VIEW_TOP, {}},
- { "M", ACTION_GOTO_VIEW_CENTER, {}},
- { "L", ACTION_GOTO_VIEW_BOTTOM, {}},
+g_normal_defaults[] =
+{
+ { "Escape", ACTION_QUIT },
+ { "q", ACTION_QUIT },
+ { "C-l", ACTION_REDRAW },
+ { "M-Tab", ACTION_LAST_TAB },
+ { "F1", ACTION_HELP_TAB },
+
+ { "Home", ACTION_GOTO_TOP },
+ { "End", ACTION_GOTO_BOTTOM },
+ { "M-<", ACTION_GOTO_TOP },
+ { "M->", ACTION_GOTO_BOTTOM },
+ { "S-Up", ACTION_MOVE_UP },
+ { "S-Down", ACTION_MOVE_DOWN },
+ { "Up", ACTION_GOTO_ITEM_PREVIOUS },
+ { "Down", ACTION_GOTO_ITEM_NEXT },
+ { "k", ACTION_GOTO_ITEM_PREVIOUS },
+ { "j", ACTION_GOTO_ITEM_NEXT },
+ { "PageUp", ACTION_GOTO_PAGE_PREVIOUS },
+ { "PageDown", ACTION_GOTO_PAGE_NEXT },
+ { "C-p", ACTION_GOTO_ITEM_PREVIOUS },
+ { "C-n", ACTION_GOTO_ITEM_NEXT },
+ { "C-b", ACTION_GOTO_PAGE_PREVIOUS },
+ { "C-f", ACTION_GOTO_PAGE_NEXT },
+
+ { "H", ACTION_GOTO_VIEW_TOP },
+ { "M", ACTION_GOTO_VIEW_CENTER },
+ { "L", ACTION_GOTO_VIEW_BOTTOM },
// Not sure how to set these up, they're pretty arbitrary so far
- { "Enter", ACTION_CHOOSE, {}},
- { "Delete", ACTION_DELETE, {}},
- { "Backspace", ACTION_UP, {}},
- { "a", ACTION_MPD_ADD, {}},
- { "r", ACTION_MPD_REPLACE, {}},
- { ":", ACTION_MPD_COMMAND, {}},
-
- { "Left", ACTION_MPD_PREVIOUS, {}},
- { "Right", ACTION_MPD_NEXT, {}},
- { "M-Left", ACTION_MPD_BACKWARD, {}},
- { "M-Right", ACTION_MPD_FORWARD, {}},
- { "h", ACTION_MPD_PREVIOUS, {}},
- { "l", ACTION_MPD_NEXT, {}},
- { "Space", ACTION_MPD_TOGGLE, {}},
- { "C-Space", ACTION_MPD_STOP, {}},
- { "u", ACTION_MPD_UPDATE_DB, {}},
- { "M-PageUp", ACTION_MPD_VOLUME_UP, {}},
- { "M-PageDown", ACTION_MPD_VOLUME_DOWN, {}},
+ { "Enter", ACTION_CHOOSE },
+ { "Delete", ACTION_DELETE },
+ { "Backspace", ACTION_UP },
+ { "a", ACTION_MPD_ADD },
+ { "r", ACTION_MPD_REPLACE },
+ { ":", ACTION_MPD_COMMAND },
+
+ { "Left", ACTION_MPD_PREVIOUS },
+ { "Right", ACTION_MPD_NEXT },
+ { "M-Left", ACTION_MPD_BACKWARD },
+ { "M-Right", ACTION_MPD_FORWARD },
+ { "h", ACTION_MPD_PREVIOUS },
+ { "l", ACTION_MPD_NEXT },
+ { "Space", ACTION_MPD_TOGGLE },
+ { "C-Space", ACTION_MPD_STOP },
+ { "u", ACTION_MPD_UPDATE_DB },
+ { "M-PageUp", ACTION_MPD_VOLUME_UP },
+ { "M-PageDown", ACTION_MPD_VOLUME_DOWN },
},
-g_editor_bindings[] =
-{
- { "Left", ACTION_EDITOR_B_CHAR, {}},
- { "Right", ACTION_EDITOR_F_CHAR, {}},
- { "C-b", ACTION_EDITOR_B_CHAR, {}},
- { "C-f", ACTION_EDITOR_F_CHAR, {}},
- { "M-b", ACTION_EDITOR_B_WORD, {}},
- { "M-f", ACTION_EDITOR_F_WORD, {}},
- { "Home", ACTION_EDITOR_HOME, {}},
- { "End", ACTION_EDITOR_END, {}},
- { "C-a", ACTION_EDITOR_HOME, {}},
- { "C-e", ACTION_EDITOR_END, {}},
-
- { "C-h", ACTION_EDITOR_B_DELETE, {}},
- { "DEL", ACTION_EDITOR_B_DELETE, {}},
- { "Backspace", ACTION_EDITOR_B_DELETE, {}},
- { "C-d", ACTION_EDITOR_F_DELETE, {}},
- { "Delete", ACTION_EDITOR_F_DELETE, {}},
- { "C-u", ACTION_EDITOR_B_KILL_LINE, {}},
- { "C-k", ACTION_EDITOR_F_KILL_LINE, {}},
- { "C-w", ACTION_EDITOR_B_KILL_WORD, {}},
-
- { "C-g", ACTION_QUIT, {}},
- { "Escape", ACTION_QUIT, {}},
- { "Enter", ACTION_EDITOR_CONFIRM, {}},
+g_editor_defaults[] =
+{
+ { "Left", ACTION_EDITOR_B_CHAR },
+ { "Right", ACTION_EDITOR_F_CHAR },
+ { "C-b", ACTION_EDITOR_B_CHAR },
+ { "C-f", ACTION_EDITOR_F_CHAR },
+ { "M-b", ACTION_EDITOR_B_WORD },
+ { "M-f", ACTION_EDITOR_F_WORD },
+ { "Home", ACTION_EDITOR_HOME },
+ { "End", ACTION_EDITOR_END },
+ { "C-a", ACTION_EDITOR_HOME },
+ { "C-e", ACTION_EDITOR_END },
+
+ { "C-h", ACTION_EDITOR_B_DELETE },
+ { "DEL", ACTION_EDITOR_B_DELETE },
+ { "Backspace", ACTION_EDITOR_B_DELETE },
+ { "C-d", ACTION_EDITOR_F_DELETE },
+ { "Delete", ACTION_EDITOR_F_DELETE },
+ { "C-u", ACTION_EDITOR_B_KILL_LINE },
+ { "C-k", ACTION_EDITOR_F_KILL_LINE },
+ { "C-w", ACTION_EDITOR_B_KILL_WORD },
+
+ { "C-g", ACTION_QUIT },
+ { "Escape", ACTION_QUIT },
+ { "Enter", ACTION_EDITOR_CONFIRM },
};
static int
app_binding_cmp (const void *a, const void *b)
{
- return termo_keycmp (g.tk,
- &((struct binding *) a)->decoded, &((struct binding *) b)->decoded);
+ const struct binding *aa = a, *bb = b;
+ int cmp = termo_keycmp (g.tk, &aa->decoded, &bb->decoded);
+ return cmp ? cmp : bb->order - aa->order;
+}
+
+static bool
+app_next_binding (struct str_map_iter *iter, termo_key_t *key, int *action)
+{
+ struct config_item *v;
+ while ((v = str_map_iter_next (iter)))
+ {
+ *action = ACTION_NONE;
+ if (*termo_strpkey_utf8 (g.tk,
+ iter->link->key, key, TERMO_FORMAT_ALTISMETA))
+ print_error ("%s: invalid binding", iter->link->key);
+ else if (v->type == CONFIG_ITEM_NULL)
+ return true;
+ else if (v->type != CONFIG_ITEM_STRING)
+ print_error ("%s: bindings must be strings", iter->link->key);
+ else if ((*action = action_resolve (v->value.string.str)) >= 0)
+ return true;
+ else
+ print_error ("%s: unknown action: %s",
+ iter->link->key, v->value.string.str);
+ }
+ return false;
}
static void
-app_init_bindings (struct binding *bindings, size_t len)
+app_init_bindings (const char *keymap,
+ struct binding_default *defaults, size_t defaults_len,
+ struct binding **result, size_t *result_len)
{
- for (size_t i = 0; i < len; i++)
+ ARRAY (struct binding, a)
+ ARRAY_INIT_SIZED (a, defaults_len);
+
+ termo_key_t decoded;
+ for (size_t i = 0; i < defaults_len; i++)
+ {
hard_assert (!*termo_strpkey_utf8 (g.tk,
- bindings[i].key, &bindings[i].decoded, TERMO_FORMAT_ALTISMETA));
- qsort (bindings, len, sizeof *bindings, app_binding_cmp);
+ defaults[i].key, &decoded, TERMO_FORMAT_ALTISMETA));
+ a[a_len++] = (struct binding) { decoded, defaults[i].action, a_len };
+ }
+
+ struct config_item *root = config_item_get (g.config.root, keymap, NULL);
+ if (root && root->type == CONFIG_ITEM_OBJECT)
+ {
+ struct str_map_iter iter = str_map_iter_make (&root->value.object);
+ ARRAY_RESERVE (a, iter.map->len);
+
+ int action;
+ while (app_next_binding (&iter, &decoded, &action))
+ a[a_len++] = (struct binding) { decoded, action, a_len };
+ }
+
+ // Use the helper field to use the last mappings of identical bindings
+ size_t out = 0;
+ qsort (a, a_len, sizeof *a, app_binding_cmp);
+ for (size_t in = 0; in < a_len; in++)
+ {
+ a[in].order = 0;
+ if (!out || termo_keycmp (g.tk, &a[in].decoded, &a[out - 1].decoded))
+ a[out++] = a[in];
+ }
+
+ *result = a;
+ *result_len = out;
}
static bool
app_process_termo_event (termo_key_t *event)
{
- struct binding dummy = { NULL, 0, *event }, *binding;
+ struct binding dummy = { *event, 0, 0 }, *binding;
if (g.editor_line)
{
if ((binding = bsearch (&dummy, g_editor_bindings,
- N_ELEMENTS (g_editor_bindings), sizeof *binding, app_binding_cmp)))
+ g_editor_bindings_len, sizeof *binding, app_binding_cmp)))
return app_editor_process_action (binding->action);
if (event->type != TERMO_TYPE_KEY || event->modifiers != 0)
return false;
@@ -2181,8 +2262,8 @@ app_process_termo_event (termo_key_t *event)
app_invalidate ();
return true;
}
- if ((binding = bsearch (&dummy, g_default_bindings,
- N_ELEMENTS (g_default_bindings), sizeof *binding, app_binding_cmp)))
+ if ((binding = bsearch (&dummy, g_normal_bindings,
+ g_normal_bindings_len, sizeof *binding, app_binding_cmp)))
return app_process_action (binding->action);
// TODO: parametrize actions, put this among other bindings
@@ -2956,10 +3037,23 @@ help_tab_on_item_draw (size_t item_index, struct row_buffer *buffer, int width)
(void) width;
// TODO: group them the other way around for clarity
- hard_assert (item_index < N_ELEMENTS (g_default_bindings));
- struct binding *binding = &g_default_bindings[item_index];
+ // - go through 0..ACTION_COUNT
+ // - ...
+
+ hard_assert (item_index < g_normal_bindings_len);
+ struct binding *binding = &g_normal_bindings[item_index];
+
+ // For display purposes, this is highly desirable
+ int flags = termo_get_flags (g.tk);
+ termo_set_flags (g.tk, flags | TERMO_FLAG_SPACESYMBOL);
+ termo_key_t key = binding->decoded;
+ termo_canonicalise (g.tk, &key);
+ termo_set_flags (g.tk, flags);
+
+ char buf[16];
+ termo_strfkey_utf8 (g.tk, buf, sizeof buf, &key, TERMO_FORMAT_ALTISMETA);
char *text = xstrdup_printf ("%-12s %s",
- binding->key, g_actions[binding->action].description);
+ buf, g_actions[binding->action].description);
row_buffer_append (buffer, text, 0);
free (text);
}
@@ -2970,7 +3064,7 @@ help_tab_init (void)
static struct tab super;
tab_init (&super, "Help");
super.on_item_draw = help_tab_on_item_draw;
- super.item_count = N_ELEMENTS (g_default_bindings);
+ super.item_count = g_normal_bindings_len;
return &super;
}
@@ -3633,8 +3727,12 @@ main (int argc, char *argv[])
signals_setup_handlers ();
app_init_poller_events ();
- app_init_bindings (g_default_bindings, N_ELEMENTS (g_default_bindings));
- app_init_bindings (g_editor_bindings, N_ELEMENTS (g_editor_bindings));
+ app_init_bindings ("normal",
+ g_normal_defaults, N_ELEMENTS (g_normal_defaults),
+ &g_normal_bindings, &g_normal_bindings_len);
+ app_init_bindings ("editor",
+ g_editor_defaults, N_ELEMENTS (g_editor_defaults),
+ &g_editor_bindings, &g_editor_bindings_len);
if (g_debug_mode)
app_prepend_tab (debug_tab_init ());
--
cgit v1.2.3-70-g09d2