Customizable key bindings
This commit is contained in:
parent
2375e0dd3e
commit
4bb9b11fed
260
nncmpp.c
260
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[] =
|
||||
g_normal_defaults[] =
|
||||
{
|
||||
{ "Escape", ACTION_QUIT, {}},
|
||||
{ "q", ACTION_QUIT, {}},
|
||||
{ "C-l", ACTION_REDRAW, {}},
|
||||
{ "M-Tab", ACTION_LAST_TAB, {}},
|
||||
{ "F1", ACTION_HELP_TAB, {}},
|
||||
{ "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, {}},
|
||||
{ "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, {}},
|
||||
{ "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, {}},
|
||||
{ "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, {}},
|
||||
{ "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[] =
|
||||
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, {}},
|
||||
{ "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-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, {}},
|
||||
{ "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 ());
|
||||
|
Loading…
Reference in New Issue
Block a user