From af7ca02f361988f49943a0ca1efdf2ec1ccdfe57 Mon Sep 17 00:00:00 2001 From: nick black Date: Wed, 4 Dec 2019 23:43:19 -0500 Subject: [PATCH] Decode input escape sequences to special keys #78 Add the necessary input buffer, non-blocking reads, escape trie, and unit tests to support extended keys, including arrow keys. Update notcurses-input to print Unicode Control Glyphs instead of a blank space for control chars. --- include/notcurses.h | 11 ++++++++++- src/demo/panelreel.c | 2 +- src/lib/input.c | 42 ++++++++++++++++++++++++++---------------- src/lib/internal.h | 5 ++--- src/lib/notcurses.c | 33 +++++++++++++++++++++++++++------ tests/notcurses.cpp | 8 ++++++++ 6 files changed, 74 insertions(+), 27 deletions(-) diff --git a/include/notcurses.h b/include/notcurses.h index cb84083db..12e9da08e 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -154,7 +154,11 @@ typedef enum { NCKEY_RIGHT, NCKEY_DOWN, NCKEY_LEFT, - NCKEY_DC, // delete + NCKEY_DEL, + NCKEY_PGDOWN, + NCKEY_PGUP, + NCKEY_HOME, + NCKEY_END, // FIXME... } ncspecial_key; @@ -180,6 +184,11 @@ notcurses_getc_blocking(struct notcurses* n, cell* c, ncspecial_key* nkey){ return notcurses_getc(n, c, nkey, NULL, &sigmask); } +// Add this escape sequence to the trie, resolving to the specified specialkey. +// Exported mainly for the benefit of unit tests. +API int notcurses_add_input_escape(struct notcurses* nc, const char* esc, + ncspecial_key special); + // Refresh our idea of the terminal's dimensions, reshaping the standard plane // if necessary. Without a call to this function following a terminal resize // (as signaled via SIGWINCH), notcurses_render() might not function properly. diff --git a/src/demo/panelreel.c b/src/demo/panelreel.c index a9ca922ee..cb4e8c067 100644 --- a/src/demo/panelreel.c +++ b/src/demo/panelreel.c @@ -315,7 +315,7 @@ panelreel_demo_core(struct notcurses* nc, int efd, tabletctx** tctxs){ case NCKEY_RIGHT: ++x; if(panelreel_move(pr, x, y)){ --x; } break; case NCKEY_UP: panelreel_prev(pr); break; case NCKEY_DOWN: panelreel_next(pr); break; - case NCKEY_DC: kill_active_tablet(pr, tctxs); break; + case NCKEY_DEL: kill_active_tablet(pr, tctxs); break; default: ncplane_cursor_move_yx(w, 3, 2); ncplane_printf(w, "Unknown special (%d)\n", special); diff --git a/src/lib/input.c b/src/lib/input.c index 6ed29e8e7..d9d5b120f 100644 --- a/src/lib/input.c +++ b/src/lib/input.c @@ -48,21 +48,24 @@ void input_free_esctrie(esctrie** eptr){ } } -int input_add_escape(notcurses* nc, const char* esc, ncspecial_key special){ - esctrie** cur; - fprintf(stderr, "ADDING: %s for %d\n", esc, special); +int notcurses_add_input_escape(notcurses* nc, const char* esc, ncspecial_key special){ if(esc[0] != ESC || strlen(esc) < 2){ // assume ESC prefix + content return -1; } + esctrie** cur = &nc->inputescapes; do{ +//fprintf(stderr, "ADDING: %s (%zu) for %d\n", esc, strlen(esc), special); ++esc; int validate = *esc; if(validate < 0 || validate >= 0x80){ return -1; } - if(nc->inputescapes == NULL){ - cur = &nc->inputescapes; - }else if(validate){ + if(*cur == NULL){ + if((*cur = create_esctrie_node(NCKEY_INVALID)) == NULL){ + return -1; + } + } + if(validate){ if((*cur)->trie == NULL){ const size_t tsize = sizeof((*cur)->trie) * 0x80; (*cur)->trie = malloc(tsize); @@ -70,11 +73,6 @@ int input_add_escape(notcurses* nc, const char* esc, ncspecial_key special){ } cur = &(*cur)->trie[validate]; } - if(*cur == NULL){ - if((*cur = create_esctrie_node(NCKEY_INVALID)) == NULL){ - return -1; - } - } }while(*esc); if((*cur)->special){ // already had one here! return -1; @@ -94,13 +92,24 @@ handle_getc(notcurses* nc, cell* c, int kpress, ncspecial_key* special){ } if(kpress == ESC){ // FIXME delay a little waiting for more? - while(nc->inputbuf_occupied){ + const esctrie* esc = nc->inputescapes; + while(esc && esc->special == NCKEY_INVALID && nc->inputbuf_occupied){ int candidate = pop_input_keypress(nc); - // FIXME walk trie via candidate, exiting (and ungetc()ing) on failure -fprintf(stderr, "CANDIDATE: %c\n", candidate); +//fprintf(stderr, "CANDIDATE: %c\n", candidate); + if(esc->trie == NULL){ + esc = NULL; + }else if(candidate >= 0x80 || candidate < 0){ + esc = NULL; + }else{ + esc = esc->trie[candidate]; + } + } + if(esc && esc->special != NCKEY_INVALID){ + *special = esc->special; + return 1; } + // FIXME ungetc on failure! walk trie backwards or something } - *special = 0; if(kpress == 0x04){ // ctrl-d, treated as EOF return -1; } @@ -137,10 +146,11 @@ static int handle_input(notcurses* nc, cell* c, ncspecial_key* special){ int r; c->gcluster = 0; + *special = NCKEY_INVALID; // getc() returns unsigned chars cast to ints while(!input_queue_full(nc) && (r = getc(nc->ttyinfp)) >= 0){ nc->inputbuf[nc->inputbuf_write_at] = (unsigned char)r; -fprintf(stderr, "OCCUPY: %u@%u read: %d\n", nc->inputbuf_occupied, nc->inputbuf_write_at, nc->inputbuf[nc->inputbuf_write_at]); +//fprintf(stderr, "OCCUPY: %u@%u read: %d\n", nc->inputbuf_occupied, nc->inputbuf_write_at, nc->inputbuf[nc->inputbuf_write_at]); if(++nc->inputbuf_write_at == sizeof(nc->inputbuf) / sizeof(*nc->inputbuf)){ nc->inputbuf_write_at = 0; } diff --git a/src/lib/internal.h b/src/lib/internal.h index 6cff13b7b..47d38bc6a 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -104,6 +104,8 @@ typedef struct notcurses { char* right; // kcuf1 char* up; // kcuu1 char* down; // kcud1 + char* del; // delete + char* home; // home char* npage; // knp char* ppage; // kpp @@ -127,9 +129,6 @@ typedef struct notcurses { extern sig_atomic_t resize_seen; -// add this escape sequence to the trie, resolving to the specified specialkey -int input_add_escape(notcurses* nc, const char* esc, ncspecial_key special); - // free up the input escapes trie void input_free_esctrie(struct esctrie** trie); diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 728e14202..4e18f6147 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -520,12 +520,33 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){ term_verify_seq(&nc->italoff, "ritm"); term_verify_seq(&nc->op, "op"); term_verify_seq(&nc->clear, "clear"); - term_verify_seq(&nc->left, "kcub1"); - term_verify_seq(&nc->right, "kcuf1"); - term_verify_seq(&nc->up, "kcuu1"); - term_verify_seq(&nc->down, "kcud1"); - term_verify_seq(&nc->npage, "knp"); - term_verify_seq(&nc->ppage, "kpp"); + if(term_verify_seq(&nc->left, "kcub1") == 0){ + notcurses_add_input_escape(nc, nc->left, NCKEY_LEFT); + } + if(term_verify_seq(&nc->right, "kcuf1") == 0){ + notcurses_add_input_escape(nc, nc->right, NCKEY_RIGHT); + } + if(term_verify_seq(&nc->up, "kcuu1") == 0){ + notcurses_add_input_escape(nc, nc->up, NCKEY_UP); + } + if(term_verify_seq(&nc->down, "kcud1") == 0){ + notcurses_add_input_escape(nc, nc->down, NCKEY_DOWN); + } + if(term_verify_seq(&nc->del, "kdch1") == 0){ + notcurses_add_input_escape(nc, nc->down, NCKEY_DEL); + } + if(term_verify_seq(&nc->home, "kend") == 0){ + notcurses_add_input_escape(nc, nc->down, NCKEY_END); + } + if(term_verify_seq(&nc->home, "khome") == 0){ + notcurses_add_input_escape(nc, nc->down, NCKEY_HOME); + } + if(term_verify_seq(&nc->npage, "knp") == 0){ + notcurses_add_input_escape(nc, nc->down, NCKEY_PGDOWN); + } + if(term_verify_seq(&nc->ppage, "kpp") == 0){ + notcurses_add_input_escape(nc, nc->down, NCKEY_PGUP); + } // Some terminals cannot combine certain styles with colors. Don't advertise // support for the style in that case. int nocolor_stylemask = tigetnum("ncv"); diff --git a/tests/notcurses.cpp b/tests/notcurses.cpp index 77360ee66..b5713c969 100644 --- a/tests/notcurses.cpp +++ b/tests/notcurses.cpp @@ -1,6 +1,7 @@ #include #include #include +#include "internal.h" #include "main.h" class NotcursesTest : public :: testing::Test { @@ -103,3 +104,10 @@ TEST_F(NotcursesTest, TileScreenWithPlanes) { delete[] planes; ASSERT_EQ(0, notcurses_render(nc_)); } + +// build a trie up from terminfo(5)-style escape sequences +TEST_F(NotcursesTest, InputEscapesTrie) { + const char* khome = "\x1bOH"; + ASSERT_EQ(0, notcurses_add_input_escape(nc_, khome, NCKEY_HOME)); + // FIXME need be able to lookup +}