>From 5f92b7c647c7bbc70fc1eaa33a2e2793de8a453d Mon Sep 17 00:00:00 2001 From: Brand Huntsman Date: Thu, 30 Aug 2018 02:53:39 -0600 Subject: [PATCH 2/3] interpolate functions in string binds Signed-off-by: Brand Huntsman --- src/browser.c | 16 ++++++- src/global.c | 7 +++ src/help.c | 16 ++++++- src/nano.c | 12 ++++-- src/nano.h | 23 +++++++++- src/prompt.c | 14 +++++- src/proto.h | 7 ++- src/rcfile.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/winio.c | 98 +++++++++++++++++++++++++++++++++++++++--- 9 files changed, 302 insertions(+), 25 deletions(-) diff --git a/src/browser.c b/src/browser.c index 61c8ab09..6f11cd64 100644 --- a/src/browser.c +++ b/src/browser.c @@ -204,7 +204,7 @@ static int do_browser_shortcuts(const sc *shortcut) return DBS_GOTO_READ_DIRECTORY_CONTENTS; #ifdef ENABLE_NANORC } else if (shortcut->func == (functionptrtype)implant) { - implant(shortcut->expansion); + implant(do_browser_shortcuts, shortcut->expansion); #endif } else if (shortcut->func == do_exit) { /* Exit from the file browser. */ @@ -297,6 +297,20 @@ char *do_browser(char *path) old_selected = selected; +#ifdef ENABLE_NANORC + if (implant_stack && get_key_buffer_len() == 0) { + int result = do_next_implant(do_browser_shortcuts); + if (result == -1) { + unbound_key(kbinput); + } else if (result == 1) + /* Exit from the file browser. */ + break; + else if (result == DBS_GOTO_READ_DIRECTORY_CONTENTS) + goto read_directory_contents; + continue; + } +#endif + kbinput = get_kbinput(edit, ISSET(SHOW_CURSOR)); #ifdef ENABLE_MOUSE diff --git a/src/global.c b/src/global.c index 04755599..f23b8ed2 100644 --- a/src/global.c +++ b/src/global.c @@ -255,6 +255,8 @@ int menusymbols[NUMBER_OF_MENUS] = { MMAIN, MWHEREIS, MREPLACE, MREPLACEWITH, char *rcfile_with_errors = NULL; /* The first nanorc file, if any, that produced warnings. */ +implant_fragment *implant_stack = NULL; +void (*implant_func)(void) = NULL; #endif /* Return the number of entries in the shortcut list for a given menu. */ @@ -433,6 +435,11 @@ int the_code_for(void (*func)(void), int defaultval) /* Return a pointer to the function that is bound to the given key. */ functionptrtype func_from_key(int *kbinput) { +#ifdef ENABLE_NANORC + if (*kbinput == KEY_IMPLANT && implant_func) + return implant_func; +#endif + const sc *s = get_shortcut(kbinput); if (s) diff --git a/src/help.c b/src/help.c index 094391a7..6b2ba655 100644 --- a/src/help.c +++ b/src/help.c @@ -122,7 +122,7 @@ static int do_help_shortcuts(const sc *shortcut) do_findnext(); #ifdef ENABLE_NANORC } else if (shortcut->func == (functionptrtype)implant) { - implant(shortcut->expansion); + implant(do_help_shortcuts, shortcut->expansion); #endif } else if (shortcut->func == do_exit) { /* Exit from the help viewer. */ @@ -228,6 +228,19 @@ void do_help(void) lastmessage = HUSH; focusing = TRUE; +#ifdef ENABLE_NANORC + if (implant_stack && get_key_buffer_len() == 0) { + didfind = 0; + int result = do_next_implant(do_help_shortcuts); + if (result == -1) { + unbound_key(kbinput); + } else if (result == 1) + /* Exit from the help viewer. */ + break; + goto refresh; + } +#endif + /* Show the cursor when we searched and found something. */ kbinput = get_kbinput(edit, didfind == 1); didfind = 0; @@ -258,6 +271,7 @@ void do_help(void) break; } + refresh: currmenu = MHELP; edit_refresh(); diff --git a/src/nano.c b/src/nano.c index 64c286db..e6f854d8 100644 --- a/src/nano.c +++ b/src/nano.c @@ -1688,8 +1688,8 @@ static int do_edit_shortcuts(const sc *shortcut) #endif #ifdef ENABLE_NANORC if (shortcut->func == (functionptrtype)implant) { - implant(shortcut->expansion); - return 42; + implant(do_edit_shortcuts, shortcut->expansion); + return KEY_IMPLANT; } #endif #ifndef NANO_TINY @@ -1715,7 +1715,8 @@ static int do_edit_shortcuts(const sc *shortcut) } #endif /* Execute the function of the shortcut. */ - shortcut->func(); + if (!do_shortcut(shortcut)) + return ERR; #ifndef NANO_TINY /* When the marked region changes without Shift being held, @@ -1770,6 +1771,11 @@ int do_input(bool allow_funcs) /* The length of the input buffer. */ const sc *shortcut; +#ifdef ENABLE_NANORC + if (implant_stack && get_key_buffer_len() == 0) + return do_next_implant(do_edit_shortcuts); +#endif + /* Read in a keystroke, and show the cursor while waiting. */ input = get_kbinput(edit, VISIBLE); diff --git a/src/nano.h b/src/nano.h index e1042759..5b6c97db 100644 --- a/src/nano.h +++ b/src/nano.h @@ -426,6 +426,20 @@ typedef struct rcoption { } rcoption; #endif +#ifdef ENABLE_NANORC +typedef struct implant_fragment { + char *keycodes; + /* A string of keycodes inserted during implant expansion. */ + struct sc *shortcut; + /* A shortcut called during implant expansion. + * Only the func and toggle fields are valid. */ + bool shifted; + /* Whether this is a shifted shortcut fragment. */ + struct implant_fragment *next; + /* Next in the list. */ +} implant_fragment; +#endif + typedef struct sc { const char *keystr; /* The string that describes a keystroke, like "^C" or "M-R". */ @@ -445,8 +459,8 @@ typedef struct sc { * keep them in sequence. */ #endif #ifdef ENABLE_NANORC - char *expansion; - /* The string of keycodes to which this shortcut is expanded. */ + implant_fragment *expansion; + /* List of keycodes and shortcuts to which this shortcut is expanded. */ #endif struct sc *next; /* Next in the list. */ @@ -611,6 +625,11 @@ enum #define KEY_FLUSH KEY_F0 /* Nonexistent function key. */ #endif +#ifdef ENABLE_NANORC +/* An imaginary key for implanted shortcuts. */ +#define KEY_IMPLANT -42 +#endif + #ifndef NANO_TINY /* An imaginary key for when we get a SIGWINCH (window resize). */ #define KEY_WINCH -2 diff --git a/src/prompt.c b/src/prompt.c index 08ba5230..108dd177 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -81,7 +81,7 @@ static int do_statusbar_shortcuts(const sc *shortcut) ; #ifdef ENABLE_NANORC else if (shortcut->func == (functionptrtype)implant) - implant(shortcut->expansion); + implant(do_statusbar_shortcuts, shortcut->expansion); #endif else if (shortcut->func == do_verbatim_input) do_statusbar_verbatim_input(); @@ -99,7 +99,9 @@ static int do_statusbar_shortcuts(const sc *shortcut) * to TRUE to indicate that we're done after running or trying to * run its associated function. */ if (!ISSET(VIEW_MODE) || okay_for_view(shortcut)) - shortcut->func(); + /* Keep prompt open if invalid shortcut. */ + if (!do_shortcut(shortcut)) + return 0; return 1; } @@ -121,6 +123,14 @@ int do_statusbar_input(bool *finished) *finished = FALSE; +#ifdef ENABLE_NANORC + if (implant_stack && get_key_buffer_len() == 0) { + if (do_next_implant(do_statusbar_shortcuts)) + *finished = TRUE; + return KEY_IMPLANT; + } +#endif + /* Read in a character. */ input = get_kbinput(bottomwin, VISIBLE); diff --git a/src/proto.h b/src/proto.h index 3a1f5025..dc18edb8 100644 --- a/src/proto.h +++ b/src/proto.h @@ -180,6 +180,8 @@ extern char *homedir; extern char *statedir; #ifdef ENABLE_NANORC extern char *rcfile_with_errors; +extern implant_fragment *implant_stack; +extern void (*implant_func)(void); #endif typedef void (*functionptrtype)(void); @@ -615,8 +617,11 @@ void run_macro(void); size_t get_key_buffer_len(void); void put_back(int keycode); void unget_kbinput(int kbinput, bool metakey); +bool do_shortcut(const sc *shortcut); #ifdef ENABLE_NANORC -void implant(const char *string); +int do_next_implant(int (*do_shortcuts)(const sc *shortcut)); +void implant(int (*do_shortcuts)(const sc *shortcut), + implant_fragment *expansion); #endif int get_kbinput(WINDOW *win, bool showcursor); int parse_kbinput(WINDOW *win); diff --git a/src/rcfile.c b/src/rcfile.c index a504db5d..b098b953 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -338,6 +338,119 @@ bool is_universal(void (*func)(void)) func == do_tab || func == do_enter || func == do_verbatim_input); } +/* Free an implant_fragment list. */ +static void free_implant_fragments(implant_fragment *f) +{ + implant_fragment *next; + + while (f != NULL) { + next = f->next; + if (f->keycodes) free(f->keycodes); + if (f->shortcut) free(f->shortcut); + free(f); + f = next; + } +} + +/* Interpolate implant string into keycode and shortcut fragments. */ +static sc *interpolate_implant(char *ptr) +{ + sc *newsc, *s; + implant_fragment **next, *f; + const char *start = ptr; + char *current, *sigil, *fn; + + newsc = nmalloc(sizeof(sc)); + newsc->func = (functionptrtype)implant; + next = &newsc->expansion; +#ifndef NANO_TINY + newsc->toggle = 0; +#endif + + do { + /* Get keycode fragment, if one. */ + if (*ptr == '$' && *(ptr+1) == '$') { + /* Skip over $$, and begin keycode fragment with a single $. */ + current = ptr + 1; + ptr += 2; + } else + current = ptr; + while (*ptr != '\0' && *ptr != '$') + ptr++; + + if (ptr > current) { + /* This is a keycode fragment. */ + f = *next = nmalloc(sizeof(implant_fragment)); + f->keycodes = mallocstrncpy(NULL, current, ptr - current); + f->keycodes[ptr-current] = '\0'; + f->shortcut = NULL; + next = &f->next; + } else if (*ptr == '$') { + /* This is a shortcut fragment. */ + ptr++; + sigil = ptr; + if (*ptr != '{') { + rcfile_error(N_("Invalid implant shortcut at character %d"), + ptr - start); + *next = NULL; + free_implant_fragments(newsc->expansion); + free(newsc); + return NULL; + } + do { + /* Skip the { or space. */ + ptr++; + + /* A ! prefix indicates a shifted shortcut. */ + bool shifted = FALSE; + if (*ptr == '!') { + shifted = TRUE; + ptr++; + } + + /* Get function name. */ + current = ptr; + while (*ptr != '\0' && *ptr != ' ' && *ptr != '}') + ptr++; + + if (*ptr == '\0') { + rcfile_error(N_("Unterminated implant shortcut at character %d"), + sigil - start); + *next = NULL; + free_implant_fragments(newsc->expansion); + free(newsc); + return NULL; + } + + fn = mallocstrncpy(NULL, current, ptr - current); + fn[ptr-current] = '\0'; + s = strtosc(fn); + if (s == NULL) { + rcfile_error(N_("Cannot map name \"%s\" to a function"), + fn); + free(fn); + *next = NULL; + free_implant_fragments(newsc->expansion); + free(newsc); + return NULL; + } + free(fn); + + f = *next = nmalloc(sizeof(implant_fragment)); + f->keycodes = NULL; + f->shortcut = s; + f->shifted = shifted; + next = &f->next; + } while (*ptr == ' '); + ptr++; + } + } while (*ptr != '\0'); + + *next = NULL; + + return newsc; +} + /* Bind or unbind a key combo, to or from a function. */ void parse_binding(char *ptr, bool dobind) { @@ -407,18 +520,17 @@ void parse_binding(char *ptr, bool dobind) /* If the thing to bind starts with a double quote, it is a string, * otherwise it is the name of a function. */ if (*funcptr == '"') { - newsc = nmalloc(sizeof(sc)); - newsc->func = (functionptrtype)implant; - newsc->expansion = mallocstrcpy(NULL, funcptr + 1); -#ifndef NANO_TINY - newsc->toggle = 0; -#endif - } else + newsc = interpolate_implant(funcptr + 1); + + if (newsc == NULL) + goto free_things; + } else { newsc = strtosc(funcptr); - if (newsc == NULL) { - rcfile_error(N_("Cannot map name \"%s\" to a function"), funcptr); - goto free_things; + if (newsc == NULL) { + rcfile_error(N_("Cannot map name \"%s\" to a function"), funcptr); + goto free_things; + } } } @@ -488,6 +600,8 @@ void parse_binding(char *ptr, bool dobind) } free_things: + if (newsc) + free_implant_fragments(newsc->expansion); free(newsc); free(keycopy); } diff --git a/src/winio.c b/src/winio.c index 6908ee1d..c43bf5e6 100644 --- a/src/winio.c +++ b/src/winio.c @@ -286,12 +286,86 @@ void unget_kbinput(int kbinput, bool metakey) put_back(ESC_CODE); } -#ifdef ENABLE_NANORC -/* Insert the given string into the keyboard buffer. */ -void implant(const char *string) +/* Verify shortcut is valid for current menu and execute its function. */ +bool do_shortcut(const sc *shortcut) { - for (int i = strlen(string); i > 0; i--) - put_back(string[i - 1]); + const subnfunc *func = sctofunc(shortcut); + + if (func == NULL || (func->menus & currmenu) == 0) { + /* Shortcut function is not valid for current menu. + * Stop implanting all remaining fragments. */ + beep(); +#ifdef ENABLE_NANORC + implant_stack = NULL; +#endif + return FALSE; + } + + shortcut->func(); + return TRUE; +} + +#ifdef ENABLE_NANORC +/* Pop the next fragment off implant stack and process it. */ +int do_next_implant(int (*do_shortcuts)(const sc *shortcut)) +{ + implant_fragment *f = implant_stack; + + if (f == NULL) + return 0; + + implant_stack = (f->next ? f->next : NULL); + + if (f->keycodes) { + /* Insert keycode fragment into the keyboard buffer. */ + for (int i = strlen(f->keycodes); i > 0; i--) + put_back(f->keycodes[i - 1]); + + implant_func = NULL; + return 0; + } + + bool was_shifted = shift_held; + shift_held = f->shifted; + int result = do_shortcuts(f->shortcut); + shift_held = was_shifted; + if (result == ERR) + return ERR; + + implant_func = f->shortcut->func; + return result; +} + +/* Push implant fragments to implant stack. */ +void implant(int (*do_shortcuts)(const sc *shortcut), + implant_fragment *expansion) +{ + if (expansion == NULL) + return; + + if (implant_stack != NULL) { + /* An implant string referenced the bind for this implant. + * It is okay to do that if the bind is in the last keycode fragment + * or this implant only has one fragment. The bind was not in the + * last fragment since there are still fragments on the stack. */ + + if (expansion->next == NULL) { + /* This implant only has one fragment, save the current + * stack and process this fragment. */ + implant_fragment *stack = implant_stack; + implant_stack = expansion; + do_next_implant(do_shortcuts); + implant_stack = stack; + return; + } + + beep(); + implant_stack = NULL; + return; + } + + implant_stack = expansion; + do_next_implant(do_shortcuts); } #endif @@ -359,6 +433,11 @@ int parse_kbinput(WINDOW *win) meta_key = FALSE; shift_held = FALSE; +#ifdef ENABLE_NANORC + /* Prevent issues with func_from_key() if kbinput is same as KEY_IMPLANT. */ + implant_func = NULL; +#endif + /* Read in a character. */ kbinput = get_input(win, 1); @@ -1495,6 +1574,15 @@ int *get_verbatim_kbinput(WINDOW *win, size_t *kbinput_len) { int *retval; +#ifdef ENABLE_NANORC + if (implant_stack && get_key_buffer_len() == 0 && implant_stack->keycodes) { + /* Insert keycode fragment into the keyboard buffer. */ + for (int i = strlen(implant_stack->keycodes); i > 0; i--) + put_back(implant_stack->keycodes[i - 1]); + implant_stack = (implant_stack->next ? implant_stack->next : NULL); + } +#endif + /* Turn off flow control characters if necessary so that we can type * them in verbatim, and turn the keypad off if necessary so that we * don't get extended keypad values. */ -- 2.16.4