diff options
| author | Marc André Tanner <mat@brain-dump.org> | 2015-09-13 17:49:38 +0200 |
|---|---|---|
| committer | Marc André Tanner <mat@brain-dump.org> | 2015-10-05 15:54:30 +0200 |
| commit | a76057df97157dc76959bcd40649f5953aebc9d3 (patch) | |
| tree | 75df46b238edacc60ede00ee5821d47cb814b316 | |
| parent | fa950307aeb2a060be72fc73e0017e5497cb4ace (diff) | |
| download | vis-a76057df97157dc76959bcd40649f5953aebc9d3.tar.gz vis-a76057df97157dc76959bcd40649f5953aebc9d3.tar.xz | |
vis: rework input handling using libtermkey
Key bindings are now specified as symbolic key strings, this
will eventually allow run time configurable key mappings.
This introduces a bulid time dependency on libtermkey which
can be found at:
http://www.leonerd.org.uk/code/libtermkey/
| -rw-r--r-- | config.def.h | 834 | ||||
| -rw-r--r-- | config.mk | 2 | ||||
| -rw-r--r-- | editor.c | 21 | ||||
| -rw-r--r-- | editor.h | 17 | ||||
| -rw-r--r-- | macro.h | 2 | ||||
| -rw-r--r-- | ui-curses.c | 84 | ||||
| -rw-r--r-- | ui-curses.h | 15 | ||||
| -rw-r--r-- | ui.h | 9 | ||||
| -rw-r--r-- | vis.c | 178 |
9 files changed, 585 insertions, 577 deletions
diff --git a/config.def.h b/config.def.h index 1516762..758bf69 100644 --- a/config.def.h +++ b/config.def.h @@ -1,10 +1,4 @@ /** start by reading from the top of vis.c up until config.h is included */ -/* macros used to specify keys for key bindings */ -#define ESC 0x1B -#define NONE(k) { .str = { k }, .code = 0 } -#define KEY(k) { .str = { '\0' }, .code = UI_KEY_##k } -#define CONTROL(k) NONE((k)&0x1F) -#define META(k) { .str = { ESC, (k) }, .code = 0 } /* a mode contains a set of key bindings which are currently valid. * @@ -70,160 +64,159 @@ static Command cmds[] = { /* called before any other keybindings are checked, if the function returns false * the key is completely ignored. */ -static bool vis_keypress(Key *key) { +static bool vis_keypress(const char *key) { editor_info_hide(vis); - if (vis->recording) - macro_append(vis->recording, key, sizeof(*key)); + if (vis->recording && key) + macro_append(vis->recording, key); return true; } static KeyBinding basic_movement[] = { - { { CONTROL('Z') }, suspend, { NULL } }, - { { KEY(LEFT) }, movement, { .i = MOVE_CHAR_PREV } }, - { { KEY(SHIFT_LEFT) }, movement, { .i = MOVE_LONGWORD_START_PREV } }, - { { KEY(RIGHT) }, movement, { .i = MOVE_CHAR_NEXT } }, - { { KEY(SHIFT_RIGHT) }, movement, { .i = MOVE_LONGWORD_START_NEXT } }, - { { KEY(UP) }, movement, { .i = MOVE_LINE_UP } }, - { { KEY(DOWN) }, movement, { .i = MOVE_LINE_DOWN } }, - { { KEY(PAGE_UP) }, wscroll, { .i = -PAGE } }, - { { KEY(PAGE_DOWN) }, wscroll, { .i = +PAGE } }, - { { KEY(HOME) }, movement, { .i = MOVE_LINE_START } }, - { { KEY(END) }, movement, { .i = MOVE_LINE_FINISH } }, + { "<C-z>" , suspend, { NULL } }, + { "<Left>" , movement, { .i = MOVE_CHAR_PREV } }, + { "<S-Left>" , movement, { .i = MOVE_LONGWORD_START_PREV } }, + { "<Right>" , movement, { .i = MOVE_CHAR_NEXT } }, + { "<S-Right>" , movement, { .i = MOVE_LONGWORD_START_NEXT } }, + { "<Up>" , movement, { .i = MOVE_LINE_UP } }, + { "<Down>" , movement, { .i = MOVE_LINE_DOWN } }, + { "<PageUp>" , wscroll, { .i = -PAGE } }, + { "<PageDown>" , wscroll, { .i = +PAGE } }, + { "<Home>" , movement, { .i = MOVE_LINE_START } }, + { "<End>" , movement, { .i = MOVE_LINE_FINISH } }, { /* empty last element, array terminator */ }, }; static KeyBinding vis_movements[] = { - { { KEY(BACKSPACE) }, movement, { .i = MOVE_CHAR_PREV } }, - { { NONE('h') }, movement, { .i = MOVE_CHAR_PREV } }, - { { NONE(' ') }, movement, { .i = MOVE_CHAR_NEXT } }, - { { NONE('l') }, movement, { .i = MOVE_CHAR_NEXT } }, - { { NONE('k') }, movement, { .i = MOVE_LINE_UP } }, - { { CONTROL('P') }, movement, { .i = MOVE_LINE_UP } }, - { { NONE('g'), NONE('k') }, movement, { .i = MOVE_SCREEN_LINE_UP } }, - { { NONE('g'), KEY(UP) }, movement, { .i = MOVE_SCREEN_LINE_UP } }, - { { NONE('j') }, movement, { .i = MOVE_LINE_DOWN } }, - { { CONTROL('J') }, movement, { .i = MOVE_LINE_DOWN } }, - { { CONTROL('N') }, movement, { .i = MOVE_LINE_DOWN } }, - { { NONE('\n') }, movement, { .i = MOVE_LINE_DOWN } }, - { { NONE('\r') }, movement, { .i = MOVE_LINE_DOWN } }, - { { NONE('g'), NONE('j') }, movement, { .i = MOVE_SCREEN_LINE_DOWN } }, - { { NONE('g'), KEY(DOWN) }, movement, { .i = MOVE_SCREEN_LINE_DOWN } }, - { { NONE('^') }, movement, { .i = MOVE_LINE_START } }, - { { NONE('g'), NONE('_') }, movement, { .i = MOVE_LINE_FINISH } }, - { { NONE('$') }, movement, { .i = MOVE_LINE_LASTCHAR } }, - { { NONE('%') }, movement, { .i = MOVE_BRACKET_MATCH } }, - { { NONE('b') }, movement, { .i = MOVE_WORD_START_PREV } }, - { { NONE('B') }, movement, { .i = MOVE_LONGWORD_START_PREV } }, - { { NONE('w') }, movement, { .i = MOVE_WORD_START_NEXT } }, - { { NONE('W') }, movement, { .i = MOVE_LONGWORD_START_NEXT } }, - { { NONE('g'), NONE('e') }, movement, { .i = MOVE_WORD_END_PREV } }, - { { NONE('g'), NONE('E') }, movement, { .i = MOVE_LONGWORD_END_PREV } }, - { { NONE('e') }, movement, { .i = MOVE_WORD_END_NEXT } }, - { { NONE('E') }, movement, { .i = MOVE_LONGWORD_END_NEXT } }, - { { NONE('{') }, movement, { .i = MOVE_PARAGRAPH_PREV } }, - { { NONE('}') }, movement, { .i = MOVE_PARAGRAPH_NEXT } }, - { { NONE('(') }, movement, { .i = MOVE_SENTENCE_PREV } }, - { { NONE(')') }, movement, { .i = MOVE_SENTENCE_NEXT } }, - { { NONE('['), NONE('[') }, movement, { .i = MOVE_FUNCTION_START_PREV } }, - { { NONE('['), NONE(']') }, movement, { .i = MOVE_FUNCTION_END_PREV } }, - { { NONE(']'), NONE('[') }, movement, { .i = MOVE_FUNCTION_START_NEXT } }, - { { NONE(']'), NONE(']') }, movement, { .i = MOVE_FUNCTION_END_NEXT } }, - { { NONE('g'), NONE('g') }, gotoline, { .i = -1 } }, - { { NONE('g'), NONE('0') }, movement, { .i = MOVE_SCREEN_LINE_BEGIN } }, - { { NONE('g'), NONE('m') }, movement, { .i = MOVE_SCREEN_LINE_MIDDLE } }, - { { NONE('g'), NONE('$') }, movement, { .i = MOVE_SCREEN_LINE_END } }, - { { NONE('G') }, gotoline, { .i = +1 } }, - { { NONE('|') }, movement, { .i = MOVE_COLUMN } }, - { { NONE('n') }, movement, { .i = MOVE_SEARCH_FORWARD } }, - { { NONE('N') }, movement, { .i = MOVE_SEARCH_BACKWARD } }, - { { NONE('H') }, movement, { .i = MOVE_WINDOW_LINE_TOP } }, - { { NONE('M') }, movement, { .i = MOVE_WINDOW_LINE_MIDDLE } }, - { { NONE('L') }, movement, { .i = MOVE_WINDOW_LINE_BOTTOM } }, - { { NONE('*') }, movement, { .i = MOVE_SEARCH_WORD_FORWARD } }, - { { NONE('#') }, movement, { .i = MOVE_SEARCH_WORD_BACKWARD} }, - { { NONE('f') }, movement_key, { .i = MOVE_RIGHT_TO } }, - { { NONE('F') }, movement_key, { .i = MOVE_LEFT_TO } }, - { { NONE('t') }, movement_key, { .i = MOVE_RIGHT_TILL } }, - { { NONE('T') }, movement_key, { .i = MOVE_LEFT_TILL } }, - { { NONE(';') }, totill_repeat, { NULL } }, - { { NONE(',') }, totill_reverse,{ NULL } }, - { { NONE('/') }, prompt_search,{ .s = "/" } }, - { { NONE('?') }, prompt_search,{ .s = "?" } }, + { "<Backspace>" , movement, { .i = MOVE_CHAR_PREV } }, + { "h" , movement, { .i = MOVE_CHAR_PREV } }, + { " " , movement, { .i = MOVE_CHAR_NEXT } }, + { "l" , movement, { .i = MOVE_CHAR_NEXT } }, + { "k" , movement, { .i = MOVE_LINE_UP } }, + { "C-p" , movement, { .i = MOVE_LINE_UP } }, + { "gk" , movement, { .i = MOVE_SCREEN_LINE_UP } }, + { "g<Up>" , movement, { .i = MOVE_SCREEN_LINE_UP } }, + { "j" , movement, { .i = MOVE_LINE_DOWN } }, + { "<C-j>" , movement, { .i = MOVE_LINE_DOWN } }, + { "<C-n>" , movement, { .i = MOVE_LINE_DOWN } }, + { "<Enter>" , movement, { .i = MOVE_LINE_DOWN } }, + { "gj" , movement, { .i = MOVE_SCREEN_LINE_DOWN } }, + { "g<Down>" , movement, { .i = MOVE_SCREEN_LINE_DOWN } }, + { "^" , movement, { .i = MOVE_LINE_START } }, + { "g_" , movement, { .i = MOVE_LINE_FINISH } }, + { "$" , movement, { .i = MOVE_LINE_LASTCHAR } }, + { "%" , movement, { .i = MOVE_BRACKET_MATCH } }, + { "b" , movement, { .i = MOVE_WORD_START_PREV } }, + { "B" , movement, { .i = MOVE_LONGWORD_START_PREV } }, + { "w" , movement, { .i = MOVE_WORD_START_NEXT } }, + { "W" , movement, { .i = MOVE_LONGWORD_START_NEXT } }, + { "ge" , movement, { .i = MOVE_WORD_END_PREV } }, + { "gE" , movement, { .i = MOVE_LONGWORD_END_PREV } }, + { "e" , movement, { .i = MOVE_WORD_END_NEXT } }, + { "E" , movement, { .i = MOVE_LONGWORD_END_NEXT } }, + { "{" , movement, { .i = MOVE_PARAGRAPH_PREV } }, + { "}" , movement, { .i = MOVE_PARAGRAPH_NEXT } }, + { "(" , movement, { .i = MOVE_SENTENCE_PREV } }, + { ")" , movement, { .i = MOVE_SENTENCE_NEXT } }, + { "[[" , movement, { .i = MOVE_FUNCTION_START_PREV } }, + { "[]" , movement, { .i = MOVE_FUNCTION_END_PREV } }, + { "][" , movement, { .i = MOVE_FUNCTION_START_NEXT } }, + { "]]" , movement, { .i = MOVE_FUNCTION_END_NEXT } }, + { "gg" , gotoline, { .i = -1 } }, + { "g0" , movement, { .i = MOVE_SCREEN_LINE_BEGIN } }, + { "gm" , movement, { .i = MOVE_SCREEN_LINE_MIDDLE } }, + { "g$" , movement, { .i = MOVE_SCREEN_LINE_END } }, + { "G" , gotoline, { .i = +1 } }, + { "|" , movement, { .i = MOVE_COLUMN } }, + { "n" , movement, { .i = MOVE_SEARCH_FORWARD } }, + { "N" , movement, { .i = MOVE_SEARCH_BACKWARD } }, + { "H" , movement, { .i = MOVE_WINDOW_LINE_TOP } }, + { "M" , movement, { .i = MOVE_WINDOW_LINE_MIDDLE } }, + { "L" , movement, { .i = MOVE_WINDOW_LINE_BOTTOM } }, + { "*" , movement, { .i = MOVE_SEARCH_WORD_FORWARD } }, + { "#" , movement, { .i = MOVE_SEARCH_WORD_BACKWARD} }, + { "f" , movement_key, { .i = MOVE_RIGHT_TO } }, + { "F" , movement_key, { .i = MOVE_LEFT_TO } }, + { "t" , movement_key, { .i = MOVE_RIGHT_TILL } }, + { "T" , movement_key, { .i = MOVE_LEFT_TILL } }, + { ";" , totill_repeat, { NULL } }, + { "," , totill_reverse,{ NULL } }, + { "/" , prompt_search,{ .s = "/" } }, + { "?" , prompt_search,{ .s = "?" } }, { /* empty last element, array terminator */ }, }; static KeyBinding vis_textobjs[] = { - { { NONE('a'), NONE('w') }, textobj, { .i = TEXT_OBJ_OUTER_WORD } }, - { { NONE('a'), NONE('W') }, textobj, { .i = TEXT_OBJ_OUTER_LONGWORD } }, - { { NONE('a'), NONE('s') }, textobj, { .i = TEXT_OBJ_SENTENCE } }, - { { NONE('a'), NONE('p') }, textobj, { .i = TEXT_OBJ_PARAGRAPH } }, - { { NONE('a'), NONE('[') }, textobj, { .i = TEXT_OBJ_OUTER_SQUARE_BRACKET } }, - { { NONE('a'), NONE(']') }, textobj, { .i = TEXT_OBJ_OUTER_SQUARE_BRACKET } }, - { { NONE('a'), NONE('(') }, textobj, { .i = TEXT_OBJ_OUTER_PARANTHESE } }, - { { NONE('a'), NONE(')') }, textobj, { .i = TEXT_OBJ_OUTER_PARANTHESE } }, - { { NONE('a'), NONE('b') }, textobj, { .i = TEXT_OBJ_OUTER_PARANTHESE } }, - { { NONE('a'), NONE('<') }, textobj, { .i = TEXT_OBJ_OUTER_ANGLE_BRACKET } }, - { { NONE('a'), NONE('>') }, textobj, { .i = TEXT_OBJ_OUTER_ANGLE_BRACKET } }, - { { NONE('a'), NONE('{') }, textobj, { .i = TEXT_OBJ_OUTER_CURLY_BRACKET } }, - { { NONE('a'), NONE('}') }, textobj, { .i = TEXT_OBJ_OUTER_CURLY_BRACKET } }, - { { NONE('a'), NONE('B') }, textobj, { .i = TEXT_OBJ_OUTER_CURLY_BRACKET } }, - { { NONE('a'), NONE('"') }, textobj, { .i = TEXT_OBJ_OUTER_QUOTE } }, - { { NONE('a'), NONE('\'') }, textobj, { .i = TEXT_OBJ_OUTER_SINGLE_QUOTE } }, - { { NONE('a'), NONE('`') }, textobj, { .i = TEXT_OBJ_OUTER_BACKTICK } }, - { { NONE('a'), NONE('e') }, textobj, { .i = TEXT_OBJ_OUTER_ENTIRE } }, - { { NONE('a'), NONE('f') }, textobj, { .i = TEXT_OBJ_OUTER_FUNCTION } }, - { { NONE('a'), NONE('l') }, textobj, { .i = TEXT_OBJ_OUTER_LINE } }, + { "aw", textobj, { .i = TEXT_OBJ_OUTER_WORD } }, + { "aW", textobj, { .i = TEXT_OBJ_OUTER_LONGWORD } }, + { "as", textobj, { .i = TEXT_OBJ_SENTENCE } }, + { "ap", textobj, { .i = TEXT_OBJ_PARAGRAPH } }, + { "a[", textobj, { .i = TEXT_OBJ_OUTER_SQUARE_BRACKET } }, + { "a]", textobj, { .i = TEXT_OBJ_OUTER_SQUARE_BRACKET } }, + { "a(", textobj, { .i = TEXT_OBJ_OUTER_PARANTHESE } }, + { "a)", textobj, { .i = TEXT_OBJ_OUTER_PARANTHESE } }, + { "ab", textobj, { .i = TEXT_OBJ_OUTER_PARANTHESE } }, + { "a<", textobj, { .i = TEXT_OBJ_OUTER_ANGLE_BRACKET } }, + { "a>", textobj, { .i = TEXT_OBJ_OUTER_ANGLE_BRACKET } }, + { "a{", textobj, { .i = TEXT_OBJ_OUTER_CURLY_BRACKET } }, + { "a}", textobj, { .i = TEXT_OBJ_OUTER_CURLY_BRACKET } }, + { "aB", textobj, { .i = TEXT_OBJ_OUTER_CURLY_BRACKET } }, + { "a\"", textobj, { .i = TEXT_OBJ_OUTER_QUOTE } }, + { "a\'", textobj, { .i = TEXT_OBJ_OUTER_SINGLE_QUOTE } }, + { "a`", textobj, { .i = TEXT_OBJ_OUTER_BACKTICK } }, + { "ae", textobj, { .i = TEXT_OBJ_OUTER_ENTIRE } }, + { "af", textobj, { .i = TEXT_OBJ_OUTER_FUNCTION } }, + { "al", textobj, { .i = TEXT_OBJ_OUTER_LINE } }, { /* empty last element, array terminator */ }, }; static KeyBinding vis_inner_textobjs[] = { - { { NONE('i'), NONE('w') }, textobj, { .i = TEXT_OBJ_INNER_WORD } }, - { { NONE('i'), NONE('W') }, textobj, { .i = TEXT_OBJ_INNER_LONGWORD } }, - { { NONE('i'), NONE('s') }, textobj, { .i = TEXT_OBJ_SENTENCE } }, - { { NONE('i'), NONE('p') }, textobj, { .i = TEXT_OBJ_PARAGRAPH } }, - { { NONE('i'), NONE('[') }, textobj, { .i = TEXT_OBJ_INNER_SQUARE_BRACKET } }, - { { NONE('i'), NONE(']') }, textobj, { .i = TEXT_OBJ_INNER_SQUARE_BRACKET } }, - { { NONE('i'), NONE('(') }, textobj, { .i = TEXT_OBJ_INNER_PARANTHESE } }, - { { NONE('i'), NONE(')') }, textobj, { .i = TEXT_OBJ_INNER_PARANTHESE } }, - { { NONE('i'), NONE('b') }, textobj, { .i = TEXT_OBJ_INNER_PARANTHESE } }, - { { NONE('i'), NONE('<') }, textobj, { .i = TEXT_OBJ_INNER_ANGLE_BRACKET } }, - { { NONE('i'), NONE('>') }, textobj, { .i = TEXT_OBJ_INNER_ANGLE_BRACKET } }, - { { NONE('i'), NONE('{') }, textobj, { .i = TEXT_OBJ_INNER_CURLY_BRACKET } }, - { { NONE('i'), NONE('}') }, textobj, { .i = TEXT_OBJ_INNER_CURLY_BRACKET } }, - { { NONE('i'), NONE('B') }, textobj, { .i = TEXT_OBJ_INNER_CURLY_BRACKET } }, - { { NONE('i'), NONE('"') }, textobj, { .i = TEXT_OBJ_INNER_QUOTE } }, - { { NONE('i'), NONE('\'') }, textobj, { .i = TEXT_OBJ_INNER_SINGLE_QUOTE } }, - { { NONE('i'), NONE('`') }, textobj, { .i = TEXT_OBJ_INNER_BACKTICK } }, - { { NONE('i'), NONE('e') }, textobj, { .i = TEXT_OBJ_INNER_ENTIRE } }, - { { NONE('i'), NONE('f') }, textobj, { .i = TEXT_OBJ_INNER_FUNCTION } }, - { { NONE('i'), NONE('l') }, textobj, { .i = TEXT_OBJ_INNER_LINE } }, + { "iw", textobj, { .i = TEXT_OBJ_INNER_WORD } }, + { "iW", textobj, { .i = TEXT_OBJ_INNER_LONGWORD } }, + { "is", textobj, { .i = TEXT_OBJ_SENTENCE } }, + { "ip", textobj, { .i = TEXT_OBJ_PARAGRAPH } }, + { "i[", textobj, { .i = TEXT_OBJ_INNER_SQUARE_BRACKET } }, + { "i]", textobj, { .i = TEXT_OBJ_INNER_SQUARE_BRACKET } }, + { "i(", textobj, { .i = TEXT_OBJ_INNER_PARANTHESE } }, + { "i)", textobj, { .i = TEXT_OBJ_INNER_PARANTHESE } }, + { "ib", textobj, { .i = TEXT_OBJ_INNER_PARANTHESE } }, + { "i<", textobj, { .i = TEXT_OBJ_INNER_ANGLE_BRACKET } }, + { "i>", textobj, { .i = TEXT_OBJ_INNER_ANGLE_BRACKET } }, + { "i{", textobj, { .i = TEXT_OBJ_INNER_CURLY_BRACKET } }, + { "i}", textobj, { .i = TEXT_OBJ_INNER_CURLY_BRACKET } }, + { "iB", textobj, { .i = TEXT_OBJ_INNER_CURLY_BRACKET } }, + { "i\"", textobj, { .i = TEXT_OBJ_INNER_QUOTE } }, + { "i\'", textobj, { .i = TEXT_OBJ_INNER_SINGLE_QUOTE } }, + { "i`", textobj, { .i = TEXT_OBJ_INNER_BACKTICK } }, + { "ie", textobj, { .i = TEXT_OBJ_INNER_ENTIRE } }, + { "if", textobj, { .i = TEXT_OBJ_INNER_FUNCTION } }, + { "il", textobj, { .i = TEXT_OBJ_INNER_LINE } }, { /* empty last element, array terminator */ }, }; static KeyBinding vis_operators[] = { - { { NONE('0') }, zero, { NULL } }, - { { NONE('1') }, count, { .i = 1 } }, - { { NONE('2') }, count, { .i = 2 } }, - { { NONE('3') }, count, { .i = 3 } }, - { { NONE('4') }, count, { .i = 4 } }, - { { NONE('5') }, count, { .i = 5 } }, - { { NONE('6') }, count, { .i = 6 } }, - { { NONE('7') }, count, { .i = 7 } }, - { { NONE('8') }, count, { .i = 8 } }, - { { NONE('9') }, count, { .i = 9 } }, - { { NONE('d') }, operator, { .i = OP_DELETE } }, - { { NONE('c') }, operator, { .i = OP_CHANGE } }, - { { NONE('y') }, operator, { .i = OP_YANK } }, - { { NONE('p') }, put, { .i = PUT_AFTER } }, - { { NONE('P') }, put, { .i = PUT_BEFORE } }, - { { NONE('g'), NONE('p') }, put, { .i = PUT_AFTER_END } }, - { { NONE('g'), NONE('P') }, put, { .i = PUT_BEFORE_END } }, - { { NONE('>') }, operator, { .i = OP_SHIFT_RIGHT } }, - { { NONE('<') }, operator, { .i = OP_SHIFT_LEFT } }, - { { NONE('g'), NONE('U') }, changecase, { .i = +1 } }, - { { NONE('~') }, changecase, { .i = 0 } }, - { { NONE('g'), NONE('~') }, changecase, { .i = 0 } }, - { { NONE('g'), NONE('u') }, changecase, { .i = -1 } }, + { "0" , zero, { NULL } }, + { "1" , count, { .i = 1 } }, + { "2" , count, { .i = 2 } }, + { "3" , count, { .i = 3 } }, + { "4" , count, { .i = 4 } }, + { "5" , count, { .i = 5 } }, + { "6" , count, { .i = 6 } }, + { "7" , count, { .i = 7 } }, + { "8" , count, { .i = 8 } }, + { "9" , count, { .i = 9 } }, + { "d" , operator, { .i = OP_DELETE } }, + { "c" , operator, { .i = OP_CHANGE } }, + { "y" , operator, { .i = OP_YANK } }, + { "p" , put, { .i = PUT_AFTER } }, + { "P" , put, { .i = PUT_BEFORE } }, + { "gp" , put, { .i = PUT_AFTER_END } }, + { "gP" , put, { .i = PUT_BEFORE_END } }, + { ">" , operator, { .i = OP_SHIFT_RIGHT } }, + { "<" , operator, { .i = OP_SHIFT_LEFT } }, + { "gU" , changecase, { .i = +1 } }, + { "~" , changecase, { .i = 0 } }, + { "g~" , changecase, { .i = 0 } }, + { "gu" , changecase, { .i = -1 } }, { /* empty last element, array terminator */ }, }; @@ -242,224 +235,224 @@ static void vis_mode_operator_input(const char *str, size_t len) { } static KeyBinding vis_operator_options[] = { - { { NONE('v') }, motiontype, { .i = CHARWISE } }, - { { NONE('V') }, motiontype, { .i = LINEWISE } }, + { "v" , motiontype, { .i = CHARWISE } }, + { "V" , motiontype, { .i = LINEWISE } }, { /* empty last element, array terminator */ }, }; static KeyBinding vis_registers[] = { /* {a-zA-Z0-9.%#:-"} */ - { { NONE('"'), NONE('a') }, reg, { .i = REG_a } }, - { { NONE('"'), NONE('b') }, reg, { .i = REG_b } }, - { { NONE('"'), NONE('c') }, reg, { .i = REG_c } }, - { { NONE('"'), NONE('d') }, reg, { .i = REG_d } }, - { { NONE('"'), NONE('e') }, reg, { .i = REG_e } }, - { { NONE('"'), NONE('f') }, reg, { .i = REG_f } }, - { { NONE('"'), NONE('g') }, reg, { .i = REG_g } }, - { { NONE('"'), NONE('h') }, reg, { .i = REG_h } }, - { { NONE('"'), NONE('i') }, reg, { .i = REG_i } }, - { { NONE('"'), NONE('j') }, reg, { .i = REG_j } }, - { { NONE('"'), NONE('k') }, reg, { .i = REG_k } }, - { { NONE('"'), NONE('l') }, reg, { .i = REG_l } }, - { { NONE('"'), NONE('m') }, reg, { .i = REG_m } }, - { { NONE('"'), NONE('n') }, reg, { .i = REG_n } }, - { { NONE('"'), NONE('o') }, reg, { .i = REG_o } }, - { { NONE('"'), NONE('p') }, reg, { .i = REG_p } }, - { { NONE('"'), NONE('q') }, reg, { .i = REG_q } }, - { { NONE('"'), NONE('r') }, reg, { .i = REG_r } }, - { { NONE('"'), NONE('s') }, reg, { .i = REG_s } }, - { { NONE('"'), NONE('t') }, reg, { .i = REG_t } }, - { { NONE('"'), NONE('u') }, reg, { .i = REG_u } }, - { { NONE('"'), NONE('v') }, reg, { .i = REG_v } }, - { { NONE('"'), NONE('w') }, reg, { .i = REG_w } }, - { { NONE('"'), NONE('x') }, reg, { .i = REG_x } }, - { { NONE('"'), NONE('y') }, reg, { .i = REG_y } }, - { { NONE('"'), NONE('z') }, reg, { .i = REG_z } }, - { /* empty last element, array terminator */ }, + { "\"a", reg, { .i = REG_a } }, + { "\"b", reg, { .i = REG_b } }, + { "\"c", reg, { .i = REG_c } }, + { "\"d", reg, { .i = REG_d } }, + { "\"e", reg, { .i = REG_e } }, + { "\"f", reg, { .i = REG_f } }, + { "\"g", reg, { .i = REG_g } }, + { "\"h", reg, { .i = REG_h } }, + { "\"i", reg, { .i = REG_i } }, + { "\"j", reg, { .i = REG_j } }, + { "\"k", reg, { .i = REG_k } }, + { "\"l", reg, { .i = REG_l } }, + { "\"m", reg, { .i = REG_m } }, + { "\"n", reg, { .i = REG_n } }, + { "\"o", reg, { .i = REG_o } }, + { "\"p", reg, { .i = REG_p } }, + { "\"q", reg, { .i = REG_q } }, + { "\"r", reg, { .i = REG_r } }, + { "\"s", reg, { .i = REG_s } }, + { "\"t", reg, { .i = REG_t } }, + { "\"u", reg, { .i = REG_u } }, + { "\"v", reg, { .i = REG_v } }, + { "\"w", reg, { .i = REG_w } }, + { "\"x", reg, { .i = REG_x } }, + { "\"y", reg, { .i = REG_y } }, + { "\"z", reg, { .i = REG_z } }, + { /* empty last element, array terminator */ }, }; static KeyBinding vis_marks[] = { - { { NONE('`'), NONE('a') }, mark, { .i = MARK_a } }, - { { NONE('`'), NONE('b') }, mark, { .i = MARK_b } }, - { { NONE('`'), NONE('c') }, mark, { .i = MARK_c } }, - { { NONE('`'), NONE('d') }, mark, { .i = MARK_d } }, - { { NONE('`'), NONE('e') }, mark, { .i = MARK_e } }, - { { NONE('`'), NONE('f') }, mark, { .i = MARK_f } }, - { { NONE('`'), NONE('g') }, mark, { .i = MARK_g } }, - { { NONE('`'), NONE('h') }, mark, { .i = MARK_h } }, - { { NONE('`'), NONE('i') }, mark, { .i = MARK_i } }, - { { NONE('`'), NONE('j') }, mark, { .i = MARK_j } }, - { { NONE('`'), NONE('k') }, mark, { .i = MARK_k } }, - { { NONE('`'), NONE('l') }, mark, { .i = MARK_l } }, - { { NONE('`'), NONE('m') }, mark, { .i = MARK_m } }, - { { NONE('`'), NONE('n') }, mark, { .i = MARK_n } }, - { { NONE('`'), NONE('o') }, mark, { .i = MARK_o } }, - { { NONE('`'), NONE('p') }, mark, { .i = MARK_p } }, - { { NONE('`'), NONE('q') }, mark, { .i = MARK_q } }, - { { NONE('`'), NONE('r') }, mark, { .i = MARK_r } }, - { { NONE('`'), NONE('s') }, mark, { .i = MARK_s } }, - { { NONE('`'), NONE('t') }, mark, { .i = MARK_t } }, - { { NONE('`'), NONE('u') }, mark, { .i = MARK_u } }, - { { NONE('`'), NONE('v') }, mark, { .i = MARK_v } }, - { { NONE('`'), NONE('w') }, mark, { .i = MARK_w } }, - { { NONE('`'), NONE('x') }, mark, { .i = MARK_x } }, - { { NONE('`'), NONE('y') }, mark, { .i = MARK_y } }, - { { NONE('`'), NONE('z') }, mark, { .i = MARK_z } }, - { { NONE('`'), NONE('<') }, mark, { .i = MARK_SELECTION_START } }, - { { NONE('`'), NONE('>') }, mark, { .i = MARK_SELECTION_END } }, - { /* empty last element, array terminator */ }, + { "`a'", mark, { .i = MARK_a } }, + { "`b'", mark, { .i = MARK_b } }, + { "`c'", mark, { .i = MARK_c } }, + { "`d'", mark, { .i = MARK_d } }, + { "`e'", mark, { .i = MARK_e } }, + { "`f'", mark, { .i = MARK_f } }, + { "`g'", mark, { .i = MARK_g } }, + { "`h'", mark, { .i = MARK_h } }, + { "`i'", mark, { .i = MARK_i } }, + { "`j'", mark, { .i = MARK_j } }, + { "`k'", mark, { .i = MARK_k } }, + { "`l'", mark, { .i = MARK_l } }, + { "`m'", mark, { .i = MARK_m } }, + { "`n'", mark, { .i = MARK_n } }, + { "`o'", mark, { .i = MARK_o } }, + { "`p'", mark, { .i = MARK_p } }, + { "`q'", mark, { .i = MARK_q } }, + { "`r'", mark, { .i = MARK_r } }, + { "`s'", mark, { .i = MARK_s } }, + { "`t'", mark, { .i = MARK_t } }, + { "`u'", mark, { .i = MARK_u } }, + { "`v'", mark, { .i = MARK_v } }, + { "`w'", mark, { .i = MARK_w } }, + { "`x'", mark, { .i = MARK_x } }, + { "`y'", mark, { .i = MARK_y } }, + { "`z'", mark, { .i = MARK_z } }, + { "`<'", mark, { .i = MARK_SELECTION_START } }, + { "`>'", mark, { .i = MARK_SELECTION_END } }, + { /* empty last element, array terminator */ }, }; static KeyBinding vis_marks_line[] = { - { { NONE('\''), NONE('a') }, mark_line, { .i = MARK_a } }, - { { NONE('\''), NONE('b') }, mark_line, { .i = MARK_b } }, - { { NONE('\''), NONE('c') }, mark_line, { .i = MARK_c } }, - { { NONE('\''), NONE('d') }, mark_line, { .i = MARK_d } }, - { { NONE('\''), NONE('e') }, mark_line, { .i = MARK_e } }, - { { NONE('\''), NONE('f') }, mark_line, { .i = MARK_f } }, - { { NONE('\''), NONE('g') }, mark_line, { .i = MARK_g } }, - { { NONE('\''), NONE('h') }, mark_line, { .i = MARK_h } }, - { { NONE('\''), NONE('i') }, mark_line, { .i = MARK_i } }, - { { NONE('\''), NONE('j') }, mark_line, { .i = MARK_j } }, - { { NONE('\''), NONE('k') }, mark_line, { .i = MARK_k } }, - { { NONE('\''), NONE('l') }, mark_line, { .i = MARK_l } }, - { { NONE('\''), NONE('m') }, mark_line, { .i = MARK_m } }, - { { NONE('\''), NONE('n') }, mark_line, { .i = MARK_n } }, - { { NONE('\''), NONE('o') }, mark_line, { .i = MARK_o } }, - { { NONE('\''), NONE('p') }, mark_line, { .i = MARK_p } }, - { { NONE('\''), NONE('q') }, mark_line, { .i = MARK_q } }, - { { NONE('\''), NONE('r') }, mark_line, { .i = MARK_r } }, - { { NONE('\''), NONE('s') }, mark_line, { .i = MARK_s } }, - { { NONE('\''), NONE('t') }, mark_line, { .i = MARK_t } }, - { { NONE('\''), NONE('u') }, mark_line, { .i = MARK_u } }, - { { NONE('\''), NONE('v') }, mark_line, { .i = MARK_v } }, - { { NONE('\''), NONE('w') }, mark_line, { .i = MARK_w } }, - { { NONE('\''), NONE('x') }, mark_line, { .i = MARK_x } }, - { { NONE('\''), NONE('y') }, mark_line, { .i = MARK_y } }, - { { NONE('\''), NONE('z') }, mark_line, { .i = MARK_z } }, - { { NONE('\''), NONE('<') }, mark_line, { .i = MARK_SELECTION_START } }, - { { NONE('\''), NONE('>') }, mark_line, { .i = MARK_SELECTION_END } }, - { /* empty last element, array terminator */ }, + { "'a", mark_line, { .i = MARK_a } }, + { "'b", mark_line, { .i = MARK_b } }, + { "'c", mark_line, { .i = MARK_c } }, + { "'d", mark_line, { .i = MARK_d } }, + { "'e", mark_line, { .i = MARK_e } }, + { "'f", mark_line, { .i = MARK_f } }, + { "'g", mark_line, { .i = MARK_g } }, + { "'h", mark_line, { .i = MARK_h } }, + { "'i", mark_line, { .i = MARK_i } }, + { "'j", mark_line, { .i = MARK_j } }, + { "'k", mark_line, { .i = MARK_k } }, + { "'l", mark_line, { .i = MARK_l } }, + { "'m", mark_line, { .i = MARK_m } }, + { "'n", mark_line, { .i = MARK_n } }, + { "'o", mark_line, { .i = MARK_o } }, + { "'p", mark_line, { .i = MARK_p } }, + { "'q", mark_line, { .i = MARK_q } }, + { "'r", mark_line, { .i = MARK_r } }, + { "'s", mark_line, { .i = MARK_s } }, + { "'t", mark_line, { .i = MARK_t } }, + { "'u", mark_line, { .i = MARK_u } }, + { "'v", mark_line, { .i = MARK_v } }, + { "'w", mark_line, { .i = MARK_w } }, + { "'x", mark_line, { .i = MARK_x } }, + { "'y", mark_line, { .i = MARK_y } }, + { "'z", mark_line, { .i = MARK_z } }, + { "'<", mark_line, { .i = MARK_SELECTION_START } }, + { "'>", mark_line, { .i = MARK_SELECTION_END } }, + { /* empty last element, array terminator */ }, }; static KeyBinding vis_marks_set[] = { - { { NONE('m'), NONE('a') }, mark_set, { .i = MARK_a } }, - { { NONE('m'), NONE('b') }, mark_set, { .i = MARK_b } }, - { { NONE('m'), NONE('c') }, mark_set, { .i = MARK_c } }, - { { NONE('m'), NONE('d') }, mark_set, { .i = MARK_d } }, - { { NONE('m'), NONE('e') }, mark_set, { .i = MARK_e } }, - { { NONE('m'), NONE('f') }, mark_set, { .i = MARK_f } }, - { { NONE('m'), NONE('g') }, mark_set, { .i = MARK_g } }, - { { NONE('m'), NONE('h') }, mark_set, { .i = MARK_h } }, - { { NONE('m'), NONE('i') }, mark_set, { .i = MARK_i } }, - { { NONE('m'), NONE('j') }, mark_set, { .i = MARK_j } }, - { { NONE('m'), NONE('k') }, mark_set, { .i = MARK_k } }, - { { NONE('m'), NONE('l') }, mark_set, { .i = MARK_l } }, - { { NONE('m'), NONE('m') }, mark_set, { .i = MARK_m } }, - { { NONE('m'), NONE('n') }, mark_set, { .i = MARK_n } }, - { { NONE('m'), NONE('o') }, mark_set, { .i = MARK_o } }, - { { NONE('m'), NONE('p') }, mark_set, { .i = MARK_p } }, - { { NONE('m'), NONE('q') }, mark_set, { .i = MARK_q } }, - { { NONE('m'), NONE('r') }, mark_set, { .i = MARK_r } }, - { { NONE('m'), NONE('s') }, mark_set, { .i = MARK_s } }, - { { NONE('m'), NONE('t') }, mark_set, { .i = MARK_t } }, - { { NONE('m'), NONE('u') }, mark_set, { .i = MARK_u } }, - { { NONE('m'), NONE('v') }, mark_set, { .i = MARK_v } }, - { { NONE('m'), NONE('w') }, mark_set, { .i = MARK_w } }, - { { NONE('m'), NONE('x') }, mark_set, { .i = MARK_x } }, - { { NONE('m'), NONE('y') }, mark_set, { .i = MARK_y } }, - { { NONE('m'), NONE('z') }, mark_set, { .i = MARK_z } }, - { /* empty last element, array terminator */ }, + { "ma", mark_set, { .i = MARK_a } }, + { "mb", mark_set, { .i = MARK_b } }, + { "mc", mark_set, { .i = MARK_c } }, + { "md", mark_set, { .i = MARK_d } }, + { "me", mark_set, { .i = MARK_e } }, + { "mf", mark_set, { .i = MARK_f } }, + { "mg", mark_set, { .i = MARK_g } }, + { "mh", mark_set, { .i = MARK_h } }, + { "mi", mark_set, { .i = MARK_i } }, + { "mj", mark_set, { .i = MARK_j } }, + { "mk", mark_set, { .i = MARK_k } }, + { "ml", mark_set, { .i = MARK_l } }, + { "mm", mark_set, { .i = MARK_m } }, + { "mn", mark_set, { .i = MARK_n } }, + { "mo", mark_set, { .i = MARK_o } }, + { "mp", mark_set, { .i = MARK_p } }, + { "mq", mark_set, { .i = MARK_q } }, + { "mr", mark_set, { .i = MARK_r } }, + { "ms", mark_set, { .i = MARK_s } }, + { "mt", mark_set, { .i = MARK_t } }, + { "mu", mark_set, { .i = MARK_u } }, + { "mv", mark_set, { .i = MARK_v } }, + { "mw", mark_set, { .i = MARK_w } }, + { "mx", mark_set, { .i = MARK_x } }, + { "my", mark_set, { .i = MARK_y } }, + { "mz", mark_set, { .i = MARK_z } }, + { /* empty last element, array terminator */ }, }; static KeyBinding vis_mode_normal[] = { - { { NONE(ESC) }, cursors_clear, { } }, - { { CONTROL('K') }, cursors_new, { .i = -1 } }, - { { CONTROL('J') }, cursors_new, { .i = +1 } }, - { { CONTROL('A') }, cursors_align, { } }, - { { CONTROL('N') }, cursors_select, { } }, - { { CONTROL('P') }, cursors_remove, { } }, - { { CONTROL('w'), NONE('n') }, cmd, { .s = "open" } }, - { { CONTROL('w'), NONE('c') }, cmd, { .s = "q" } }, - { { CONTROL('w'), NONE('s') }, cmd, { .s = "split" } }, - { { CONTROL('w'), NONE('v') }, cmd, { .s = "vsplit" } }, - { { CONTROL('w'), NONE('j') }, call, { .f = editor_window_next } }, - { { CONTROL('w'), NONE('l') }, call, { .f = editor_window_next } }, - { { CONTROL('w'), NONE('k') }, call, { .f = editor_window_prev } }, - { { CONTROL('w'), NONE('h') }, call, { .f = editor_window_prev } }, - { { CONTROL('w'), CONTROL('j') }, call, { .f = editor_window_next } }, - { { CONTROL('w'), CONTROL('l') }, call, { .f = editor_window_next } }, - { { CONTROL('w'), CONTROL('k') }, call, { .f = editor_window_prev } }, - { { CONTROL('w'), CONTROL('w') }, call, { .f = editor_window_next } }, - { { CONTROL('w'), CONTROL('h') }, call, { .f = editor_window_prev } }, - { { CONTROL('w'), KEY(BACKSPACE) }, call, { .f = editor_window_prev } }, - { { CONTROL('B') }, wscroll, { .i = -PAGE } }, - { { CONTROL('F') }, wscroll, { .i = +PAGE } }, - { { CONTROL('U') }, wscroll, { .i = -PAGE_HALF } }, - { { CONTROL('D') }, wscroll, { .i = +PAGE_HALF } }, - { { CONTROL('E') }, wslide, { .i = -1 } }, - { { CONTROL('Y') }, wslide, { .i = +1 } }, - { { CONTROL('O') }, jumplist, { .i = -1 } }, - { { CONTROL('I') }, jumplist, { .i = +1 } }, - { { NONE('g'), NONE(';') }, changelist, { .i = -1 } }, - { { NONE('g'), NONE(',') }, changelist, { .i = +1 } }, - { { NONE('a') }, insertmode, { .i = MOVE_CHAR_NEXT } }, - { { NONE('A') }, insertmode, { .i = MOVE_LINE_END } }, - { { NONE('C') }, change, { .i = MOVE_LINE_END } }, - { { NONE('D') }, delete, { .i = MOVE_LINE_END } }, - { { NONE('I') }, insertmode, { .i = MOVE_LINE_START } }, - { { NONE('.') }, repeat, { NULL } }, - { { NONE('o') }, openline, { .i = MOVE_LINE_NEXT } }, - { { NONE('O') }, openline, { .i = MOVE_LINE_PREV } }, - { { NONE('J') }, join, { .i = MOVE_LINE_NEXT } }, - { { NONE('x') }, delete, { .i = MOVE_CHAR_NEXT } }, - { { NONE('r') }, replace, { NULL } }, - { { NONE('i') }, switchmode, { .i = VIS_MODE_INSERT } }, - { { NONE('v') }, switchmode, { .i = VIS_MODE_VISUAL } }, - { { NONE('V') }, switchmode, { .i = VIS_MODE_VISUAL_LINE } }, - { { NONE('R') }, switchmode, { .i = VIS_MODE_REPLACE } }, - { { NONE('S') }, operator_twice, { .i = OP_CHANGE } }, - { { NONE('s') }, change, { .i = MOVE_CHAR_NEXT } }, - { { NONE('Y') }, operator_twice, { .i = OP_YANK } }, - { { NONE('X') }, delete, { .i = MOVE_CHAR_PREV } }, - { { NONE('u') }, undo, { NULL } }, - { { CONTROL('R') }, redo, { NULL } }, - { { NONE('g'), NONE('+') }, later, { NULL } }, - { { NONE('g'), NONE('-') }, earlier, { NULL } }, - { { CONTROL('L') }, call, { .f = editor_draw } }, - { { NONE(':') }, prompt_cmd, { .s = "" } }, - { { NONE('Z'), NONE('Z') }, cmd, { .s = "wq" } }, - { { NONE('Z'), NONE('Q') }, cmd, { .s = "q!" } }, - { { NONE('z'), NONE('t') }, window, { .w = view_redraw_top } }, - { { NONE('z'), NONE('z') }, window, { .w = view_redraw_center } }, - { { NONE('z'), NONE('b') }, window, { .w = view_redraw_bottom } }, - { { NONE('q') }, macro_record, { NULL } }, - { { NONE('@') }, macro_replay, { NULL } }, - { { NONE('g'), NONE('v') }, selection_restore, { NULL } }, - { /* empty last element, array terminator */ }, + { "<Escape>", cursors_clear, { } }, + { "<C-k>", cursors_new, { .i = -1 } }, + { "<C-j>", cursors_new, { .i = +1 } }, + { "<C-a>", cursors_align, { } }, + { "<C-n>", cursors_select, { } }, + { "<C-p>", cursors_remove, { } }, + { "<C-w>n", cmd, { .s = "open" } }, + { "<C-w>c", cmd, { .s = "q" } }, + { "<C-w>s", cmd, { .s = "split" } }, + { "<C-w>v", cmd, { .s = "vsplit" } }, + { "<C-w>j", call, { .f = editor_window_next } }, + { "<C-w>l", call, { .f = editor_window_next } }, + { "<C-w>k", call, { .f = editor_window_prev } }, + { "<C-w>h", call, { .f = editor_window_prev } }, + { "<C-w><C-j>", call, { .f = editor_window_next } }, + { "<C-w><C-l>", call, { .f = editor_window_next } }, + { "<C-w><C-k>", call, { .f = editor_window_prev } }, + { "<C-w><C-w>", call, { .f = editor_window_next } }, + { "<C-w><C-h>", call, { .f = editor_window_prev } }, + { "<C-w><Backspace>", call, { .f = editor_window_prev } }, + { "<C-b>", wscroll, { .i = -PAGE } }, + { "<C-f>", wscroll, { .i = +PAGE } }, + { "<C-u>", wscroll, { .i = -PAGE_HALF } }, + { "<C-d>", wscroll, { .i = +PAGE_HALF } }, + { "<C-e>", wslide, { .i = -1 } }, + { "<C-y>", wslide, { .i = +1 } }, + { "<C-o>", jumplist, { .i = -1 } }, + { "<C-i>", jumplist, { .i = +1 } }, + { "g;", changelist, { .i = -1 } }, + { "g,", changelist, { .i = +1 } }, + { "a", insertmode, { .i = MOVE_CHAR_NEXT } }, + { "A", insertmode, { .i = MOVE_LINE_END } }, + { "C", change, { .i = MOVE_LINE_END } }, + { "D", delete, { .i = MOVE_LINE_END } }, + { "I", insertmode, { .i = MOVE_LINE_START } }, + { ".", repeat, { NULL } }, + { "o", openline, { .i = MOVE_LINE_NEXT } }, + { "O", openline, { .i = MOVE_LINE_PREV } }, + { "J", join, { .i = MOVE_LINE_NEXT } }, + { "x", delete, { .i = MOVE_CHAR_NEXT } }, + { "r", replace, { NULL } }, + { "i", switchmode, { .i = VIS_MODE_INSERT } }, + { "v", switchmode, { .i = VIS_MODE_VISUAL } }, + { "V", switchmode, { .i = VIS_MODE_VISUAL_LINE } }, + { "R", switchmode, { .i = VIS_MODE_REPLACE } }, + { "S", operator_twice, { .i = OP_CHANGE } }, + { "s", change, { .i = MOVE_CHAR_NEXT } }, + { "Y", operator_twice, { .i = OP_YANK } }, + { "X", delete, { .i = MOVE_CHAR_PREV } }, + { "u", undo, { NULL } }, + { "<C-r>", redo, { NULL } }, + { "g+", later, { NULL } }, + { "g-", earlier, { NULL } }, + { "<C-l>", call, { .f = editor_draw } }, + { ":", prompt_cmd, { .s = "" } }, + { "ZZ", cmd, { .s = "wq" } }, + { "ZQ", cmd, { .s = "q!" } }, + { "zt", window, { .w = view_redraw_top } }, + { "zz", window, { .w = view_redraw_center } }, + { "zb", window, { .w = view_redraw_bottom } }, + { "q", macro_record, { NULL } }, + { "@", macro_replay, { NULL } }, + { "gv", selection_restore, { NULL } }, + { /* empty last element, array terminator */ }, }; static KeyBinding vis_mode_visual[] = { - { { CONTROL('N') }, cursors_select_next, { } }, - { { CONTROL('X') }, cursors_select_skip, { } }, - { { CONTROL('P') }, cursors_remove, { } }, - { { NONE('I') }, cursors_split, { .i = -1 } }, - { { NONE('A') }, cursors_split, { .i = +1 } }, - { { KEY(BACKSPACE) }, operator, { .i = OP_DELETE } }, - { { KEY(DELETE) }, operator, { .i = OP_DELETE } }, - { { NONE(ESC) }, switchmode, { .i = VIS_MODE_NORMAL } }, - { { CONTROL('c') }, switchmode, { .i = VIS_MODE_NORMAL } }, - { { NONE('v') }, switchmode, { .i = VIS_MODE_NORMAL } }, - { { NONE('V') }, switchmode, { .i = VIS_MODE_VISUAL_LINE } }, - { { NONE(':') }, prompt_cmd, { .s = "'<,'>" } }, - { { CONTROL('H') }, operator, { .i = OP_DELETE } }, - { { NONE('d') }, operator, { .i = OP_DELETE } }, - { { NONE('x') }, operator, { .i = OP_DELETE } }, - { { NONE('y') }, operator, { .i = OP_YANK } }, - { { NONE('c') }, operator, { .i = OP_CHANGE } }, - { { NONE('r') }, operator, { .i = OP_CHANGE } }, - { { NONE('s') }, operator, { .i = OP_CHANGE } }, - { { NONE('J') }, operator, { .i = OP_JOIN } }, - { { NONE('o') }, selection_end, { NULL } }, + { "<C-n>", cursors_select_next, { } }, + { "<C-x>", cursors_select_skip, { } }, + { "<C-p>", cursors_remove, { } }, + { "I", cursors_split, { .i = -1 } }, + { "A", cursors_split, { .i = +1 } }, + { "<Backspace>", operator, { .i = OP_DELETE } }, + { "<DEL>", operator, { .i = OP_DELETE } }, + { "<Escape>", switchmode, { .i = VIS_MODE_NORMAL } }, + { "<C-c>", switchmode, { .i = VIS_MODE_NORMAL } }, + { "v", switchmode, { .i = VIS_MODE_NORMAL } }, + { "V", switchmode, { .i = VIS_MODE_VISUAL_LINE } }, + { ":", prompt_cmd, { .s = "'<,'>" } }, + { "<C-h>", operator, { .i = OP_DELETE } }, + { "d", operator, { .i = OP_DELETE } }, + { "x", operator, { .i = OP_DELETE } }, + { "y", operator, { .i = OP_YANK } }, + { "c", operator, { .i = OP_CHANGE } }, + { "r", operator, { .i = OP_CHANGE } }, + { "s", operator, { .i = OP_CHANGE } }, + { "J", operator, { .i = OP_JOIN } }, + { "o", selection_end, { NULL } }, { /* empty last element, array terminator */ }, }; @@ -479,9 +472,9 @@ static void vis_mode_visual_leave(Mode *new) { } static KeyBinding vis_mode_visual_line[] = { - { { NONE('v') }, switchmode, { .i = VIS_MODE_VISUAL } }, - { { NONE('V') }, switchmode, { .i = VIS_MODE_NORMAL } }, - { /* empty last element, array terminator */ }, + { "v", switchmode, { .i = VIS_MODE_VISUAL } }, + { "V", switchmode, { .i = VIS_MODE_NORMAL } }, + { /* empty last element, array terminator */ }, }; static void vis_mode_visual_line_enter(Mode *old) { @@ -503,28 +496,27 @@ static void vis_mode_visual_line_leave(Mode *new) { } static KeyBinding vis_mode_readline[] = { - { { KEY(BACKSPACE) }, delete, { .i = MOVE_CHAR_PREV } }, - { { NONE(ESC) }, switchmode, { .i = VIS_MODE_NORMAL } }, - { { CONTROL('c') }, switchmode, { .i = VIS_MODE_NORMAL } }, - { { CONTROL('D') }, delete , { .i = MOVE_CHAR_NEXT } }, - { { CONTROL('W') }, delete, { .i = MOVE_LONGWORD_START_PREV } }, - { { CONTROL('U') }, delete, { .i = MOVE_LINE_BEGIN } }, - { /* empty last element, array terminator */ }, + { "<Backspace>", delete, { .i = MOVE_CHAR_PREV } }, + { "<Escape>", switchmode, { .i = VIS_MODE_NORMAL } }, + { "<C-c>", switchmode, { .i = VIS_MODE_NORMAL } }, + { "<C-d>", delete , { .i = MOVE_CHAR_NEXT } }, + { "<C-w>", delete, { .i = MOVE_LONGWORD_START_PREV } }, + { "<C-u>", delete, { .i = MOVE_LINE_BEGIN } }, + { /* empty last element, array terminator */ }, }; static KeyBinding vis_mode_prompt[] = { - { { KEY(BACKSPACE) }, prompt_backspace,{ .s = NULL } }, - { { NONE('\n') }, prompt_enter, { NULL } }, - { { NONE('\r') }, prompt_enter, { NULL } }, - { { CONTROL('J') }, prompt_enter, { NULL } }, - { { KEY(UP) }, prompt_up, { NULL } }, - { { KEY(DOWN) }, prompt_down, { NULL } }, - { { KEY(HOME) }, movement, { .i = MOVE_FILE_BEGIN } }, - { { CONTROL('B') }, movement, { .i = MOVE_FILE_BEGIN } }, - { { KEY(END) }, movement, { .i = MOVE_FILE_END } }, - { { CONTROL('E') }, movement, { .i = MOVE_FILE_END } }, - { { NONE('\t') }, NULL, { NULL } }, - { /* empty last element, array terminator */ }, + { "<Backspace>", prompt_backspace,{ .s = NULL } }, + { "<Enter>", prompt_enter, { NULL } }, + { "<C-j>", prompt_enter, { NULL } }, + { "<Up>", prompt_up, { NULL } }, + { "<Down>", prompt_down, { NULL } }, + { "<Home>", movement, { .i = MOVE_FILE_BEGIN } }, + { "<C-b>", movement, { .i = MOVE_FILE_BEGIN } }, + { "<End>", movement, { .i = MOVE_FILE_END } }, + { "<C-e>", movement, { .i = MOVE_FILE_END } }, + { "<Tab>", NULL, { NULL } }, + { /* empty last element, array terminator */ }, }; static void vis_mode_prompt_input(const char *str, size_t len) { @@ -542,50 +534,52 @@ static void vis_mode_prompt_leave(Mode *new) { } static KeyBinding vis_mode_insert_register[] = { - { { CONTROL('R'), NONE('a') }, insert_register, { .i = REG_a } }, - { { CONTROL('R'), NONE('b') }, insert_register, { .i = REG_b } }, - { { CONTROL('R'), NONE('c') }, insert_register, { .i = REG_c } }, - { { CONTROL('R'), NONE('d') }, insert_register, { .i = REG_d } }, - { { CONTROL('R'), NONE('e') }, insert_register, { .i = REG_e } }, - { { CONTROL('R'), NONE('f') }, insert_register, { .i = REG_f } }, - { { CONTROL('R'), NONE('g') }, insert_register, { .i = REG_g } }, - { { CONTROL('R'), NONE('h') }, insert_register, { .i = REG_h } }, - { { CONTROL('R'), NONE('i') }, insert_register, { .i = REG_i } }, - { { CONTROL('R'), NONE('j') }, insert_register, { .i = REG_j } }, - { { CONTROL('R'), NONE('k') }, insert_register, { .i = REG_k } }, - { { CONTROL('R'), NONE('l') }, insert_register, { .i = REG_l } }, - { { CONTROL('R'), NONE('m') }, insert_register, { .i = REG_m } }, - { { CONTROL('R'), NONE('n') }, insert_register, { .i = REG_n } }, - { { CONTROL('R'), NONE('o') }, insert_register, { .i = REG_o } }, - { { CONTROL('R'), NONE('p') }, insert_register, { .i = REG_p } }, - { { CONTROL('R'), NONE('q') }, insert_register, { .i = REG_q } }, - { { CONTROL('R'), NONE('r') }, insert_register, { .i = REG_r } }, - { { CONTROL('R'), NONE('s') }, insert_register, { .i = REG_s } }, - { { CONTROL('R'), NONE('t') }, insert_register, { .i = REG_t } }, - { { CONTROL('R'), NONE('u') }, insert_register, { .i = REG_u } }, - { { CONTROL('R'), NONE('v') }, insert_register, { .i = REG_v } }, - { { CONTROL('R'), NONE('w') }, insert_register, { .i = REG_w } }, - { { CONTROL('R'), NONE('x') }, insert_register, { .i = REG_x } }, - { { CONTROL('R'), NONE('y') }, insert_register, { .i = REG_y } }, - { { CONTROL('R'), NONE('z') }, insert_register, { .i = REG_z } }, - { /* empty last element, array terminator */ }, + { "<C-r>a", insert_register, { .i = REG_a } }, + { "<C-r>b", insert_register, { .i = REG_b } }, + { "<C-r>c", insert_register, { .i = REG_c } }, + { "<C-r>d", insert_register, { .i = REG_d } }, + { "<C-r>e", insert_register, { .i = REG_e } }, + { "<C-r>f", insert_register, { .i = REG_f } }, + { "<C-r>g", insert_register, { .i = REG_g } }, + { "<C-r>h", insert_register, { .i = REG_h } }, + { "<C-r>i", insert_register, { .i = REG_i } }, + { "<C-r>j", insert_register, { .i = REG_j } }, + { "<C-r>k", insert_register, { .i = REG_k } }, + { "<C-r>l", insert_register, { .i = REG_l } }, + { "<C-r>m", insert_register, { .i = REG_m } }, + { "<C-r>n", insert_register, { .i = REG_n } }, + { "<C-r>o", insert_register, { .i = REG_o } }, + { "<C-r>p", insert_register, { .i = REG_p } }, + { "<C-r>q", insert_register, { .i = REG_q } }, + { "<C-r>r", insert_register, { .i = REG_r } }, + { "<C-r>s", insert_register, { .i = REG_s } }, + { "<C-r>t", insert_register, { .i = REG_t } }, + { "<C-r>u", insert_register, { .i = REG_u } }, + { "<C-r>v", insert_register, { .i = REG_v } }, + { "<C-r>w", insert_register, { .i = REG_w } }, + { "<C-r>x", insert_register, { .i = REG_x } }, + { "<C-r>y", insert_register, { .i = REG_y } }, + { "<C-r>z", insert_register, { .i = REG_z } }, + { /* empty last element, array terminator */ }, }; static KeyBinding vis_mode_insert[] = { - { { CONTROL('L') }, switchmode, { .i = VIS_MODE_NORMAL } }, - { { CONTROL('[') }, switchmode, { .i = VIS_MODE_NORMAL } }, - { { CONTROL('I') }, insert_tab, { NULL } }, - { { CONTROL('J') }, insert_newline, { NULL } }, - { { CONTROL('M') }, insert_newline, { NULL } }, - { { CONTROL('O') }, switchmode, { .i = VIS_MODE_OPERATOR } }, - { { CONTROL('V') }, insert_verbatim, { NULL } }, - { { CONTROL('D') }, operator_twice, { .i = OP_SHIFT_LEFT } }, - { { CONTROL('T') }, operator_twice, { .i = OP_SHIFT_RIGHT } }, - { { CONTROL('X'), CONTROL('E') }, wslide, { .i = -1 } }, - { { CONTROL('X'), CONTROL('Y') }, wslide, { .i = +1 } }, - { { NONE('\t') }, insert_tab, { NULL } }, - { { KEY(END) }, movement, { .i = MOVE_LINE_END } }, - { /* empty last element, array terminator */ }, + { "<Escape>", switchmode, { .i = VIS_MODE_NORMAL } }, + { "<C-l>", switchmode, { .i = VIS_MODE_NORMAL } }, + { "<C-[>", switchmode, { .i = VIS_MODE_NORMAL } }, + { "<C-i>", insert_tab, { NULL } }, + { "<C-j>", insert_newline, { NULL } }, + { "<C-m>", insert_newline, { NULL } }, + { "<Enter>", insert_newline, { NULL } }, + { "<C-o>", switchmode, { .i = VIS_MODE_OPERATOR } }, + { "<C-v>", insert_verbatim, { NULL } }, + { "<C-d>", operator_twice, { .i = OP_SHIFT_LEFT } }, + { "<C-t>", operator_twice, { .i = OP_SHIFT_RIGHT } }, + { "<C-x><C-e>", wslide, { .i = -1 } }, + { "<C-x><C-y>", wslide, { .i = +1 } }, + { "<Tab>", insert_tab, { NULL } }, + { "<End>", movement, { .i = MOVE_LINE_END } }, + { /* empty last element, array terminator */ }, }; static void vis_mode_insert_leave(Mode *old) { @@ -610,7 +604,7 @@ static void vis_mode_insert_input(const char *str, size_t len) { } static KeyBinding vis_mode_replace[] = { - { { NONE(ESC) }, switchmode, { .i = VIS_MODE_NORMAL } }, + { "<Escape>", switchmode, { .i = VIS_MODE_NORMAL } }, { /* empty last element, array terminator */ }, }; @@ -677,46 +671,46 @@ static Mode vis_modes[] = { [VIS_MODE_BASIC] = { .name = "BASIC", .parent = NULL, - .bindings = basic_movement, + .default_bindings = basic_movement, }, [VIS_MODE_MARK] = { .name = "MARK", .common_prefix = true, .parent = &vis_modes[VIS_MODE_BASIC], - .bindings = vis_marks, + .default_bindings = vis_marks, }, [VIS_MODE_MARK_LINE] = { .name = "MARK-LINE", .common_prefix = true, .parent = &vis_modes[VIS_MODE_MARK], - .bindings = vis_marks_line, + .default_bindings = vis_marks_line, }, [VIS_MODE_MOVE] = { .name = "MOVE", .parent = &vis_modes[VIS_MODE_MARK_LINE], - .bindings = vis_movements, + .default_bindings = vis_movements, }, [VIS_MODE_INNER_TEXTOBJ] = { .name = "INNER-TEXTOBJ", .common_prefix = true, .parent = &vis_modes[VIS_MODE_MOVE], - .bindings = vis_inner_textobjs, + .default_bindings = vis_inner_textobjs, }, [VIS_MODE_TEXTOBJ] = { .name = "TEXTOBJ", .common_prefix = true, .parent = &vis_modes[VIS_MODE_INNER_TEXTOBJ], - .bindings = vis_textobjs, + .default_bindings = vis_textobjs, }, [VIS_MODE_OPERATOR_OPTION] = { .name = "OPERATOR-OPTION", .parent = &vis_modes[VIS_MODE_TEXTOBJ], - .bindings = vis_operator_options, + .default_bindings = vis_operator_options, }, [VIS_MODE_OPERATOR] = { .name = "OPERATOR", .parent = &vis_modes[VIS_MODE_MOVE], - .bindings = vis_operators, + .default_bindings = vis_operators, .enter = vis_mode_operator_enter, .leave = vis_mode_operator_leave, .input = vis_mode_operator_input, @@ -725,25 +719,25 @@ static Mode vis_modes[] = { .name = "REGISTER", .common_prefix = true, .parent = &vis_modes[VIS_MODE_OPERATOR], - .bindings = vis_registers, + .default_bindings = vis_registers, }, [VIS_MODE_MARK_SET] = { .name = "MARK-SET", .common_prefix = true, .parent = &vis_modes[VIS_MODE_REGISTER], - .bindings = vis_marks_set, + .default_bindings = vis_marks_set, }, [VIS_MODE_NORMAL] = { .name = "NORMAL", .isuser = true, .parent = &vis_modes[VIS_MODE_MARK_SET], - .bindings = vis_mode_normal, + .default_bindings = vis_mode_normal, }, [VIS_MODE_VISUAL] = { .name = "--VISUAL--", .isuser = true, .parent = &vis_modes[VIS_MODE_REGISTER], - .bindings = vis_mode_visual, + .default_bindings = vis_mode_visual, .enter = vis_mode_visual_enter, .leave = vis_mode_visual_leave, .visual = true, @@ -752,7 +746,7 @@ static Mode vis_modes[] = { .name = "--VISUAL LINE--", .isuser = true, .parent = &vis_modes[VIS_MODE_VISUAL], - .bindings = vis_mode_visual_line, + .default_bindings = vis_mode_visual_line, .enter = vis_mode_visual_line_enter, .leave = vis_mode_visual_line_leave, .visual = true, @@ -760,13 +754,13 @@ static Mode vis_modes[] = { [VIS_MODE_READLINE] = { .name = "READLINE", .parent = &vis_modes[VIS_MODE_BASIC], - .bindings = vis_mode_readline, + .default_bindings = vis_mode_readline, }, [VIS_MODE_PROMPT] = { .name = "PROMPT", .isuser = true, .parent = &vis_modes[VIS_MODE_READLINE], - .bindings = vis_mode_prompt, + .default_bindings = vis_mode_prompt, .input = vis_mode_prompt_input, .enter = vis_mode_prompt_enter, .leave = vis_mode_prompt_leave, @@ -775,13 +769,13 @@ static Mode vis_modes[] = { .name = "INSERT-REGISTER", .common_prefix = true, .parent = &vis_modes[VIS_MODE_READLINE], - .bindings = vis_mode_insert_register, + .default_bindings = vis_mode_insert_register, }, [VIS_MODE_INSERT] = { .name = "--INSERT--", .isuser = true, .parent = &vis_modes[VIS_MODE_INSERT_REGISTER], - .bindings = vis_mode_insert, + .default_bindings = vis_mode_insert, .leave = vis_mode_insert_leave, .input = vis_mode_insert_input, .idle = vis_mode_insert_idle, @@ -791,7 +785,7 @@ static Mode vis_modes[] = { .name = "--REPLACE--", .isuser = true, .parent = &vis_modes[VIS_MODE_INSERT], - .bindings = vis_mode_replace, + .default_bindings = vis_mode_replace, .leave = vis_mode_replace_leave, .input = vis_mode_replace_input, .idle = vis_mode_insert_idle, @@ -18,7 +18,7 @@ PREFIX ?= /usr/local MANPREFIX = ${PREFIX}/share/man INCS = -I. -LIBS = -lc -lncursesw +LIBS = -lc -lncursesw -ltermkey OS = $(shell uname) @@ -230,6 +230,27 @@ void editor_suspend(Editor *ed) { ed->ui->suspend(ed->ui); } +bool editor_mode_map(Mode *mode, const char *name, KeyBinding *binding) { + return map_put(mode->bindings, name, binding); +} + +bool editor_mode_bindings(Mode *mode, KeyBinding **bindings) { + if (!mode->bindings) + mode->bindings = map_new(); + if (!mode->bindings) + return false; + bool success = true; + for (KeyBinding *kb = *bindings; kb->key; kb++) { + if (!editor_mode_map(mode, kb->key, kb)) + success = false; + } + return success; +} + +bool editor_mode_unmap(Mode *mode, const char *name) { + return map_delete(mode->bindings, name); +} + static void window_free(Win *win) { if (!win) return; @@ -27,11 +27,8 @@ typedef union { void (*f)(Editor*); /* generic editor commands */ } Arg; -#define MAX_KEYS 2 -typedef Key KeyCombo[MAX_KEYS]; - typedef struct { - KeyCombo key; + const char *key; void (*func)(const Arg *arg); const Arg arg; } KeyBinding; @@ -39,14 +36,13 @@ typedef struct { typedef struct Mode Mode; struct Mode { Mode *parent; /* if no match is found in this mode, search will continue there */ - KeyBinding *bindings; /* NULL terminated array of keybindings for this mode */ + Map *bindings; + KeyBinding *default_bindings; const char *name; /* descriptive, user facing name of the mode */ bool isuser; /* whether this is a user or internal mode */ bool common_prefix; /* whether the first key in this mode is always the same */ void (*enter)(Mode *old); /* called right before the mode becomes active */ void (*leave)(Mode *new); /* called right before the mode becomes inactive */ - bool (*unknown)(KeyCombo); /* called whenever a key combination is not found in this mode, - the return value determines whether parent modes will be searched */ void (*input)(const char*, size_t); /* called whenever a key is not found in this mode and all its parent modes */ void (*idle)(void); /* called whenever a certain idle time i.e. without any user input elapsed */ time_t idle_timeout; /* idle time in seconds after which the registered function will be called */ @@ -56,7 +52,7 @@ struct Mode { typedef struct { char *name; /* is used to match against argv[0] to enable this config */ Mode *mode; /* default mode in which the editor should start in */ - bool (*keypress)(Key*); /* called before any other keybindings are checked, + bool (*keypress)(const char *key); /* called before any other keybindings are checked, * return value decides whether key should be ignored */ } Config; @@ -237,6 +233,7 @@ struct Editor { Map *cmds; /* ":"-commands, used for unique prefix queries */ Map *options; /* ":set"-options */ Buffer buffer_repeat; /* holds data to repeat last insertion/replacement */ + Buffer input_queue; /* holds pending input keys */ Action action; /* current action which is in progress */ Action action_prev; /* last operator action used by the repeat '.' key */ @@ -256,6 +253,10 @@ void editor_draw(Editor*); void editor_update(Editor*); void editor_suspend(Editor*); +bool editor_mode_bindings(Mode*, KeyBinding**); +bool editor_mode_map(Mode*, const char *name, KeyBinding*); +bool editor_mode_unmap(Mode*, const char *name); + /* these function operate on the currently focused window but make sure * that all windows which show the affected region are redrawn too. */ void editor_insert_key(Editor*, const char *data, size_t len); @@ -6,6 +6,6 @@ typedef Buffer Macro; #define macro_release buffer_release #define macro_reset buffer_truncate -#define macro_append buffer_append +#define macro_append buffer_append0 #endif diff --git a/ui-curses.c b/ui-curses.c index 4705d08..b2fd099 100644 --- a/ui-curses.c +++ b/ui-curses.c @@ -3,6 +3,7 @@ #include <string.h> #include <signal.h> #include <locale.h> +#include <poll.h> #include <sys/ioctl.h> #include "ui.h" @@ -62,6 +63,8 @@ typedef struct { char info[255]; /* info message displayed at the bottom of the screen */ int width, height; /* terminal dimensions available for all windows */ enum UiLayout layout; /* whether windows are displayed horizontally or vertically */ + TermKey *termkey; /* libtermkey instance to handle keyboard input */ + char key[64]; /* string representation of last pressed key */ } UiCurses; struct UiCursesWin { @@ -518,6 +521,11 @@ static bool ui_init(Ui *ui, Editor *ed) { return true; } +static TermKey *ui_termkey_get(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + return uic->termkey; +} + static void ui_suspend(Ui *ui) { endwin(); raise(SIGSTOP); @@ -532,59 +540,51 @@ static bool ui_haskey(Ui *ui) { return c != ERR; } -static Key ui_getkey(Ui *ui) { - Key key = { .str = "", .code = 0 }; - int keycode = getch(), cur = 0; - if (keycode == ERR) - return key; - - if (keycode >= KEY_MIN) { - key.code = keycode; - } else { - key.str[cur++] = keycode; - int len = 1; - unsigned char keychar = keycode; - if (ISASCII(keychar)) len = 1; - else if (keychar == 0x1B || keychar >= 0xFC) len = 6; - else if (keychar >= 0xF8) len = 5; - else if (keychar >= 0xF0) len = 4; - else if (keychar >= 0xE0) len = 3; - else if (keychar >= 0xC0) len = 2; - len = MIN(len, LENGTH(key.str)); - - if (cur < len) { - nodelay(stdscr, TRUE); - for (int t; cur < len && (t = getch()) != ERR; cur++) - key.str[cur] = t; - nodelay(stdscr, FALSE); - } - - if (len == 1) { - switch (key.str[0]) { - case 127: - case CONTROL('H'): - key.code = KEY_BACKSPACE; - key.str[0] = '\0'; - break; - } - } +static const char *ui_getkey(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + TermKeyKey key; + TermKeyResult ret = termkey_getkey(uic->termkey, &key); + + if (ret == TERMKEY_RES_AGAIN) { + struct pollfd fd; + fd.fd = STDIN_FILENO; + fd.events = POLLIN; + if (poll(&fd, 1, termkey_get_waittime(uic->termkey)) == 0) + ret = termkey_getkey_force(uic->termkey, &key); } - - return key; + + if (ret != TERMKEY_RES_KEY) + return NULL; + termkey_strfkey(uic->termkey, uic->key, sizeof(uic->key), &key, TERMKEY_FORMAT_VIM); + return uic->key; } static void ui_terminal_save(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; + termkey_stop(uic->termkey); curs_set(1); reset_shell_mode(); } static void ui_terminal_restore(Ui *ui) { + UiCurses *uic = (UiCurses*)ui; reset_prog_mode(); wclear(stdscr); curs_set(0); + termkey_start(uic->termkey); } Ui *ui_curses_new(Color *colors) { + + UiCurses *uic = calloc(1, sizeof(UiCurses)); + Ui *ui = (Ui*)uic; + if (!uic) + return NULL; + if (!(uic->termkey = termkey_new(STDIN_FILENO, TERMKEY_FLAG_UTF8))) { + ui_curses_free(ui); + return NULL; + } + setlocale(LC_CTYPE, ""); if (!getenv("ESCDELAY")) set_escdelay(50); @@ -605,14 +605,10 @@ Ui *ui_curses_new(Color *colors) { would clear the screen (overwrite it with an empty / unused stdscr */ refresh(); - UiCurses *uic = calloc(1, sizeof(UiCurses)); - Ui *ui = (Ui*)uic; - if (!uic) - return NULL; - *ui = (Ui) { .init = ui_init, .free = ui_curses_free, + .termkey_get = ui_termkey_get, .suspend = ui_suspend, .resume = ui_resize, .resize = ui_resize, @@ -660,6 +656,8 @@ void ui_curses_free(Ui *ui) { while (uic->windows) ui_window_free((UiWin*)uic->windows); endwin(); + if (uic->termkey) + termkey_destroy(uic->termkey); free(uic); } diff --git a/ui-curses.h b/ui-curses.h index a2e1dc6..b9b63b1 100644 --- a/ui-curses.h +++ b/ui-curses.h @@ -6,21 +6,6 @@ #include "syntax.h" enum { - UI_KEY_DOWN = KEY_DOWN, - UI_KEY_UP = KEY_UP, - UI_KEY_LEFT = KEY_LEFT, - UI_KEY_RIGHT = KEY_RIGHT, - UI_KEY_HOME = KEY_HOME, - UI_KEY_BACKSPACE = KEY_BACKSPACE, - UI_KEY_DELETE = KEY_DC, - UI_KEY_PAGE_DOWN = KEY_NPAGE, - UI_KEY_PAGE_UP = KEY_PPAGE, - UI_KEY_END = KEY_END, - UI_KEY_SHIFT_LEFT = KEY_SLEFT, - UI_KEY_SHIFT_RIGHT = KEY_SRIGHT, -}; - -enum { UI_COLOR_DEFAULT = -1, UI_COLOR_BLACK = COLOR_BLACK, UI_COLOR_RED = COLOR_RED, @@ -15,13 +15,9 @@ enum UiOption { UI_OPTION_LINE_NUMBERS_RELATIVE = 1 << 1, }; -typedef struct { - char str[6]; /* UTF8 character or terminal escape code */ - int code; /* curses KEY_* constant */ -} Key; - #include <stdbool.h> #include <stdarg.h> +#include <termkey.h> #include "text.h" #include "view.h" #include "editor.h" @@ -44,10 +40,11 @@ struct Ui { void (*update)(Ui*); void (*suspend)(Ui*); void (*resume)(Ui*); - Key (*getkey)(Ui*); + const char* (*getkey)(Ui*); bool (*haskey)(Ui*); void (*terminal_save)(Ui*); void (*terminal_restore)(Ui*); + TermKey* (*termkey_get)(Ui*); }; struct UiWin { @@ -33,6 +33,7 @@ #include <sys/ioctl.h> #include <sys/mman.h> +#include <termkey.h> #include "ui-curses.h" #include "editor.h" #include "text-util.h" @@ -472,8 +473,8 @@ static bool vis_window_split(Win *win); #include "config.h" -static Key getkey(void); -static void keypress(Key *key); +static const char *getkey(void); +static bool keypress(const char *key); static void action_do(Action *a); static bool exec_command(char type, const char *cmdline); @@ -816,10 +817,10 @@ static void changelist(const Arg *arg) { static Macro *key2macro(const Arg *arg) { if (arg->i) return &vis->macros[arg->i]; - Key key = getkey(); - if (key.str[0] >= 'a' && key.str[0] <= 'z') - return &vis->macros[key.str[0] - 'a']; - if (key.str[0] == '@') + const char *key = getkey(); + if (key && key[0] >= 'a' && key[0] <= 'z') + return &vis->macros[key[0] - 'a']; + if (key && key[0] == '@') return vis->last_recording; return NULL; } @@ -828,7 +829,8 @@ static void macro_record(const Arg *arg) { if (vis->recording) { /* hack to remove last recorded key, otherwise upon replay * we would start another recording */ - vis->recording->len -= sizeof(Key); + // XXX: HACK + vis->recording->len--; vis->last_recording = vis->recording; vis->recording = NULL; } else { @@ -843,8 +845,7 @@ static void macro_replay(const Arg *arg) { Macro *macro = key2macro(arg); if (!macro || macro == vis->recording) return; - for (size_t i = 0; i < macro->len; i += sizeof(Key)) - keypress((Key*)(macro->data + i)); + keypress(macro->data); } static void suspend(const Arg *arg) { @@ -983,13 +984,13 @@ static void cursors_remove(const Arg *arg) { } static void replace(const Arg *arg) { - Key k = getkey(); - if (!k.str[0]) + const char *key = getkey(); + if (!key) return; action_reset(&vis->action_prev); vis->action_prev.op = &ops[OP_REPEAT_REPLACE]; - buffer_put(&vis->buffer_repeat, k.str, strlen(k.str)); - editor_replace_key(vis, k.str, strlen(k.str)); + buffer_put(&vis->buffer_repeat, key, strlen(key)); + editor_replace_key(vis, key, strlen(key)); text_snapshot(vis->win->file->text); } @@ -1042,12 +1043,12 @@ static void changecase(const Arg *arg) { } static void movement_key(const Arg *arg) { - Key k = getkey(); - if (!k.str[0]) { + const char *key = getkey(); + if (!key) { action_reset(&vis->action); return; } - strncpy(vis->search_char, k.str, sizeof(vis->search_char)); + strncpy(vis->search_char, key, sizeof(vis->search_char)); vis->last_totill = arg->i; vis->action.movement = &moves[arg->i]; action_do(&vis->action); @@ -1211,8 +1212,10 @@ static void prompt_backspace(const Arg *arg) { static void insert_verbatim(const Arg *arg) { int len = 0, count = 0, base; Rune rune = 0; - Key key = getkey(); - char buf[4], type = key.str[0]; + const char *key = getkey(); + if (!key) + return; + char buf[4], type = key[0]; switch (type) { case 'o': case 'O': @@ -1243,14 +1246,16 @@ static void insert_verbatim(const Arg *arg) { while (count-- > 0) { key = getkey(); int v = 0; - if (base == 8 && '0' <= key.str[0] && key.str[0] <= '7') - v = key.str[0] - '0'; - else if ((base == 10 || base == 16) && '0' <= key.str[0] && key.str[0] <= '9') - v = key.str[0] - '0'; - else if (base == 16 && 'a' <= key.str[0] && key.str[0] <= 'f') - v = 10 + key.str[0] - 'a'; - else if (base == 16 && 'A' <= key.str[0] && key.str[0] <= 'F') - v = 10 + key.str[0] - 'A'; + if (!key) + break; + else if (base == 8 && '0' <= key[0] && key[0] <= '7') + v = key[0] - '0'; + else if ((base == 10 || base == 16) && '0' <= key[0] && key[0] <= '9') + v = key[0] - '0'; + else if (base == 16 && 'a' <= key[0] && key[0] <= 'f') + v = 10 + key[0] - 'a'; + else if (base == 16 && 'A' <= key[0] && key[0] <= 'F') + v = 10 + key[0] - 'A'; else break; rune = rune * base + v; @@ -2475,68 +2480,63 @@ static void die(const char *errstr, ...) { exit(EXIT_FAILURE); } -static bool keymatch(Key *key0, Key *key1) { - return (key0->str[0] && memcmp(key0->str, key1->str, sizeof(key1->str)) == 0) || - (key0->code && key0->code == key1->code); -} - -static bool keyvalid(Key *k) { - return k && (k->str[0] || k->code); -} +static bool keypress(const char *input) { + if (!input) + return true; + TermKey *termkey = vis->ui->termkey_get(vis->ui); + char *keys = strdup(input), *start = keys, *cur = keys, *end; + if (!keys) + return true; -static KeyBinding *keybinding(Mode *mode, KeyCombo keys) { - int combolen = 0; - while (combolen < MAX_KEYS && keyvalid(&keys[combolen])) - combolen++; - for (; mode; mode = mode->parent) { - if (mode->common_prefix && !keymatch(&keys[0], &mode->bindings->key[0])) - continue; - for (KeyBinding *kb = mode->bindings; kb && keyvalid(&kb->key[0]); kb++) { - for (int k = 0; k < combolen; k++) { - if (!keymatch(&keys[k], &kb->key[k])) - break; - if (k == combolen - 1) - return kb; - } + TermKeyKey key; + bool prefix = false; + KeyBinding *binding = NULL; + + while (cur && *cur) { + /* first try to parse a special key of the form <Key> */ + if (*cur == '<' && (end = (char*)termkey_strpkey(termkey, cur+1, &key, TERMKEY_FORMAT_VIM)) && *end == '>') { + end++; + } else if (!(end = (char*)termkey_strpkey(termkey, cur, &key, TERMKEY_FORMAT_VIM))) { + // XXX: insert as document + free(keys); + return true; } - if (mode->unknown && !mode->unknown(keys)) - break; - } - return NULL; -} - -static void keypress(Key *key) { - static KeyCombo keys; - static int keylen; - - - keys[keylen++] = *key; - KeyBinding *action = keybinding(vis->mode, keys); - if (action) { - int combolen = 0; - while (combolen < MAX_KEYS && keyvalid(&action->key[combolen])) - combolen++; - if (keylen < combolen) - return; /* combo not yet complete */ - /* need to reset state before calling action->func in case - * it will call us (=keypress) again as e.g. macro_replay */ - keylen = 0; - memset(keys, 0, sizeof(keys)); - if (action->func) - action->func(&action->arg); - } else if (keylen == 1 && key->code == 0 && vis->mode->input) { - vis->mode->input(key->str, strlen(key->str)); + char tmp = *end; + *end = '\0'; + prefix = false; + binding = NULL; + + for (Mode *mode = vis->mode; mode && !binding && !prefix; mode = mode->parent) { + binding = map_get(mode->bindings, start); + /* "<" is never treated as a prefix because it is used to denote + * special key symbols */ + if (strcmp(cur, "<")) + prefix = !binding && map_contains(mode->bindings, start); + } + + if (binding) { /* exact match */ + binding->func(&binding->arg); + start = cur = end; + } else if (prefix) { /* incomplete key binding? */ + cur = end; + } else { /* no keybinding */ + if (vis->mode->input) + vis->mode->input(start, end - start); + start = cur = end; + } + + *end = tmp; } - keylen = 0; - memset(keys, 0, sizeof(keys)); + free(keys); + return !prefix; } -static Key getkey(void) { - Key key = vis->ui->getkey(vis->ui); - if (config->keypress && !config->keypress(&key)) - return (Key){ .str = "", .code = 0 }; +static const char *getkey(void) { + const char *key = vis->ui->getkey(vis->ui); + if (!key || (config->keypress && !config->keypress(key))) + return NULL; return key; } @@ -2609,8 +2609,14 @@ static void mainloop() { continue; } - Key key = getkey(); - keypress(&key); + + TermKey *termkey = vis->ui->termkey_get(vis->ui); + termkey_advisereadable(termkey); + const char *key; + while ((key = getkey())) { + if (buffer_append0(&vis->input_queue, key) && keypress(vis->input_queue.data)) + buffer_truncate(&vis->input_queue); + } if (vis->mode->idle) timeout = &idle; @@ -2630,6 +2636,12 @@ int main(int argc, char *argv[]) { } } + for (int i = 0; i < LENGTH(vis_modes); i++) { + Mode *mode = &vis_modes[i]; + if (!editor_mode_bindings(mode, &mode->default_bindings)) + die("Could not load bindings for mode: %s\n", mode->name); + } + if (!(vis = editor_new(ui_curses_new(colors)))) die("Could not allocate editor core\n"); |
