From edc526f5dbb460e34bd06fb14743efa23f29ebf8 Mon Sep 17 00:00:00 2001
From: Brand Huntsman <alpha@qzx.com>
Date: Mon, 13 May 2019 18:01:59 -0600
Subject: [PATCH 2/2] rcfile: fully parse a syntax file only when needed

When parsing an included syntax file, stop reading when a command other
than 'syntax', 'header' or 'magic' is encountered.  The syntax file is
fully parsed the first time that a file needs it.  Each 'extendsyntax'
command is stored for unloaded syntaxes and applied after the syntax
is loaded.

Closing a buffer does not unload the syntax, even if no longer used by
another buffer.

This addresses https://savannah.gnu.org/bugs/?54928.

Signed-off-by: Brand Huntsman <alpha@qzx.com>
---
 src/color.c  |  58 +++++++++++++---------
 src/nano.h   |  15 ++++++
 src/proto.h  |   4 +-
 src/rcfile.c | 135 ++++++++++++++++++++++++++++++++++++++++-----------
 4 files changed, 161 insertions(+), 51 deletions(-)

diff --git a/src/color.c b/src/color.c
index c879a392..94b41f08 100644
--- a/src/color.c
+++ b/src/color.c
@@ -39,11 +39,34 @@
 #define A_BANDAID  A_NORMAL
 #endif
 
+/* Assign pair numbers for the colors in the given syntax, giving identical
+ * color pairs the same number. */
+void set_syntax_colorpairs(syntaxtype *sint)
+{
+	int new_number = NUMBER_OF_ELEMENTS + 1;
+	colortype *ink;
+
+	for (ink = sint->color; ink != NULL; ink = ink->next) {
+		const colortype *beforenow = sint->color;
+
+		while (beforenow != ink && (beforenow->fg != ink->fg ||
+									beforenow->bg != ink->bg))
+			beforenow = beforenow->next;
+
+		if (beforenow != ink)
+			ink->pairnum = beforenow->pairnum;
+		else
+			ink->pairnum = new_number++;
+
+		ink->attributes |= COLOR_PAIR(ink->pairnum) | A_BANDAID;
+	}
+}
+
 /* Initialize the colors for nano's interface, and assign pair numbers
- * for the colors in each syntax. */
+ * for the colors in each loaded syntax. */
 void set_colorpairs(void)
 {
-	const syntaxtype *sint;
+	syntaxtype *sint;
 	bool using_defaults = FALSE;
 	size_t i;
 
@@ -82,27 +105,10 @@ void set_colorpairs(void)
 		free(color_combo[i]);
 	}
 
-	/* For each syntax, go through its list of colors and assign each
-	 * its pair number, giving identical color pairs the same number. */
-	for (sint = syntaxes; sint != NULL; sint = sint->next) {
-		colortype *ink;
-		int new_number = NUMBER_OF_ELEMENTS + 1;
-
-		for (ink = sint->color; ink != NULL; ink = ink->next) {
-			const colortype *beforenow = sint->color;
-
-			while (beforenow != ink && (beforenow->fg != ink->fg ||
-										beforenow->bg != ink->bg))
-				beforenow = beforenow->next;
-
-			if (beforenow != ink)
-				ink->pairnum = beforenow->pairnum;
-			else
-				ink->pairnum = new_number++;
-
-			ink->attributes |= COLOR_PAIR(ink->pairnum) | A_BANDAID;
-		}
-	}
+	/* For each loaded syntax, assign pair numbers to color combinations. */
+	for (sint = syntaxes; sint != NULL; sint = sint->next)
+		if (sint->filename == NULL)
+			set_syntax_colorpairs(sint);
 }
 
 /* Initialize the color information. */
@@ -267,6 +273,12 @@ void color_update(void)
 		}
 	}
 
+	/* When the syntax isn't loaded yet, parse it and initialize its colors. */
+	if (sint->filename != NULL) {
+		parse_one_include(sint->filename, sint);
+		set_syntax_colorpairs(sint);
+	}
+
 	openfile->syntax = sint;
 	openfile->colorstrings = (sint == NULL ? NULL : sint->color);
 
diff --git a/src/nano.h b/src/nano.h
index 5b89e33b..9e3af94a 100644
--- a/src/nano.h
+++ b/src/nano.h
@@ -216,9 +216,24 @@ typedef struct regexlisttype {
 		/* The next regex. */
 } regexlisttype;
 
+typedef struct extendsyntaxstruct {
+	char *filename;
+		/* The file where the syntax is extended. */
+	ssize_t lineno;
+		/* The number of the line of the extendsyntax command. */
+	char *data;
+		/* The text of the line. */
+	struct extendsyntaxstruct *next;
+		/* Next node. */
+} extendsyntaxstruct;
+
 typedef struct syntaxtype {
 	char *name;
 		/* The name of this syntax. */
+	char *filename;
+		/* File where the syntax is defined, or NULL if not an included file. */
+	struct extendsyntaxstruct *extendsyntax;
+		/* List of extendsyntax commands to apply when loaded. */
 	regexlisttype *extensions;
 		/* The list of extensions that this syntax applies to. */
 	regexlisttype *headers;
diff --git a/src/proto.h b/src/proto.h
index 1788f6a6..10e13664 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -465,10 +465,12 @@ int do_yesno_prompt(bool all, const char *msg);
 /* Most functions in rcfile.c. */
 #ifdef ENABLE_NANORC
 void display_rcfile_errors();
+bool parse_syntax_commands(char *keyword, char *ptr);
+void parse_one_include(char *file, syntaxtype *syntax);
 #ifdef ENABLE_COLOR
 void grab_and_store(const char *kind, char *ptr, regexlisttype **storage);
 #endif
-void parse_rcfile(FILE *rcstream, bool syntax_only);
+void parse_rcfile(FILE *rcstream, bool syntax_only, bool headers_only);
 void do_rcfiles(void);
 #endif /* ENABLE_NANORC */
 
diff --git a/src/rcfile.c b/src/rcfile.c
index 770b3b56..a374da7c 100644
--- a/src/rcfile.c
+++ b/src/rcfile.c
@@ -288,7 +288,7 @@ bool nregcomp(const char *regex, int compile_flags)
 
 /* Parse the next syntax name and its possible extension regexes from the
  * line at ptr, and add it to the global linked list of color syntaxes. */
-void parse_syntax(char *ptr)
+void parse_syntax(char *ptr, bool headers_only)
 {
 	char *nameptr = ptr;
 
@@ -324,6 +324,8 @@ void parse_syntax(char *ptr)
 	/* Initialize a new syntax struct. */
 	live_syntax = (syntaxtype *)nmalloc(sizeof(syntaxtype));
 	live_syntax->name = mallocstrcpy(NULL, nameptr);
+	live_syntax->filename = (headers_only ? strdup(nanorc) : NULL);
+	live_syntax->extendsyntax = NULL;
 	live_syntax->extensions = NULL;
 	live_syntax->headers = NULL;
 	live_syntax->magics = NULL;
@@ -539,7 +541,7 @@ bool is_good_file(char *file)
 
 #ifdef ENABLE_COLOR
 /* Read and parse one included syntax file. */
-static void parse_one_include(char *file)
+void parse_one_include(char *file, syntaxtype *syntax)
 {
 	FILE *rcstream;
 
@@ -560,7 +562,49 @@ static void parse_one_include(char *file)
 	nanorc = file;
 	lineno = 0;
 
-	parse_rcfile(rcstream, TRUE);
+	/* If this is the first pass, parse only the prologue. */
+	if (syntax == NULL) {
+		parse_rcfile(rcstream, TRUE, TRUE);
+		return;
+	}
+
+	live_syntax = syntax;
+	opensyntax = TRUE;
+	lastcolor = NULL;
+
+	/* Parse the included file fully. */
+	parse_rcfile(rcstream, TRUE, FALSE);
+	opensyntax = TRUE;
+
+	lastcolor = syntax->color;
+	if (lastcolor != NULL)
+		while (lastcolor->next != NULL)
+			lastcolor = lastcolor->next;
+
+	extendsyntaxstruct *es = syntax->extendsyntax;
+
+	/* Apply any stored extendsyntax commands. */
+	while (es != NULL) {
+		extendsyntaxstruct *next = es->next;
+		char *keyword = es->data, *ptr = parse_next_word(es->data);
+
+		nanorc = es->filename;
+		lineno = es->lineno;
+
+		if (!parse_syntax_commands(keyword, ptr))
+			rcfile_error(N_("Command \"%s\" not understood"), keyword);
+
+		free(es->filename);
+		free(es->data);
+		free(es);
+
+		es = next;
+	}
+
+	free(syntax->filename);
+	syntax->filename = NULL;
+	syntax->extendsyntax = NULL;
+	opensyntax = FALSE;
 }
 
 /* Expand globs in the passed name, and parse the resultant files. */
@@ -585,7 +629,7 @@ void parse_includes(char *ptr)
 	 * report an error if it's something other than zero matches. */
 	if (result == 0) {
 		for (size_t i = 0; i < files.gl_pathc; ++i)
-			parse_one_include(files.gl_pathv[i]);
+			parse_one_include(files.gl_pathv[i], NULL);
 	} else if (result != GLOB_NOMATCH)
 		rcfile_error(_("Error expanding %s: %s"), pattern, strerror(errno));
 
@@ -931,10 +975,31 @@ static void check_vitals_mapped(void)
 	}
 }
 
+/* Parse syntax-only commands. */
+bool parse_syntax_commands(char *keyword, char *ptr)
+{
+	if (strcasecmp(keyword, "comment") == 0)
+#ifdef ENABLE_COMMENT
+		pick_up_name("comment", ptr, &live_syntax->comment);
+#else
+		;
+#endif
+	else if (strcasecmp(keyword, "color") == 0)
+		parse_colors(ptr, NANO_REG_EXTENDED);
+	else if (strcasecmp(keyword, "icolor") == 0)
+		parse_colors(ptr, NANO_REG_EXTENDED | REG_ICASE);
+	else if (strcasecmp(keyword, "linter") == 0)
+		pick_up_name("linter", ptr, &live_syntax->linter);
+	else
+		return FALSE;
+
+	return TRUE;
+}
+
 /* Parse the rcfile, once it has been opened successfully at rcstream,
  * and close it afterwards.  If syntax_only is TRUE, allow the file to
  * to contain only color syntax commands. */
-void parse_rcfile(FILE *rcstream, bool syntax_only)
+void parse_rcfile(FILE *rcstream, bool syntax_only, bool headers_only)
 {
 	char *buf = NULL;
 	ssize_t len;
@@ -981,6 +1046,26 @@ void parse_rcfile(FILE *rcstream, bool syntax_only)
 				continue;
 			}
 
+			/* When the syntax isn't loaded yet, store extendsyntax commands. */
+			if (sint->filename != NULL) {
+				extendsyntaxstruct *newextendsyntax = nmalloc(sizeof(extendsyntaxstruct));;
+
+				newextendsyntax->filename = strdup(nanorc);
+				newextendsyntax->lineno = lineno;
+				newextendsyntax->data = strdup(ptr);
+				newextendsyntax->next = NULL;
+
+				if (sint->extendsyntax != NULL) {
+					extendsyntaxstruct *es = sint->extendsyntax;
+					while (es->next != NULL)
+						es = es->next;
+					es->next = newextendsyntax;
+				} else
+					sint->extendsyntax = newextendsyntax;
+
+				continue;
+			}
+
 			live_syntax = sint;
 			opensyntax = TRUE;
 
@@ -996,31 +1081,27 @@ void parse_rcfile(FILE *rcstream, bool syntax_only)
 
 		/* Try to parse the keyword. */
 		if (strcasecmp(keyword, "syntax") == 0) {
-			if (opensyntax && lastcolor == NULL)
-				rcfile_error(N_("Syntax \"%s\" has no color commands"),
-								live_syntax->name);
-			parse_syntax(ptr);
+			if (headers_only || !syntax_only) {
+				if (opensyntax && lastcolor == NULL && live_syntax->filename == NULL)
+					rcfile_error(N_("Syntax \"%s\" has no color commands"),
+									live_syntax->name);
+				parse_syntax(ptr, headers_only);
+			}
 		}
-		else if (strcasecmp(keyword, "header") == 0)
-			grab_and_store("header", ptr, &live_syntax->headers);
-		else if (strcasecmp(keyword, "magic") == 0)
+		else if (strcasecmp(keyword, "header") == 0) {
+			if (headers_only || !syntax_only)
+				grab_and_store("header", ptr, &live_syntax->headers);
+		} else if (strcasecmp(keyword, "magic") == 0)
 #ifdef HAVE_LIBMAGIC
-			grab_and_store("magic", ptr, &live_syntax->magics);
+			if (headers_only || !syntax_only)
+				grab_and_store("magic", ptr, &live_syntax->magics);
 #else
 			;
 #endif
-		else if (strcasecmp(keyword, "comment") == 0)
-#ifdef ENABLE_COMMENT
-			pick_up_name("comment", ptr, &live_syntax->comment);
-#else
+		else if (headers_only)
+			break;
+		else if (parse_syntax_commands(keyword, ptr))
 			;
-#endif
-		else if (strcasecmp(keyword, "color") == 0)
-			parse_colors(ptr, NANO_REG_EXTENDED);
-		else if (strcasecmp(keyword, "icolor") == 0)
-			parse_colors(ptr, NANO_REG_EXTENDED | REG_ICASE);
-		else if (strcasecmp(keyword, "linter") == 0)
-			pick_up_name("linter", ptr, &live_syntax->linter);
 		else if (syntax_only && (strcasecmp(keyword, "set") == 0 ||
 								strcasecmp(keyword, "unset") == 0 ||
 								strcasecmp(keyword, "bind") == 0 ||
@@ -1046,7 +1127,7 @@ void parse_rcfile(FILE *rcstream, bool syntax_only)
 
 #ifdef ENABLE_COLOR
 		/* If a syntax was extended, it stops at the end of the command. */
-		if (live_syntax != syntaxes)
+		if (!syntax_only && live_syntax != syntaxes)
 			opensyntax = FALSE;
 #endif
 
@@ -1210,7 +1291,7 @@ void parse_rcfile(FILE *rcstream, bool syntax_only)
 	}
 
 #ifdef ENABLE_COLOR
-	if (opensyntax && lastcolor == NULL)
+	if (opensyntax && lastcolor == NULL && !headers_only)
 		rcfile_error(N_("Syntax \"%s\" has no color commands"),
 						live_syntax->name);
 
@@ -1238,7 +1319,7 @@ void parse_one_nanorc(void)
 	/* If opening the file succeeded, parse it.  Otherwise, only
 	 * complain if the file actually exists. */
 	if (rcstream != NULL)
-		parse_rcfile(rcstream, FALSE);
+		parse_rcfile(rcstream, FALSE, FALSE);
 	else if (errno != ENOENT)
 		rcfile_error(N_("Error reading %s: %s"), nanorc, strerror(errno));
 }
-- 
2.20.1

