From 02d7ce506fc25117b19de3c3d783d87a5cf55e48 Mon Sep 17 00:00:00 2001
From: Benno Schulenberg <bensberg@telfort.nl>
Date: Wed, 9 Oct 2019 19:42:22 +0200
Subject: [PATCH] restored feature: a per-syntax 'fixer' command that
 processes the buffer

The command can be used to run some kind of formatter or corrector or
arranging tool on the buffer.  By default the command is bound to M-F.
The formatter/corrector/arranging program must be non-interactive.

This addresses https://savannah.gnu.org/bugs/?55365,
and addresses https://savannah.gnu.org/bugs/?54651.
---
 src/files.c  |  7 ++---
 src/global.c | 12 +++++++--
 src/nano.h   |  2 ++
 src/proto.h  |  4 ++-
 src/rcfile.c |  6 ++++-
 src/text.c   | 76 +++++++++++++++++++++++++++++++++++++++++++---------
 6 files changed, 88 insertions(+), 19 deletions(-)

diff --git a/src/files.c b/src/files.c
index 5f687645..ebf361f6 100644
--- a/src/files.c
+++ b/src/files.c
@@ -513,7 +513,8 @@ bool open_buffer(const char *filename, bool new_buffer)
 #ifdef ENABLE_SPELLER
 /* Open the specified file, and if that succeeds, remove the text of the marked
  * region or of the entire buffer and read the file contents into its place. */
-bool replace_buffer(const char *filename, undo_type action, bool marked)
+bool replace_buffer(const char *filename, undo_type action, bool marked,
+		const char *operation)
 {
 	linestruct *was_cutbuffer = cutbuffer;
 	int descriptor;
@@ -525,7 +526,7 @@ bool replace_buffer(const char *filename, undo_type action, bool marked)
 		return FALSE;
 
 #ifndef NANO_TINY
-	add_undo(COUPLE_BEGIN, "spelling correction");
+	add_undo(COUPLE_BEGIN, operation);
 #endif
 
 	/* When nothing is marked, start at the top of the buffer. */
@@ -550,7 +551,7 @@ bool replace_buffer(const char *filename, undo_type action, bool marked)
 	read_file(f, descriptor, filename, TRUE);
 
 #ifndef NANO_TINY
-	add_undo(COUPLE_END, "spelling correction");
+	add_undo(COUPLE_END, operation);
 #endif
 	return TRUE;
 }
diff --git a/src/global.c b/src/global.c
index c8b01c7e..b3a3c40e 100644
--- a/src/global.c
+++ b/src/global.c
@@ -693,6 +693,8 @@ void shortcut_init(void)
 	const char *lint_gist = N_("Invoke the linter, if available");
 	const char *prevlint_gist = N_("Go to previous linter msg");
 	const char *nextlint_gist = N_("Go to next linter msg");
+	const char *fixer_gist =
+		N_("Invoke a program to manipulate the buffer, if available");
 #endif
 #endif /* ENABLE_HELP */
 
@@ -1001,9 +1003,12 @@ void shortcut_init(void)
 		N_("Zap Text"), WITHORSANS(zap_gist), BLANKAFTER, NOVIEW);
 
 #ifdef ENABLE_COLOR
-	if (!ISSET(RESTRICTED))
+	if (!ISSET(RESTRICTED)) {
 		add_to_funcs(do_linter, MMAIN,
-				N_("To Linter"), WITHORSANS(lint_gist), BLANKAFTER, NOVIEW);
+				N_("To Linter"), WITHORSANS(lint_gist), TOGETHER, NOVIEW);
+		add_to_funcs(do_fixer, MMAIN,
+				N_("Manipulate"), WITHORSANS(fixer_gist), BLANKAFTER, NOVIEW);
+	}
 #endif
 #endif
 	add_to_funcs(do_savefile, MMAIN,
@@ -1124,6 +1129,7 @@ void shortcut_init(void)
 #endif
 #ifdef ENABLE_COLOR
 	add_to_sclist(MMAIN, "M-B", 0, do_linter, 0);
+	add_to_sclist(MMAIN, "M-F", 0, do_fixer, 0);
 #endif
 	add_to_sclist(MMAIN, "^C", 0, do_cursorpos_void, 0);
 	add_to_sclist(MMAIN, "^_", 0, do_gotolinecolumn_void, 0);
@@ -1502,6 +1508,8 @@ keystruct *strtosc(const char *input)
 #ifdef ENABLE_COLOR
 	else if (!strcasecmp(input, "linter"))
 		s->func = do_linter;
+	else if (!strcasecmp(input, "fixer"))
+		s->func = do_fixer;
 #endif
 	else if (!strcasecmp(input, "curpos"))
 		s->func = do_cursorpos_void;
diff --git a/src/nano.h b/src/nano.h
index 373ce5c2..f25e5382 100644
--- a/src/nano.h
+++ b/src/nano.h
@@ -234,6 +234,8 @@ typedef struct syntaxtype {
 		/* The list of libmagic results that this syntax applies to. */
 	char *linter;
 		/* The command with which to lint this type of file. */
+	char *fixer;
+		/* The command with which to "fix"/format/modify this type of file. */
 	char *tab;
 		/* What the Tab key should produce; NULL for default behavior. */
 #ifdef ENABLE_COMMENT
diff --git a/src/proto.h b/src/proto.h
index 14e4d139..c97cd863 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -276,7 +276,8 @@ void make_new_buffer(void);
 void set_modified(void);
 bool open_buffer(const char *filename, bool new_buffer);
 #ifdef ENABLE_SPELLER
-bool replace_buffer(const char *filename, undo_type action, bool marked);
+bool replace_buffer(const char *filename, undo_type action, bool marked,
+		const char *operation);
 #endif
 void prepare_for_display(void);
 #ifdef ENABLE_MULTIBUFFER
@@ -551,6 +552,7 @@ void do_spell(void);
 #endif
 #ifdef ENABLE_COLOR
 void do_linter(void);
+void do_fixer(void);
 #endif
 #ifndef NANO_TINY
 void do_wordlinechar_count(void);
diff --git a/src/rcfile.c b/src/rcfile.c
index eb8e842d..4a5d294e 100644
--- a/src/rcfile.c
+++ b/src/rcfile.c
@@ -333,6 +333,7 @@ void begin_new_syntax(char *ptr)
 	live_syntax->headers = NULL;
 	live_syntax->magics = NULL;
 	live_syntax->linter = NULL;
+	live_syntax->fixer = NULL;
 	live_syntax->tab = NULL;
 #ifdef ENABLE_COMMENT
 	live_syntax->comment = mallocstrcpy(NULL, GENERAL_COMMENT_CHARACTER);
@@ -955,6 +956,8 @@ bool parse_syntax_commands(char *keyword, char *ptr)
 #endif
 	} else if (strcasecmp(keyword, "linter") == 0)
 		pick_up_name("linter", ptr, &live_syntax->linter);
+	else if (strcasecmp(keyword, "fixer") == 0)
+		pick_up_name("fixer", ptr, &live_syntax->fixer);
 	else
 		return FALSE;
 
@@ -1110,7 +1113,8 @@ void parse_rcfile(FILE *rcstream, bool just_syntax, bool intros_only)
 								strcasecmp(keyword, "icolor") == 0 ||
 								strcasecmp(keyword, "comment") == 0 ||
 								strcasecmp(keyword, "tabgives") == 0 ||
-								strcasecmp(keyword, "linter") == 0)) {
+								strcasecmp(keyword, "linter") == 0 ||
+								strcasecmp(keyword, "fixer") == 0)) {
 			if (!opensyntax)
 				jot_error(N_("A '%s' command requires a preceding "
 									"'syntax' command"), keyword);
diff --git a/src/text.c b/src/text.c
index 7921eac7..295a6095 100644
--- a/src/text.c
+++ b/src/text.c
@@ -2534,28 +2534,31 @@ const char *do_int_speller(const char *tempfile_name)
 }
 
 /* Execute the given program, with the given temp file as last argument. */
-const char *treat(char *tempfile_name, char *theprogram)
+const char *treat(char *tempfile_name, char *theprogram, bool spelling)
 {
 	ssize_t lineno_save = openfile->current->lineno;
 	size_t current_x_save = openfile->current_x;
 	size_t pww_save = openfile->placewewant;
 	bool was_at_eol = (openfile->current->data[openfile->current_x] == '\0');
+	const char *msg = (spelling ? N_("spelling correction") : N_("manipulation"));
 	struct stat fileinfo;
-	time_t timestamp;
+	long timestamp_sec, timestamp_nsec;
 	static char **arguments = NULL;
 	pid_t thepid;
 	int program_status;
 
 	/* Get the timestamp and the size of the temporary file. */
 	stat(tempfile_name, &fileinfo);
-	timestamp = fileinfo.st_mtime;
+	timestamp_sec = (long)fileinfo.st_mtim.tv_sec;
+	timestamp_nsec = (long)fileinfo.st_mtim.tv_nsec;
 
 	/* If the number of bytes to check is zero, get out. */
 	if (fileinfo.st_size == 0)
 		return NULL;
 
-	/* Exit from curses mode. */
-	endwin();
+	/* The spell checker needs the screen, so exit from curses mode. */
+	if (spelling)
+		endwin();
 
 	construct_argument_list(&arguments, theprogram, tempfile_name);
 
@@ -2574,9 +2577,11 @@ const char *treat(char *tempfile_name, char *theprogram)
 	wait(&program_status);
 	block_sigwinch(FALSE);
 
-	/* Set the desired terminal state again, and reenter curses mode. */
-	terminal_init();
-	doupdate();
+	/* When needed, restore the terminal state and reenter curses mode. */
+	if (spelling) {
+		terminal_init();
+		doupdate();
+	}
 
 	if (!WIFEXITED(program_status) || WEXITSTATUS(program_status) != 0)
 		return invocation_error(theprogram);
@@ -2585,7 +2590,8 @@ const char *treat(char *tempfile_name, char *theprogram)
 	stat(tempfile_name, &fileinfo);
 
 	/* Read in the temporary file only when it changed. */
-	if (fileinfo.st_mtime != timestamp) {
+	if ((long)fileinfo.st_mtim.tv_sec != timestamp_sec ||
+				(long)fileinfo.st_mtim.tv_nsec != timestamp_nsec) {
 		bool replaced = FALSE;
 #ifndef NANO_TINY
 		/* Replace the marked text (or entire text) with the corrected text. */
@@ -2595,7 +2601,7 @@ const char *treat(char *tempfile_name, char *theprogram)
 									openfile->mark_x < openfile->current_x));
 			ssize_t was_mark_lineno = openfile->mark->lineno;
 
-			replaced = replace_buffer(tempfile_name, CUT, TRUE);
+			replaced = replace_buffer(tempfile_name, CUT, TRUE, msg);
 
 			/* Adjust the end point of the marked region for any change in
 			 * length of the region's last line. */
@@ -2608,7 +2614,7 @@ const char *treat(char *tempfile_name, char *theprogram)
 			openfile->mark = line_from_number(was_mark_lineno);
 		} else
 #endif
-			replaced = replace_buffer(tempfile_name, CUT_TO_EOF, FALSE);
+			replaced = replace_buffer(tempfile_name, CUT_TO_EOF, FALSE, msg);
 
 		/* Go back to the old position. */
 		goto_line_posx(lineno_save, current_x_save);
@@ -2667,7 +2673,10 @@ void do_spell(void)
 
 	blank_bottombars();
 
-	result_msg = (alt_speller ? treat(temp, alt_speller) : do_int_speller(temp));
+	if (alt_speller)
+		result_msg = treat(temp, alt_speller, TRUE);
+	else
+		result_msg = do_int_speller(temp);
 
 	unlink(temp);
 	free(temp);
@@ -3031,6 +3040,49 @@ void do_linter(void)
 	currmenu = MMOST;
 	titlebar(NULL);
 }
+
+#ifdef ENABLE_SPELLER
+/* Run a manipulation program on the contents of the buffer. */
+void do_fixer(void)
+{
+	FILE *stream;
+	char *temp_name;
+	bool okay = FALSE;
+	const char *result_msg;
+
+	if (in_restricted_mode())
+		return;
+
+	if (!openfile->syntax || !openfile->syntax->fixer) {
+		statusbar(_("No fixer is defined for this type of file"));
+		return;
+	}
+
+	temp_name = safe_tempfile(&stream);
+
+	if (temp_name != NULL)
+		okay = write_file(temp_name, stream, TRUE, OVERWRITE, TRUE);
+
+	if (!okay) {
+		statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
+		free(temp_name);
+		return;
+	}
+
+	/* Manipulation always happens on the whole buffer. */
+	openfile->mark = NULL;
+
+	result_msg = treat(temp_name, openfile->syntax->fixer, FALSE);
+
+	if (result_msg != NULL)
+		statusline(ALERT, result_msg);
+	else
+		statusbar(_("Buffer has been processed"));
+
+	unlink(temp_name);
+	free(temp_name);
+}
+#endif /* ENABLE_SPELLER */
 #endif /* ENABLE_COLOR */
 
 #ifndef NANO_TINY
-- 
2.23.0

