Customizable key bindings

This commit is contained in:
Přemysl Eric Janouch 2017-06-26 07:28:42 +02:00
parent 2375e0dd3e
commit 4bb9b11fed
Signed by: p
GPG Key ID: B715679E3A361BE6
1 changed files with 179 additions and 81 deletions

260
nncmpp.c
View File

@ -1612,6 +1612,23 @@ g_actions[] =
#undef XX #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 ------------------------------------------------------------- // --- Line editor -------------------------------------------------------------
// TODO: move the editor out as a component to liberty-tui.c // 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 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 const char *key; ///< Key definition
enum action action; ///< Action to take enum action action; ///< Action to take
termo_key_t decoded; ///< Decoded key definition
} }
g_default_bindings[] = g_normal_defaults[] =
{ {
{ "Escape", ACTION_QUIT, {}}, { "Escape", ACTION_QUIT },
{ "q", ACTION_QUIT, {}}, { "q", ACTION_QUIT },
{ "C-l", ACTION_REDRAW, {}}, { "C-l", ACTION_REDRAW },
{ "M-Tab", ACTION_LAST_TAB, {}}, { "M-Tab", ACTION_LAST_TAB },
{ "F1", ACTION_HELP_TAB, {}}, { "F1", ACTION_HELP_TAB },
{ "Home", ACTION_GOTO_TOP, {}}, { "Home", ACTION_GOTO_TOP },
{ "End", ACTION_GOTO_BOTTOM, {}}, { "End", ACTION_GOTO_BOTTOM },
{ "M-<", ACTION_GOTO_TOP, {}}, { "M-<", ACTION_GOTO_TOP },
{ "M->", ACTION_GOTO_BOTTOM, {}}, { "M->", ACTION_GOTO_BOTTOM },
{ "S-Up", ACTION_MOVE_UP, {}}, { "S-Up", ACTION_MOVE_UP },
{ "S-Down", ACTION_MOVE_DOWN, {}}, { "S-Down", ACTION_MOVE_DOWN },
{ "Up", ACTION_GOTO_ITEM_PREVIOUS, {}}, { "Up", ACTION_GOTO_ITEM_PREVIOUS },
{ "Down", ACTION_GOTO_ITEM_NEXT, {}}, { "Down", ACTION_GOTO_ITEM_NEXT },
{ "k", ACTION_GOTO_ITEM_PREVIOUS, {}}, { "k", ACTION_GOTO_ITEM_PREVIOUS },
{ "j", ACTION_GOTO_ITEM_NEXT, {}}, { "j", ACTION_GOTO_ITEM_NEXT },
{ "PageUp", ACTION_GOTO_PAGE_PREVIOUS, {}}, { "PageUp", ACTION_GOTO_PAGE_PREVIOUS },
{ "PageDown", ACTION_GOTO_PAGE_NEXT, {}}, { "PageDown", ACTION_GOTO_PAGE_NEXT },
{ "C-p", ACTION_GOTO_ITEM_PREVIOUS, {}}, { "C-p", ACTION_GOTO_ITEM_PREVIOUS },
{ "C-n", ACTION_GOTO_ITEM_NEXT, {}}, { "C-n", ACTION_GOTO_ITEM_NEXT },
{ "C-b", ACTION_GOTO_PAGE_PREVIOUS, {}}, { "C-b", ACTION_GOTO_PAGE_PREVIOUS },
{ "C-f", ACTION_GOTO_PAGE_NEXT, {}}, { "C-f", ACTION_GOTO_PAGE_NEXT },
{ "H", ACTION_GOTO_VIEW_TOP, {}}, { "H", ACTION_GOTO_VIEW_TOP },
{ "M", ACTION_GOTO_VIEW_CENTER, {}}, { "M", ACTION_GOTO_VIEW_CENTER },
{ "L", ACTION_GOTO_VIEW_BOTTOM, {}}, { "L", ACTION_GOTO_VIEW_BOTTOM },
// Not sure how to set these up, they're pretty arbitrary so far // Not sure how to set these up, they're pretty arbitrary so far
{ "Enter", ACTION_CHOOSE, {}}, { "Enter", ACTION_CHOOSE },
{ "Delete", ACTION_DELETE, {}}, { "Delete", ACTION_DELETE },
{ "Backspace", ACTION_UP, {}}, { "Backspace", ACTION_UP },
{ "a", ACTION_MPD_ADD, {}}, { "a", ACTION_MPD_ADD },
{ "r", ACTION_MPD_REPLACE, {}}, { "r", ACTION_MPD_REPLACE },
{ ":", ACTION_MPD_COMMAND, {}}, { ":", ACTION_MPD_COMMAND },
{ "Left", ACTION_MPD_PREVIOUS, {}}, { "Left", ACTION_MPD_PREVIOUS },
{ "Right", ACTION_MPD_NEXT, {}}, { "Right", ACTION_MPD_NEXT },
{ "M-Left", ACTION_MPD_BACKWARD, {}}, { "M-Left", ACTION_MPD_BACKWARD },
{ "M-Right", ACTION_MPD_FORWARD, {}}, { "M-Right", ACTION_MPD_FORWARD },
{ "h", ACTION_MPD_PREVIOUS, {}}, { "h", ACTION_MPD_PREVIOUS },
{ "l", ACTION_MPD_NEXT, {}}, { "l", ACTION_MPD_NEXT },
{ "Space", ACTION_MPD_TOGGLE, {}}, { "Space", ACTION_MPD_TOGGLE },
{ "C-Space", ACTION_MPD_STOP, {}}, { "C-Space", ACTION_MPD_STOP },
{ "u", ACTION_MPD_UPDATE_DB, {}}, { "u", ACTION_MPD_UPDATE_DB },
{ "M-PageUp", ACTION_MPD_VOLUME_UP, {}}, { "M-PageUp", ACTION_MPD_VOLUME_UP },
{ "M-PageDown", ACTION_MPD_VOLUME_DOWN, {}}, { "M-PageDown", ACTION_MPD_VOLUME_DOWN },
}, },
g_editor_bindings[] = g_editor_defaults[] =
{ {
{ "Left", ACTION_EDITOR_B_CHAR, {}}, { "Left", ACTION_EDITOR_B_CHAR },
{ "Right", ACTION_EDITOR_F_CHAR, {}}, { "Right", ACTION_EDITOR_F_CHAR },
{ "C-b", ACTION_EDITOR_B_CHAR, {}}, { "C-b", ACTION_EDITOR_B_CHAR },
{ "C-f", ACTION_EDITOR_F_CHAR, {}}, { "C-f", ACTION_EDITOR_F_CHAR },
{ "M-b", ACTION_EDITOR_B_WORD, {}}, { "M-b", ACTION_EDITOR_B_WORD },
{ "M-f", ACTION_EDITOR_F_WORD, {}}, { "M-f", ACTION_EDITOR_F_WORD },
{ "Home", ACTION_EDITOR_HOME, {}}, { "Home", ACTION_EDITOR_HOME },
{ "End", ACTION_EDITOR_END, {}}, { "End", ACTION_EDITOR_END },
{ "C-a", ACTION_EDITOR_HOME, {}}, { "C-a", ACTION_EDITOR_HOME },
{ "C-e", ACTION_EDITOR_END, {}}, { "C-e", ACTION_EDITOR_END },
{ "C-h", ACTION_EDITOR_B_DELETE, {}}, { "C-h", ACTION_EDITOR_B_DELETE },
{ "DEL", ACTION_EDITOR_B_DELETE, {}}, { "DEL", ACTION_EDITOR_B_DELETE },
{ "Backspace", ACTION_EDITOR_B_DELETE, {}}, { "Backspace", ACTION_EDITOR_B_DELETE },
{ "C-d", ACTION_EDITOR_F_DELETE, {}}, { "C-d", ACTION_EDITOR_F_DELETE },
{ "Delete", ACTION_EDITOR_F_DELETE, {}}, { "Delete", ACTION_EDITOR_F_DELETE },
{ "C-u", ACTION_EDITOR_B_KILL_LINE, {}}, { "C-u", ACTION_EDITOR_B_KILL_LINE },
{ "C-k", ACTION_EDITOR_F_KILL_LINE, {}}, { "C-k", ACTION_EDITOR_F_KILL_LINE },
{ "C-w", ACTION_EDITOR_B_KILL_WORD, {}}, { "C-w", ACTION_EDITOR_B_KILL_WORD },
{ "C-g", ACTION_QUIT, {}}, { "C-g", ACTION_QUIT },
{ "Escape", ACTION_QUIT, {}}, { "Escape", ACTION_QUIT },
{ "Enter", ACTION_EDITOR_CONFIRM, {}}, { "Enter", ACTION_EDITOR_CONFIRM },
}; };
static int static int
app_binding_cmp (const void *a, const void *b) app_binding_cmp (const void *a, const void *b)
{ {
return termo_keycmp (g.tk, const struct binding *aa = a, *bb = b;
&((struct binding *) a)->decoded, &((struct binding *) b)->decoded); 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 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)
{
ARRAY (struct binding, a)
ARRAY_INIT_SIZED (a, defaults_len);
termo_key_t decoded;
for (size_t i = 0; i < defaults_len; i++)
{ {
for (size_t i = 0; i < len; i++)
hard_assert (!*termo_strpkey_utf8 (g.tk, hard_assert (!*termo_strpkey_utf8 (g.tk,
bindings[i].key, &bindings[i].decoded, TERMO_FORMAT_ALTISMETA)); defaults[i].key, &decoded, TERMO_FORMAT_ALTISMETA));
qsort (bindings, len, sizeof *bindings, app_binding_cmp); 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 static bool
app_process_termo_event (termo_key_t *event) 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 (g.editor_line)
{ {
if ((binding = bsearch (&dummy, g_editor_bindings, 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); return app_editor_process_action (binding->action);
if (event->type != TERMO_TYPE_KEY || event->modifiers != 0) if (event->type != TERMO_TYPE_KEY || event->modifiers != 0)
return false; return false;
@ -2181,8 +2262,8 @@ app_process_termo_event (termo_key_t *event)
app_invalidate (); app_invalidate ();
return true; return true;
} }
if ((binding = bsearch (&dummy, g_default_bindings, if ((binding = bsearch (&dummy, g_normal_bindings,
N_ELEMENTS (g_default_bindings), sizeof *binding, app_binding_cmp))) g_normal_bindings_len, sizeof *binding, app_binding_cmp)))
return app_process_action (binding->action); return app_process_action (binding->action);
// TODO: parametrize actions, put this among other bindings // 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; (void) width;
// TODO: group them the other way around for clarity // TODO: group them the other way around for clarity
hard_assert (item_index < N_ELEMENTS (g_default_bindings)); // - go through 0..ACTION_COUNT
struct binding *binding = &g_default_bindings[item_index]; // - ...
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", 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); row_buffer_append (buffer, text, 0);
free (text); free (text);
} }
@ -2970,7 +3064,7 @@ help_tab_init (void)
static struct tab super; static struct tab super;
tab_init (&super, "Help"); tab_init (&super, "Help");
super.on_item_draw = help_tab_on_item_draw; 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; return &super;
} }
@ -3633,8 +3727,12 @@ main (int argc, char *argv[])
signals_setup_handlers (); signals_setup_handlers ();
app_init_poller_events (); app_init_poller_events ();
app_init_bindings (g_default_bindings, N_ELEMENTS (g_default_bindings)); app_init_bindings ("normal",
app_init_bindings (g_editor_bindings, N_ELEMENTS (g_editor_bindings)); 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) if (g_debug_mode)
app_prepend_tab (debug_tab_init ()); app_prepend_tab (debug_tab_init ());