[PATCH] upcoming new feature: smooth whole-window sideways scrolling

Benno Schulenberg <[email protected]> Fri, 9 Jan 2026 11:42:32 +0100
Newsgroups gmane.editors.nano.devel
Message-ID <[email protected]>
Instead of horizontally scrolling just the current line by nearly
a screen width when the cursor is about to go offscreen, now all
screen lines are scrolled horizontally together by just the amount
needed to keep the cursor comfortably in view.

(The joint-panning option is set here by default, for testing.)

Motivated-by: Xylia Allegretta <[email protected]>
Original-patch-by: Mark Majeres <[email protected]>
---
 src/cut.c         |  3 +++
 src/definitions.h |  6 +++++-
 src/global.c      |  2 ++
 src/nano.c        |  6 ++++++
 src/prototypes.h  |  2 ++
 src/search.c      |  8 ++++++++
 src/utils.c       | 11 +++++++++++
 src/winio.c       | 20 +++++++++++++++++---
 8 files changed, 54 insertions(+), 4 deletions(-)

diff --git a/src/cut.c b/src/cut.c
index a2d4aecf..925cf0b1 100644
--- a/src/cut.c
+++ b/src/cut.c
@@ -53,6 +53,9 @@ void expunge(undo_type action)
 		/* When softwrapping, a changed number of chunks requires a refresh. */
 		if (ISSET(SOFTWRAP) && extra_chunks_in(openfile->current) != old_amount)
 			refresh_needed = TRUE;
+		/* When panning, and we have come near edge of the viewport... */
+		else if (ISSET(JOINT_PANNING) && openfile->placewewant < brink + CUSHION)
+			refresh_needed = TRUE;
 
 		/* Adjust the mark if it is after the cursor on the current line. */
 		if (openfile->mark == openfile->current &&
diff --git a/src/definitions.h b/src/definitions.h
index dfe2106f..319011a2 100644
--- a/src/definitions.h
+++ b/src/definitions.h
@@ -143,6 +143,9 @@
 /* The default number of columns from end of line where wrapping occurs. */
 #define COLUMNS_FROM_EOL  8
 
+/* The number of columns the cursor should stay away from the edge. */
+#define CUSHION  3
+
 /* The default comment character when a syntax does not specify any. */
 #define GENERAL_COMMENT_CHARACTER  "#"
 
@@ -376,7 +379,8 @@ enum {
 	USE_MAGIC,
 	MINIBAR,
 	ZERO,
-	MODERN_BINDINGS
+	MODERN_BINDINGS,
+	JOINT_PANNING
 };
 
 /* Structure types. */
diff --git a/src/global.c b/src/global.c
index 84912f17..56680f02 100644
--- a/src/global.c
+++ b/src/global.c
@@ -73,6 +73,8 @@ char *title = NULL;
 bool refresh_needed = FALSE;
 		/* Did a command mangle enough of the buffer that we should
 		 * repaint the screen? */
+size_t brink = 0;
+		/* From which column the edit window is drawn (when panning). */
 bool focusing = TRUE;
 		/* Whether an update of the edit window should center the cursor. */
 
diff --git a/src/nano.c b/src/nano.c
index 2b45ca76..bf3e4ac6 100644
--- a/src/nano.c
+++ b/src/nano.c
@@ -1551,6 +1551,10 @@ void inject(char *burst, size_t count)
 
 	openfile->placewewant = xplustabs();
 
+	/* When panning, and we have come near the edge of the viewport... */
+	if (ISSET(JOINT_PANNING) && openfile->placewewant > brink + editwincols - CUSHION - 1 )
+		refresh_needed = TRUE;
+
 #ifndef NANO_TINY
 	/* When softwrapping and the number of chunks in the current line changed,
 	 * or we were on the last row of the edit window and moved to a new chunk,
@@ -2642,6 +2646,8 @@ int main(int argc, char **argv)
 		die(_("Can open just one file\n"));
 #endif
 
+	SET(JOINT_PANNING);
+
 	prepare_for_display();
 
 #ifdef ENABLE_NANORC
diff --git a/src/prototypes.h b/src/prototypes.h
index c83380df..05b5f7bb 100644
--- a/src/prototypes.h
+++ b/src/prototypes.h
@@ -47,6 +47,8 @@ extern int final_status;
 extern bool inhelp;
 extern char *title;
 
+extern size_t brink;
+
 extern bool focusing;
 
 extern bool as_an_at;
diff --git a/src/search.c b/src/search.c
index 254e23a5..4367d10e 100644
--- a/src/search.c
+++ b/src/search.c
@@ -332,6 +332,11 @@ int findnextstr(const char *needle, bool whole_word_only, int modus,
 		spotlighted = TRUE;
 		light_from_col = xplustabs();
 		light_to_col = wideness(line->data, found_x + found_len);
+
+		/* When panning, ensure the end of the match will be visible too. */
+		if (ISSET(JOINT_PANNING) && !ISSET(SOFTWRAP))
+			brink = get_page_start(light_to_col);
+
 		refresh_needed = TRUE;
 	}
 #endif
@@ -591,6 +596,9 @@ ssize_t do_replace_loop(const char *needle, bool whole_word_only,
 			light_to_col = wideness(openfile->current->data,
 										openfile->current_x + match_len);
 
+			if (ISSET(JOINT_PANNING) && !ISSET(SOFTWRAP))
+				brink = get_page_start(light_to_col);
+
 			/* Refresh the edit window, scrolling it if necessary. */
 			edit_refresh();
 
diff --git a/src/utils.c b/src/utils.c
index 537c8cf7..fded81c6 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -343,6 +343,17 @@ char *free_and_assign(char *dest, char *src)
  * displayed in the edit window when the cursor is at the given column. */
 size_t get_page_start(size_t column)
 {
+	if (ISSET(JOINT_PANNING) && !ISSET(SOFTWRAP)) {
+		if (column < CUSHION)
+			return 0;
+		else if (column < brink + CUSHION)
+			return column - CUSHION;
+		else if (column > brink + editwincols - CUSHION - 1)
+			return column - editwincols + CUSHION + 1;
+		else
+			return brink;
+	}
+
 	if (column == 0 || column + 2 < editwincols || ISSET(SOFTWRAP))
 		return 0;
 	else if (editwincols > 8)
diff --git a/src/winio.c b/src/winio.c
index 277851d9..ce5c3413 100644
--- a/src/winio.c
+++ b/src/winio.c
@@ -2838,7 +2838,10 @@ int update_line(linestruct *line, size_t index)
 #endif
 
 	row = line->lineno - openfile->edittop->lineno;
-	from_col = get_page_start(wideness(line->data, index));
+	if (!ISSET(JOINT_PANNING))
+		from_col = get_page_start(wideness(line->data, index));
+	else
+		from_col = brink;
 
 	/* Expand the piece to be drawn to its representable form, and draw it. */
 	converted = display_string(line->data, from_col, editwincols, TRUE, FALSE);
@@ -2931,9 +2934,13 @@ bool line_needs_update(const size_t old_column, const size_t new_column)
 #ifndef NANO_TINY
 	if (openfile->mark)
 		return TRUE;
-	else
 #endif
-		return (get_page_start(old_column) != get_page_start(new_column));
+	if (get_page_start(old_column) == get_page_start(new_column))
+		return FALSE;
+	if (ISSET(JOINT_PANNING) && !ISSET(SOFTWRAP))
+		refresh_needed = TRUE;
+
+	return !refresh_needed;
 }
 
 /* Try to move up nrows softwrapped chunks from the given line and the
@@ -3342,6 +3349,9 @@ void edit_redraw(linestruct *old_current, update_type manner)
 		adjust_viewport(ISSET(JUMPY_SCROLLING) ? CENTERING : manner);
 		refresh_needed = TRUE;
 		return;
+	} else if (ISSET(JOINT_PANNING) && !ISSET(SOFTWRAP)) {
+		refresh_needed = TRUE;
+		return;
 	}
 
 #ifndef NANO_TINY
@@ -3381,6 +3391,10 @@ void edit_refresh(void)
 	if (current_is_offscreen())
 		adjust_viewport((focusing || ISSET(JUMPY_SCROLLING)) ? CENTERING : FLOWING);
 
+	/* When panning, ensure the cursor will be within the viewport. */
+	if (ISSET(JOINT_PANNING) && !ISSET(SOFTWRAP))
+		brink = get_page_start(xplustabs());
+
 #ifdef ENABLE_COLOR
 	/* When needed and useful, initialize the colors for the current syntax. */
 	if (openfile->syntax && !have_palette && !ISSET(NO_SYNTAX) && has_colors())
-- 
2.51.2