Skip to content

Commit

Permalink
Experimental support for key combos
Browse files Browse the repository at this point in the history
This adds experimental support for key combos. Key combos are read using
a new input handler. For each key it scans all key bindings for matches.
If an exact match is found that binding is triggered else it will wait
for the next key.

Fixes #67
  • Loading branch information
jonas committed Feb 10, 2015
1 parent cb7d7d8 commit d94f39c
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 50 deletions.
2 changes: 1 addition & 1 deletion include/tig/display.h
Expand Up @@ -17,7 +17,7 @@
#include "tig/tig.h"
#include "tig/keys.h"

int get_input(int prompt_position, struct key *key, bool modifiers);
int get_input(int prompt_position, struct key *key);
int get_input_char(void);

extern WINDOW *status_win;
Expand Down
4 changes: 2 additions & 2 deletions include/tig/keys.h
Expand Up @@ -37,7 +37,6 @@ struct key {
char bytes[7];
} data;
struct {
bool escape:1;
bool control:1;
bool multibytes:1;
} modifiers;
Expand All @@ -53,11 +52,12 @@ key_to_unicode(const struct key *key)

struct keymap *get_keymap(const char *name, size_t namelen);

const char *get_key_name(const struct key key[], size_t keys, bool quote_comma);
enum status_code get_key_value(const char **name, struct key *key);

/* Looks for a key binding first in the given map, then in the generic map, and
* lastly in the default keybindings. */
enum request get_keybinding(const struct keymap *keymap, const struct key key[], size_t keys);
enum request get_keybinding(const struct keymap *keymap, const struct key key[], size_t keys, int *matches);
enum status_code add_keybinding(struct keymap *table, enum request request, const struct key key[], size_t keys);

const char *get_keys(const struct keymap *keymap, enum request request, bool all);
Expand Down
7 changes: 2 additions & 5 deletions src/display.c
Expand Up @@ -542,12 +542,12 @@ get_input_char(void)
}

int
get_input(int prompt_position, struct key *key, bool modifiers)
get_input(int prompt_position, struct key *key)
{
struct view *view;
int i, key_value, cursor_y, cursor_x;

if (prompt_position)
if (prompt_position > 0)
input_mode = TRUE;

memset(key, 0, sizeof(*key));
Expand Down Expand Up @@ -603,9 +603,6 @@ get_input(int prompt_position, struct key *key, bool modifiers)
* there's no input. */
if (key_value == ERR) {

} else if (key_value == KEY_ESC && modifiers) {
key->modifiers.escape = 1;

} else if (key_value == KEY_RESIZE) {
int height, width;

Expand Down
50 changes: 28 additions & 22 deletions src/keys.c
Expand Up @@ -45,15 +45,12 @@ get_keymap(const char *name, size_t namelen)
}

static bool
keybinding_equals(const struct keybinding *keybinding, const struct key key[],
keybinding_matches(const struct keybinding *keybinding, const struct key key[],
size_t keys, bool *conflict_ptr)
{
bool conflict = FALSE;
int i;

if (keybinding->keys != keys)
return FALSE;

for (i = 0; i < keys; i++) {
const struct key *key1 = &keybinding->key[i];
const struct key *key2 = &key[i];
Expand Down Expand Up @@ -81,6 +78,15 @@ keybinding_equals(const struct keybinding *keybinding, const struct key key[],
return TRUE;
}

static bool
keybinding_equals(const struct keybinding *keybinding, const struct key key[],
size_t keys, bool *conflict_ptr)
{
if (keybinding->keys != keys)
return FALSE;
return keybinding_matches(keybinding, key, keys, conflict_ptr);
}

enum status_code
add_keybinding(struct keymap *table, enum request request,
const struct key key[], size_t keys)
Expand Down Expand Up @@ -122,19 +128,28 @@ add_keybinding(struct keymap *table, enum request request,
/* Looks for a key binding first in the given map, then in the generic map, and
* lastly in the default keybindings. */
enum request
get_keybinding(const struct keymap *keymap, const struct key key[], size_t keys)
get_keybinding(const struct keymap *keymap, const struct key key[], size_t keys, int *matches)
{
enum request request = REQ_UNKNOWN;
size_t i;

for (i = 0; i < keymap->size; i++)
if (keybinding_equals(keymap->data[i], key, keys, NULL))
return keymap->data[i]->request;
if (keybinding_matches(keymap->data[i], key, keys, NULL)) {
if (matches)
(*matches)++;
if (keymap->data[i]->keys == keys)
request = keymap->data[i]->request;
}

for (i = 0; i < generic_keymap->size; i++)
if (keybinding_equals(generic_keymap->data[i], key, keys, NULL))
return generic_keymap->data[i]->request;
if (keybinding_matches(generic_keymap->data[i], key, keys, NULL)) {
if (matches)
(*matches)++;
if (request == REQ_UNKNOWN && generic_keymap->data[i]->keys == keys)
request = generic_keymap->data[i]->request;
}

return REQ_NONE;
return request;
}


Expand Down Expand Up @@ -263,13 +278,6 @@ get_key_value(const char **name_ptr, struct key *key)
if (mapping->value == '<')
return parse_key_value(key, name_ptr, 0, "<", end);

if (mapping->value == KEY_ESC) {
size_t offset = (end - name) + 1;

key->modifiers.escape = 1;
return parse_key_value(key, name_ptr, offset, NULL, NULL);
}

*name_ptr = end + 1;
key->data.value = mapping->value;
return SUCCESS;
Expand All @@ -287,7 +295,7 @@ get_key_value(const char **name_ptr, struct key *key)
return parse_key_value(key, name_ptr, 0, NULL, end);
}

static const char *
const char *
get_key_name(const struct key key[], size_t keys, bool quote_comma)
{
static char buf[SIZEOF_STR];
Expand All @@ -301,9 +309,7 @@ get_key_name(const struct key key[], size_t keys, bool quote_comma)
const char *end = "";
bool use_symbolic;

if (key[i].modifiers.escape) {
start = "<Esc>";
} else if (key[i].modifiers.control) {
if (key[i].modifiers.control) {
start = "<Ctrl-";
end = ">";
} else if (*name == ',' && quote_comma) {
Expand All @@ -325,7 +331,7 @@ get_key_name(const struct key key[], size_t keys, bool quote_comma)
name = "<?>";
for (j = 0; j < ARRAY_SIZE(key_mappings); j++)
if (key_mappings[j].value == value) {
start = key[i].modifiers.escape ? "<Esc><" : "<";
start = "<";
end = ">";
name = key_mappings[j].name;
break;
Expand Down
2 changes: 1 addition & 1 deletion src/options.c
Expand Up @@ -700,7 +700,7 @@ option_set_command(int argc, const char *argv[])
static enum status_code
option_bind_command(int argc, const char *argv[])
{
struct key key[1];
struct key key[16];
size_t keys = 0;
enum request request;
struct keymap *keymap;
Expand Down
12 changes: 7 additions & 5 deletions src/prompt.c
Expand Up @@ -37,10 +37,12 @@ prompt_input(const char *prompt, struct input *input)
input->buf[pos] = 0;

while (status == INPUT_OK || status == INPUT_SKIP) {
int offset = pos ? pos + promptlen : -1;

update_status("%s%.*s", prompt, pos, input->buf);
if (offset >= 0)
update_status("%s%.*s", prompt, pos, input->buf);

if (get_input(pos + promptlen, &key, FALSE) == OK) {
if (get_input(offset, &key) == OK) {
int len = strlen(key.data.bytes);

if (pos + len >= sizeof(input->buf)) {
Expand Down Expand Up @@ -498,7 +500,7 @@ prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
update_status("%s (%d of %d) %s%s", prompt, *selected + 1, size,
item->hotkey ? hotkey : "", item->text);

switch (get_input(COLS - 1, &key, FALSE)) {
switch (get_input(COLS - 1, &key)) {
case KEY_RETURN:
case KEY_ENTER:
case '\n':
Expand Down Expand Up @@ -864,8 +866,8 @@ run_prompt_command(struct view *view, const char *argv[])
/* Try :<key> */
key.modifiers.multibytes = 1;
string_ncopy(key.data.bytes, cmd, cmdlen);
request = get_keybinding(view->keymap, &key, 1);
if (request != REQ_NONE)
request = get_keybinding(view->keymap, &key, 1, NULL);
if (request != REQ_UNKNOWN)
return request;

/* Try :<command> */
Expand Down
59 changes: 47 additions & 12 deletions src/tig.c
Expand Up @@ -590,6 +590,50 @@ handle_mouse_event(void)
}
#endif

struct key_combo {
enum request request;
struct keymap *keymap;
size_t bufpos;
size_t keys;
struct key key[16];
};

static enum input_status
key_combo_handler(struct input *input, struct key *key)
{
struct key_combo *combo = input->data;
int matches = 0;

#ifdef NCURSES_MOUSE_VERSION
if (!key->modifiers.multibytes && key->data.value == KEY_MOUSE) {
combo->request = handle_mouse_event();
return INPUT_STOP;
}
#endif

if (!key->modifiers.multibytes && combo->keys &&
key->data.value == KEY_ESC)
return INPUT_CANCEL;

string_format_from(input->buf, &combo->bufpos, "%s%s",
combo->bufpos ? " " : "Keys: ", get_key_name(key, 1, FALSE));
combo->key[combo->keys++] = *key;
combo->request = get_keybinding(combo->keymap, combo->key, combo->keys, &matches);

if (combo->request == REQ_UNKNOWN)
return matches > 0 ? INPUT_OK : INPUT_STOP;
return INPUT_STOP;
}

enum request
read_key_combo(struct keymap *keymap)
{
struct key_combo combo = { REQ_NONE, keymap, 0 };
char *value = read_prompt_incremental("", FALSE, key_combo_handler, &combo);

return value ? combo.request : REQ_NONE;
}

int
main(int argc, const char *argv[])
{
Expand Down Expand Up @@ -646,25 +690,16 @@ main(int argc, const char *argv[])
}

while (view_driver(display[current_view], request)) {
struct key key;
int key_value = get_input(0, &key, TRUE);

#ifdef NCURSES_MOUSE_VERSION
if (key_value == KEY_MOUSE) {
request = handle_mouse_event();
continue;
}
#endif

view = display[current_view];
request = get_keybinding(view->keymap, &key, 1);
request = read_key_combo(view->keymap);

/* Some low-level request handling. This keeps access to
* status_win restricted. */
switch (request) {
case REQ_NONE:
case REQ_UNKNOWN:
report("Unknown key, press %s for help",
get_view_key(view, REQ_VIEW_HELP));
request = REQ_NONE;
break;
case REQ_PROMPT:
request = open_prompt(view);
Expand Down
2 changes: 0 additions & 2 deletions test/tigrc/compat-error-test
Expand Up @@ -87,11 +87,9 @@ tig warning: ~/.tigrc:10: show-refs is obsolete; see tigrc(5) for how to set the
tig warning: ~/.tigrc:11: show-id is obsolete; see tigrc(5) for how to set the id column option
tig warning: ~/.tigrc:12: title-overflow is obsolete; see tigrc(5) for how to set the commit-title and text column option
tig warning: ~/.tigrc:13: read-git-colors has been obsoleted by the git-colors option
tig warning: ~/.tigrc:22: Except for <Esc> combos only one key is allowed in key combos: Enter
tig warning: ~/.tigrc:23: Missing '>' from key mapping: <
tig warning: ~/.tigrc:24: Control key mapping must now use '<Ctrl-N>' instead of '^N'
tig warning: ~/.tigrc:25: Escape key combo must now use '<Esc>v' instead of '^[v'
tig warning: ~/.tigrc:26: Except for <Esc> combos only one key is allowed in key combos: F5
tig warning: ~/.tigrc:29: stage-next has been replaced by \`:/^@@'
tig warning: ~/.tigrc:30: diff-context-down has been replaced by \`:toggle diff-context -1'
tig warning: ~/.tigrc:31: diff-context-up has been replaced by \`:toggle diff-context +1'
Expand Down

0 comments on commit d94f39c

Please sign in to comment.