>From 2af8cd809996f15314a641c7f66c177c41e8292d Mon Sep 17 00:00:00 2001 From: Brand Huntsman Date: Thu, 6 Sep 2018 05:32:20 -0600 Subject: [PATCH] interpolate functions in string binds Signed-off-by: Brand Huntsman --- src/global.c | 6 +++ src/nano.c | 5 ++- src/nano.h | 30 +++++++++++++- src/prompt.c | 6 ++- src/proto.h | 4 +- src/rcfile.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/winio.c | 105 ++++++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 263 insertions(+), 23 deletions(-) diff --git a/src/global.c b/src/global.c index 431a2097..b92b560b 100644 --- a/src/global.c +++ b/src/global.c @@ -255,6 +255,7 @@ int menusymbols[NUMBER_OF_MENUS] = { MMAIN, MWHEREIS, MREPLACE, MREPLACEWITH, char *rcfile_with_errors = NULL; /* The first nanorc file, if any, that produced warnings. */ +void (*implant_shortcut_func)(void) = NULL; #endif /* Return the number of entries in the shortcut list for a given menu. */ @@ -433,6 +434,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_shortcut_func) + return implant_shortcut_func; +#endif + const sc *s = get_shortcut(kbinput); if (s) diff --git a/src/nano.c b/src/nano.c index cca042cb..8a478fba 100644 --- a/src/nano.c +++ b/src/nano.c @@ -1774,7 +1774,7 @@ int do_input(bool allow_funcs) #ifdef ENABLE_NANORC if (shortcut->func == (functionptrtype)implant) { implant(shortcut->expansion); - return 42; + return KEY_IMPLANT; } #endif #ifndef NANO_TINY @@ -1800,7 +1800,8 @@ int do_input(bool allow_funcs) } #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, diff --git a/src/nano.h b/src/nano.h index e1042759..aeb1f5c6 100644 --- a/src/nano.h +++ b/src/nano.h @@ -426,6 +426,27 @@ 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; + +typedef struct implant_stack { + struct implant_fragment *next_fragment; + /* The next fragment to expand in this expansion. */ + struct implant_stack *prev; + /* Previous expansion in the stack. */ +} implant_stack; +#endif + typedef struct sc { const char *keystr; /* The string that describes a keystroke, like "^C" or "M-R". */ @@ -445,8 +466,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 +632,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 e3e394ef..56f1f62f 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -169,9 +169,11 @@ int do_statusbar_input(bool *finished) /* Handle any other shortcut in the current menu, setting finished * 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(); *finished = TRUE; + if (!ISSET(VIEW_MODE) || okay_for_view(shortcut)) + /* Keep prompt open if invalid shortcut. */ + if (!do_shortcut(shortcut)) + *finished = FALSE; } } diff --git a/src/proto.h b/src/proto.h index 3a1f5025..187feb55 100644 --- a/src/proto.h +++ b/src/proto.h @@ -180,6 +180,7 @@ extern char *homedir; extern char *statedir; #ifdef ENABLE_NANORC extern char *rcfile_with_errors; +extern void (*implant_shortcut_func)(void); #endif typedef void (*functionptrtype)(void); @@ -615,8 +616,9 @@ 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); +void implant(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..7df79127 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -338,6 +338,115 @@ 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 *s, *newsc = nmalloc(sizeof(sc)); + implant_fragment *f, **next = &newsc->expansion; + const char *start = ptr; + char *current, *sigil, *fn; + + newsc->func = (functionptrtype)implant; +#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'; + 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 +516,15 @@ 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 +594,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..ed6c7203 100644 --- a/src/winio.c +++ b/src/winio.c @@ -59,6 +59,12 @@ static int *macro_buffer = NULL; /* A buffer where the recorded key codes are stored. */ static size_t macro_length = 0; /* The current length of the macro. */ +#ifdef ENABLE_NANORC +static implant_stack *implant_stack_top = NULL; + /* Stack of implanted string bind expansions. */ +static bool implant_did_shortcut = FALSE, implant_was_shifted = FALSE; + /* Used to reset shift_held after processing a shortcut fragment. */ +#endif /* Add the given code to the macro buffer. */ void add_to_macrobuffer(int code) @@ -286,12 +292,68 @@ 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. */ + beep(); +#ifdef ENABLE_NANORC + /* Stop implanting all remaining fragments, in all stacks. */ + while (implant_stack_top) { + implant_stack *prev = implant_stack_top->prev; + free(implant_stack_top); + implant_stack_top = prev; + } +#endif + return FALSE; + } + + shortcut->func(); + return TRUE; +} + +#ifdef ENABLE_NANORC +/* Advance to next fragment on stack. */ +static void advance_implant_fragment() +{ + implant_stack_top->next_fragment = implant_stack_top->next_fragment->next; + if (implant_stack_top->next_fragment == NULL) { + /* Curent expansion is empty, switch to previous. */ + implant_stack *prev = implant_stack_top->prev; + free(implant_stack_top); + implant_stack_top = prev; + } +} + +/* If next fragment is keycodes, implant them in keyboard buffer. */ +static bool implant_keycode_fragment() +{ + char *keycodes = implant_stack_top->next_fragment->keycodes; + + if (keycodes) { + /* Insert keycode fragment into the keyboard buffer. */ + for (int i = strlen(keycodes); i > 0; i--) + put_back(keycodes[i - 1]); + advance_implant_fragment(); + return TRUE; + } + return FALSE; +} + +/* Begin processing an implant. */ +void implant(implant_fragment *expansion) +{ + if (expansion == NULL) + return; + + /* Push expansion to top of implant stack. */ + implant_stack *s = nmalloc(sizeof(implant_stack)); + s->next_fragment = expansion; + s->prev = implant_stack_top; + implant_stack_top = s; } #endif @@ -303,6 +365,16 @@ int *get_input(WINDOW *win, size_t input_len) { int *input; +#ifdef ENABLE_NANORC + if (implant_did_shortcut){ + shift_held = implant_was_shifted; + implant_did_shortcut = FALSE; + } + implant_shortcut_func = NULL; + if (implant_stack_top && get_key_buffer_len() == 0) + implant_keycode_fragment(); +#endif + if (key_buffer_len == 0 && win != NULL) read_keys_from(win); @@ -333,6 +405,17 @@ int get_kbinput(WINDOW *win, bool showcursor) reveal_cursor = showcursor; +#ifdef ENABLE_NANORC + if (implant_did_shortcut){ + shift_held = implant_was_shifted; + implant_did_shortcut = FALSE; + } + implant_shortcut_func = NULL; + if (implant_stack_top && get_key_buffer_len() == 0) + if (!implant_keycode_fragment()) + return KEY_IMPLANT; +#endif + /* Extract one keystroke from the input stream. */ while (kbinput == ERR) kbinput = parse_kbinput(win); @@ -1757,6 +1840,18 @@ const sc *get_shortcut(int *kbinput) *kbinput, meta_key ? "TRUE" : "FALSE"); #endif +#ifdef ENABLE_NANORC + if (implant_stack_top && *kbinput == KEY_IMPLANT) { + s = implant_stack_top->next_fragment->shortcut; + implant_shortcut_func = s->func; + implant_did_shortcut = TRUE; + implant_was_shifted = shift_held; + shift_held = implant_stack_top->next_fragment->shifted; + advance_implant_fragment(); + return s; + } +#endif + /* Plain characters cannot be shortcuts, so just skip those. */ if (!meta_key && (*kbinput & 0x7F) >= 0x20 && *kbinput <= 0xFF) return NULL; -- 2.16.4