[PATCH 1/4 v4] new default feature: smooth whole-window sideways scrolling

Benno Schulenberg <[email protected]> Sun, 18 Jan 2026 10:56:21 +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.

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        |  4 ++++
 src/prototypes.h  |  2 ++
 src/search.c      |  8 ++++++++
 src/utils.c       | 11 +++++++++++
 src/winio.c       | 20 +++++++++++++++++---
 8 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/src/cut.c b/src/cut.c
index 555c34db..1172d45c 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(SOLO_SIDESCROLL) && 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 8c54ca89..471fdb88 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,
+	SOLO_SIDESCROLL
 };
 
 /* Structure types. */
diff --git a/src/global.c b/src/global.c
index f0889ecf..fc2a36de 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 a1780341..66cb4d31 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(SOLO_SIDESCROLL) && 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,
diff --git a/src/prototypes.h b/src/prototypes.h
index 08a14d71..b8299228 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 24f60603..3ff1cf04 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(SOLO_SIDESCROLL) && !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(SOLO_SIDESCROLL) && !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 392cd815..7940d556 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(SOLO_SIDESCROLL) && !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 307cea61..91036811 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(SOLO_SIDESCROLL))
+		from_col = brink;
+	else
+		from_col = get_page_start(wideness(line->data, index));
 
 	/* 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(SOLO_SIDESCROLL) && !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(SOLO_SIDESCROLL) && !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(SOLO_SIDESCROLL) && !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