From dbf8546a73d9f67685d2f78eb40e9ebf656c7a7c Mon Sep 17 00:00:00 2001
From: Benno Schulenberg <bensberg@telfort.nl>
Date: Tue, 9 Aug 2022 17:04:18 +0200
Subject: [PATCH] input: interpret commands of the form {functionname} inside
 string binds

This allows specifying bindable functions in a string bind by name
instead of by the literal control code or escape sequence to which
they are bound, which makes for a much more readable string bind,
and also allows specifying functions that are not bound to any key.

The opening brace, {, is made into a special symbol inside a string
bind, and each literal occurrence there needs to be escaped as {{}.

This fulfills https://savannah.gnu.org/bugs/?61692.
Requested-by: Tasos Papastylianou <tpapastylianou@hotmail.com>

Original-idea-by: Brand Huntsman <alpha@qzx.com>
  https://lists.gnu.org/archive/html/nano-devel/2018-02/msg00006.html
---
 src/definitions.h | 12 +++++++++
 src/global.c      |  7 ++++++
 src/nano.c        |  4 +++
 src/prototypes.h  |  4 +++
 src/winio.c       | 63 +++++++++++++++++++++++++++++++++++++++++++----
 5 files changed, 85 insertions(+), 5 deletions(-)

diff --git a/src/definitions.h b/src/definitions.h
index 58012553..b064891a 100644
--- a/src/definitions.h
+++ b/src/definitions.h
@@ -214,6 +214,18 @@
 #define SHIFT_DELETE    0x45D
 #define SHIFT_TAB       0x45F
 
+/* A special keycode for when a string bind has been partially implanted. */
+#define MORE_PLANTS       0x4EA
+
+/* A special keycode for when a string bind has an unpaired opening brace. */
+#define MISSING_BRACE     0x4EB
+
+/* A special keycode for when a function in a string bind needs execution. */
+#define PLANTED_COMMAND   0x4EC
+
+/* A special keycode for when a function name in a string bind is invalid. */
+#define NO_SUCH_FUNCTION  0x4EF
+
 /* A special keycode for when <Tab> is pressed while the mark is on. */
 #define INDENT_KEY  0x4F1
 
diff --git a/src/global.c b/src/global.c
index 80adf6e9..df88c92d 100644
--- a/src/global.c
+++ b/src/global.c
@@ -266,6 +266,9 @@ char *startup_problem = NULL;
 #endif
 #ifdef ENABLE_NANORC
 char *custom_nanorc = NULL;
+
+char *commandname = NULL;
+keystruct *planted_shortcut = NULL;
 #endif
 
 bool spotlighted = FALSE;
@@ -466,6 +469,10 @@ const keystruct *get_shortcut(int *keycode)
 	if (bracketed_paste && *keycode != BRACKETED_PASTE_MARKER)
 		return NULL;
 #endif
+#ifdef ENABLE_NANORC
+	if (*keycode == PLANTED_COMMAND)
+		return planted_shortcut;
+#endif
 
 	for (keystruct *sc = sclist; sc != NULL; sc = sc->next) {
 		if ((sc->menus & currmenu) && *keycode == sc->keycode)
diff --git a/src/nano.c b/src/nano.c
index fe139486..1fe0f1f1 100644
--- a/src/nano.c
+++ b/src/nano.c
@@ -1267,6 +1267,10 @@ void unbound_key(int code)
 		/* TRANSLATORS: This refers to a sequence of escape codes
 		 * (from the keyboard) that nano does not recognize. */
 		statusline(AHEM, _("Unknown sequence"));
+	else if (code == MISSING_BRACE)
+		statusline(AHEM, _("Missing }"));
+	else if (code == NO_SUCH_FUNCTION)
+		statusline(AHEM, _("No such function: %s"), commandname);
 #ifndef NANO_TINY
 	else if (code > KEY_F0 && code < KEY_F0 + 25)
 		/* TRANSLATORS: This refers to an unbound function key. */
diff --git a/src/prototypes.h b/src/prototypes.h
index 473779ed..dbe8dad8 100644
--- a/src/prototypes.h
+++ b/src/prototypes.h
@@ -181,6 +181,9 @@ extern char *startup_problem;
 #endif
 #ifdef ENABLE_NANORC
 extern char *custom_nanorc;
+
+extern char *commandname;
+extern keystruct *planted_shortcut;
 #endif
 
 extern bool spotlighted;
@@ -441,6 +444,7 @@ void display_rcfile_errors(void);
 void jot_error(const char *msg, ...);
 #endif
 #ifdef ENABLE_NANORC
+keystruct *strtosc(const char *input);
 #ifdef ENABLE_COLOR
 void parse_one_include(char *file, syntaxtype *syntax);
 void grab_and_store(const char *kind, char *ptr, regexlisttype **storage);
diff --git a/src/winio.c b/src/winio.c
index e7dce7ff..98c4b4bc 100644
--- a/src/winio.c
+++ b/src/winio.c
@@ -64,6 +64,8 @@ static bool has_more = FALSE;
 		/* Whether the current line has more text after the displayed part. */
 static bool is_shorter = TRUE;
 		/* Whether a row's text is narrower than the screen's width. */
+static const char *plants_pointer = NULL;
+		/* Points into the expansion string for the current implantation. */
 #ifndef NANO_TINY
 static size_t sequel_column = 0;
 		/* The starting column of the next chunk when softwrapping. */
@@ -326,14 +328,60 @@ void put_back(int keycode)
 }
 
 #ifdef ENABLE_NANORC
-/* Insert the given string into the keyboard buffer. */
+/* Set up the given expansion string to be ingested by the keyboard routines. */
 void implant(const char *string)
 {
-	for (int i = strlen(string); i > 0; i--)
-		put_back((unsigned char)string[i - 1]);
+	plants_pointer = string;
+	put_back(MORE_PLANTS);
 
 	mute_modifiers = TRUE;
 }
+
+/* Continue processing an expansion string.  Returns either an error code,
+ * a plain keycode, or a placeholder for a command shortcut. */
+int get_code_from_plantation(void)
+{
+	if (*plants_pointer == '{') {
+		char *closing = strchr(plants_pointer + 1, '}');
+
+		if (!closing)
+			return MISSING_BRACE;
+
+		if (plants_pointer[1] == '{' && plants_pointer[2] == '}') {
+			plants_pointer += 3;
+			if (*plants_pointer != '\0')
+				put_back(MORE_PLANTS);
+			return '{';
+		}
+
+		free(commandname);
+		free(planted_shortcut);
+
+		commandname = measured_copy(plants_pointer + 1, closing - plants_pointer - 1);
+		planted_shortcut = strtosc(commandname);
+
+		if (planted_shortcut) {
+			plants_pointer = closing + 1;
+			if (*plants_pointer != '\0')
+				put_back(MORE_PLANTS);
+			return PLANTED_COMMAND;
+		} else
+			return NO_SUCH_FUNCTION;
+	} else {
+		char *opening = strchr(plants_pointer, '{');
+		int length = (opening ? opening - plants_pointer : strlen(plants_pointer));
+
+		if (opening)
+			put_back(MORE_PLANTS);
+
+		for (int index = length - 1; index >= 0; index--)
+			put_back((unsigned char)plants_pointer[index]);
+
+		plants_pointer += length;
+
+		return ERR;
+	}
+}
 #endif
 
 /* Return one code from the keystroke buffer.  If the buffer is empty
@@ -347,7 +395,11 @@ int get_input(WINDOW *frame)
 
 	if (waiting_codes > 0) {
 		waiting_codes--;
-		return *(nextcodes++);
+		if (*nextcodes == MORE_PLANTS) {
+			nextcodes++;
+			return get_code_from_plantation();
+		} else
+			return *(nextcodes++);
 	} else
 		return ERR;
 }
@@ -940,7 +992,8 @@ int parse_kbinput(WINDOW *frame)
 		} else if (++escapes > 2)
 			escapes = (last_escape_was_alone ? 0 : 1);
 		return ERR;
-	}
+	} else if (keycode == ERR)
+		return ERR;
 
 	if (escapes == 0) {
 		/* Most key codes in byte range cannot be special keys. */
-- 
2.35.3

