From fa63e6f3a1ec32c8b0ac4a0fa1bbe3774ea0cc3a Mon Sep 17 00:00:00 2001
From: Benno Schulenberg <bensberg@telfort.nl>
Date: Fri, 27 Mar 2020 11:36:07 +0100
Subject: [PATCH 2/2] new feature: "knots" -- a kind of bookmarks, that can be
 cycled through

With M-' a knot is placed at the current line -- or removed when there
already is one.  With M-PageUp / M-PageDown one can jump to the nearest
knot in the upward and downward direction.  The search for a knot wraps
at the top and bottom.  A knot is not visible in any way.

This fulfills https://savannah.gnu.org/bugs/?57577.
Requested-by: Ken Tyler <kent@werple.net.au>
---
 src/cut.c    |  15 +++++++-
 src/files.c  |   5 +++
 src/global.c |  13 +++++++
 src/nano.h   |  10 ++++++
 src/proto.h  |   4 +++
 src/text.c   | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 146 insertions(+), 1 deletion(-)

diff --git a/src/cut.c b/src/cut.c
index b5231371..b8d65695 100644
--- a/src/cut.c
+++ b/src/cut.c
@@ -87,6 +87,9 @@ void do_deletion(undo_type action)
 			openfile->mark = openfile->current;
 			openfile->mark_x += openfile->current_x;
 		}
+
+		/* Subtract 1 from any knots after the current line. */
+		slide_knots_after(openfile->current->lineno, -1, 0, openfile->current_x);
 #endif
 		unlink_node(joining);
 		renumber_from(openfile->current);
@@ -252,6 +255,9 @@ void extract_segment(linestruct *top, size_t top_x, linestruct *bot, size_t bot_
 
 	if (top == bot && top_x == bot_x)
 		return;
+
+	/* Adjust any knots that are after or in the cut piece. */
+	slide_knots_after(bot->lineno, top->lineno - bot->lineno, top->lineno, top_x);
 #endif
 
 	/* Reduce the buffer to cover just the text that needs to be extracted. */
@@ -331,8 +337,10 @@ void ingraft_buffer(linestruct *topline)
 	/* Remember whether mark and cursor are on the same line, and their order. */
 	bool right_side_up = (openfile->mark && mark_is_before_cursor());
 	bool same_line = (openfile->mark == openfile->current);
+	size_t was_lineno = openfile->current->lineno;
 #endif
 	size_t was_x = openfile->current_x;
+	size_t count = 0;
 
 	/* Partition the buffer so that it contains no text, then delete it.*/
 	partition_buffer(openfile->current, openfile->current_x,
@@ -342,8 +350,10 @@ void ingraft_buffer(linestruct *topline)
 	/* Replace the current buffer with the passed buffer. */
 	openfile->filetop = topline;
 	openfile->filebot = topline;
-	while (openfile->filebot->next != NULL)
+	while (openfile->filebot->next != NULL) {
 		openfile->filebot = openfile->filebot->next;
+		count++;
+	}
 
 	/* Put the cursor at the end of the pasted text. */
 	openfile->current = openfile->filebot;
@@ -364,6 +374,9 @@ void ingraft_buffer(linestruct *topline)
 		} else
 			openfile->mark = openfile->filetop;
 	}
+
+	/* Adjust any knots that are after the grafting point. */
+	slide_knots_after(was_lineno, count, was_lineno, was_x);
 #endif
 
 	/* Add the number of characters in the copied text to the file size. */
diff --git a/src/files.c b/src/files.c
index cbeba2ed..f6624ade 100644
--- a/src/files.c
+++ b/src/files.c
@@ -87,6 +87,11 @@ void make_new_buffer(void)
 
 	openfile->fmt = NIX_FILE;
 
+	openfile->origin = nmalloc(sizeof(knotstruct));
+	openfile->origin->lineno = 0;
+	openfile->origin->prev = openfile->origin;
+	openfile->origin->next = openfile->origin;
+
 	openfile->undotop = NULL;
 	openfile->current_undo = NULL;
 	openfile->last_saved = NULL;
diff --git a/src/global.c b/src/global.c
index e8fa4629..e94255aa 100644
--- a/src/global.c
+++ b/src/global.c
@@ -670,6 +670,9 @@ void shortcut_init(void)
 #ifndef NANO_TINY
 	const char *recordmacro_gist = N_("Start/stop recording a macro");
 	const char *runmacro_gist = N_("Run the last recorded macro");
+	const char *knot_gist = N_("Place or remove a knot at the current line");
+	const char *upknot_gist = N_("Jump backward to the nearest knot");
+	const char *downknot_gist = N_("Jump forward to the nearest knot");
 #endif
 	const char *case_gist = N_("Toggle the case sensitivity of the search");
 	const char *reverse_gist = N_("Reverse the direction of the search");
@@ -1023,6 +1026,13 @@ void shortcut_init(void)
 	add_to_funcs(run_macro, MMAIN,
 		N_("Run Macro"), WITHORSANS(runmacro_gist), BLANKAFTER, VIEW);
 
+	add_to_funcs(make_knot, MMAIN,
+		N_("Knot"), WITHORSANS(knot_gist), TOGETHER, VIEW);
+	add_to_funcs(up_to_knot, MMAIN,
+		N_("Up to knot"), WITHORSANS(upknot_gist), TOGETHER, VIEW);
+	add_to_funcs(down_to_knot, MMAIN,
+		N_("Down to knot"), WITHORSANS(downknot_gist), BLANKAFTER, VIEW);
+
 	add_to_funcs(zap_text, MMAIN,
 		N_("Zap Text"), WITHORSANS(zap_gist), BLANKAFTER, NOVIEW);
 
@@ -1188,6 +1198,9 @@ void shortcut_init(void)
 	add_to_sclist(MMAIN, "Sh-Tab", SHIFT_TAB, do_unindent, 0);
 	add_to_sclist(MMAIN, "M-:", 0, record_macro, 0);
 	add_to_sclist(MMAIN, "M-;", 0, run_macro, 0);
+	add_to_sclist(MMAIN, "M-'", 0, make_knot, 0);
+	add_to_sclist(MMAIN, "M-PgUp", ALT_PAGEUP, up_to_knot, 0);
+	add_to_sclist(MMAIN, "M-PgDn", ALT_PAGEDOWN, down_to_knot, 0);
 	add_to_sclist(MMAIN, "M-U", 0, do_undo, 0);
 	add_to_sclist(MMAIN, "M-E", 0, do_redo, 0);
 	add_to_sclist(MMAIN, "Sh-^Del", CONTROL_SHIFT_DELETE, chop_previous_word, 0);
diff --git a/src/nano.h b/src/nano.h
index 2815afc1..a0b654b6 100644
--- a/src/nano.h
+++ b/src/nano.h
@@ -294,6 +294,14 @@ typedef struct linestruct {
 } linestruct;
 
 #ifndef NANO_TINY
+typedef struct knotstruct {
+	size_t lineno;
+		/* The number of the line where this knot is placed. */
+	struct knotstruct *prev;
+	struct knotstruct *next;
+		/* Links to the neighbouring knots in the circular list of knots. */
+} knotstruct;
+
 typedef struct groupstruct {
 	ssize_t top_line;
 		/* First line of group. */
@@ -385,6 +393,8 @@ typedef struct openfilestruct {
 		/* The file's format -- Unix or DOS or Mac or mixed. */
 	char *lock_filename;
 		/* The path of the lockfile, if we created one. */
+	knotstruct *origin;
+		/* The base element of a circular list of knots (bookmarks). */
 	undostruct *undotop;
 		/* The top of the undo list. */
 	undostruct *current_undo;
diff --git a/src/proto.h b/src/proto.h
index 17c69c6b..8298a7d4 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -490,6 +490,10 @@ void do_find_bracket(void);
 /* Most functions in text.c. */
 #ifndef NANO_TINY
 void do_mark(void);
+void make_knot(void);
+void up_to_knot(void);
+void down_to_knot(void);
+void slide_knots_after(size_t brink, int difference, size_t floor, size_t xpos);
 #endif
 void do_tab(void);
 #ifndef NANO_TINY
diff --git a/src/text.c b/src/text.c
index 9137c8f7..a57c5906 100644
--- a/src/text.c
+++ b/src/text.c
@@ -57,6 +57,101 @@ void do_mark(void)
 		refresh_needed = TRUE;
 	}
 }
+
+/* Place a knot at the current line when none exists, otherwise remove it. */
+void make_knot(void)
+{
+	knotstruct *knot = openfile->origin->next;
+
+	while (knot->lineno < openfile->current->lineno && knot != openfile->origin)
+		knot = knot->next;
+
+	if (knot->lineno == openfile->current->lineno) {
+		knot->prev->next = knot->next;
+		knot->next->prev = knot->prev;
+		free(knot);
+		statusbar(_("Removed knot"));
+	} else {
+		knotstruct *newone = nmalloc(sizeof(knotstruct));
+
+		newone->lineno = openfile->current->lineno;
+
+		newone->next = knot;
+		newone->prev = knot->prev;
+		knot->prev->next = newone;
+		knot->prev = newone;
+
+		statusbar(_("Placed knot"));
+	}
+}
+
+/* Jump to the nearest knot (if there are any), either backwards or forwards. */
+void jump_to_nearest_knot(bool direction)
+{
+	knotstruct *knot = (direction == BACKWARD) ? openfile->origin->prev :
+												openfile->origin->next;
+
+	if (knot == openfile->origin) {
+		statusline(ALERT, _("There are no knots"));
+		return;
+	} else if (knot->prev == knot->next && knot->lineno == openfile->current->lineno) {
+		statusbar(_("This is the only knot"));
+		return;
+	}
+
+	if (direction == BACKWARD)
+		while (knot->lineno >= openfile->current->lineno && knot != openfile->origin)
+			knot = knot->prev;
+	else
+		while (knot->lineno <= openfile->current->lineno && knot != openfile->origin)
+			knot = knot->next;
+
+	if (knot == openfile->origin)
+		knot = (direction == BACKWARD) ? knot->prev : knot->next;
+
+	openfile->current = line_from_number(knot->lineno);
+	openfile->current_x = 0;
+	openfile->placewewant = 0;
+	refresh_needed = TRUE;
+}
+
+/* Jump to the first knot before the current line; wrap around at the top. */
+void up_to_knot(void)
+{
+	jump_to_nearest_knot(BACKWARD);
+}
+
+/* Jump to the first knot after the current line; wrap around at the bottom. */
+void down_to_knot(void)
+{
+	jump_to_nearest_knot(FORWARD);
+}
+
+/* Adjust any knots that are located after the given brink -- adjust them
+ * by the given difference.  If floor is nonzero, then knots between floor
+ * and brink should become equal to floor. */
+void slide_knots_after(size_t brink, int difference, size_t floor, size_t xpos)
+{
+	knotstruct *knot = openfile->origin->next;
+
+	while (knot != openfile->origin) {
+		if (knot->lineno > brink || (knot->lineno == brink && xpos == 0))
+			knot->lineno += difference;
+		else if (floor && knot->lineno > floor)
+			knot->lineno = floor;
+
+		knot = knot->next;
+
+		/* If there are two knots at the same line now, remove one. */
+		if (knot->prev->prev->lineno == knot->prev->lineno) {
+			knotstruct *twin = knot->prev;
+
+			twin->prev->next = knot;
+			knot->prev = twin->prev;
+			free(twin);
+		}
+	}
+}
 #endif /* !NANO_TINY */
 
 /* Insert a tab.  If the TABS_TO_SPACES flag is set, insert the number
@@ -532,6 +627,7 @@ void do_undo(void)
 		unlink_node(line->next);
 		renumber_from(line);
 		goto_line_posx(u->head_lineno, u->head_x);
+		slide_knots_after(u->head_lineno, -1, 0, u->head_x);
 		break;
 	case BACK:
 	case DEL:
@@ -559,6 +655,7 @@ void do_undo(void)
 		splice_node(line, intruder);
 		renumber_from(intruder);
 		goto_line_posx(u->head_lineno, u->head_x);
+		slide_knots_after(u->head_lineno, +1, 0, u->head_x);
 		break;
 	case REPLACE:
 		undidmsg = _("replacement");
@@ -874,6 +971,9 @@ void do_enter(void)
 		openfile->mark = newnode;
 		openfile->mark_x += extra - openfile->current_x;
 	}
+
+	/* Add 1 to any knots after the current line. */
+	slide_knots_after(openfile->current->lineno, +1, 0, openfile->current_x);
 #endif
 
 	/* Insert the newly created line after the current one and renumber. */
-- 
2.25.2

