From 54adc37c7b91a55719343aff348ff00ac16299d7 Mon Sep 17 00:00:00 2001
From: Devin Hussey <husseydevin@gmail.com>
Date: Thu, 12 May 2022 00:33:10 -0400
Subject: [PATCH] Reduce quadratic loops in softwrap code

Adds a new function next_softwrap_breakpoint.

This function is like get_softwrap_breakpoint, but it saves the
pointer and actual column to avoid scanning from the beginning of
the string when it is called again. This avoids quadratic loops in
functions like update_softwrapped_lines and get_chunk_and_edge.

get_softwrap_breakpoint is now just a wrapper for this function.

This improves performance on rendering very long lines, although it
isn't perfect.

When syntax highlighting is off, nano is now perfectly capable of
60fps when rapidly smooth scrolling through a long 19kB
softwrapped line. Previously, it would just choke on anything more
than a few lines down.

I haven't tested much of this code outside of the basics.

Signed-off-by: Devin Hussey <husseydevin@gmail.com>
---
 src/prototypes.h |  3 +++
 src/winio.c      | 64 ++++++++++++++++++++++++++++++++----------------
 2 files changed, 46 insertions(+), 21 deletions(-)

diff --git a/src/prototypes.h b/src/prototypes.h
index 0708ded0..a32be3ec 100644
--- a/src/prototypes.h
+++ b/src/prototypes.h
@@ -609,6 +609,9 @@ void edit_scroll(bool direction);
 #ifndef NANO_TINY
 size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
 								bool *end_of_line);
+
+size_t next_softwrap_breakpoint(const char **text, size_t *start_column,
+								size_t leftedge, bool *end_of_line);
 size_t get_chunk_and_edge(size_t column, linestruct *line, size_t *leftedge);
 size_t chunk_for(size_t column, linestruct *line);
 size_t leftedge_for(size_t column, linestruct *line);
diff --git a/src/winio.c b/src/winio.c
index dee54851..1db06561 100644
--- a/src/winio.c
+++ b/src/winio.c
@@ -2848,8 +2848,10 @@ int update_softwrapped_line(linestruct *line)
 
 	starting_row = row;
 
+	const char *ptr = line->data;
+	size_t real_col = 0;
 	while (!end_of_line && row < editwinrows) {
-		to_col = get_softwrap_breakpoint(line->data, from_col, &end_of_line);
+		to_col = next_softwrap_breakpoint(&ptr, &real_col, from_col, &end_of_line);
 
 		sequel_column = (end_of_line) ? 0 : to_col;
 
@@ -2930,12 +2932,14 @@ int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge)
 #ifndef NANO_TINY
 	if (ISSET(SOFTWRAP)) {
 		size_t current_leftedge = *leftedge;
+		const char *ptr = (*line)->data;
+		size_t real_col = 0;
 
 		/* Advance through the requested number of chunks. */
 		for (i = nrows; i > 0; i--) {
 			bool end_of_line = FALSE;
 
-			current_leftedge = get_softwrap_breakpoint((*line)->data,
+			current_leftedge = next_softwrap_breakpoint(&ptr, &real_col,
 										current_leftedge, &end_of_line);
 
 			if (!end_of_line)
@@ -2945,6 +2949,8 @@ int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge)
 				break;
 
 			*line = (*line)->next;
+			ptr = (*line)->data;
+			real_col = 0;
 			current_leftedge = 0;
 		}
 
@@ -3070,15 +3076,19 @@ void edit_scroll(bool direction)
 /* Get the column number after leftedge where we can break the given text, and
  * return it.  This will always be editwincols or less after leftedge.  Set
  * end_of_line to TRUE if we reach the end of the line while searching the
- * text.  Assume leftedge is the leftmost column of a softwrapped chunk. */
-size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
-								bool *end_of_line)
+ * text.  Assume leftedge is the leftmost column of a softwrapped chunk. 
+ *
+ * *text and *start_column will be advanced after this function to the end
+ * of the breakpoint and the actual column, for a single shot version there
+ * is get_softwrap_breakpoint. */
+size_t next_softwrap_breakpoint(const char **text, size_t *start_column,
+								size_t leftedge, bool *end_of_line)
 {
 	size_t goal_column = leftedge + editwincols;
 		/* The place at or before which text must be broken. */
 	size_t breaking_col = goal_column;
 		/* The column where text can be broken, when there's no better. */
-	size_t column = 0;
+	size_t *column = start_column;
 		/* Current column position in text. */
 	size_t last_blank_col = 0;
 		/* The column position of the last seen whitespace character. */
@@ -3086,26 +3096,26 @@ size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
 		/* A pointer to the last seen whitespace character in text. */
 
 	/* First find the place in text where the current chunk starts. */
-	while (*text != '\0' && column < leftedge)
-		text += advance_over(text, &column);
+	while (**text != '\0' && *column < leftedge)
+		*text += advance_over(*text, column);
 
 	/* Now find the place in text where this chunk should end. */
-	while (*text != '\0' && column <= goal_column) {
+	while (**text != '\0' && *column <= goal_column) {
 		/* When breaking at blanks, do it *before* the target column. */
-		if (ISSET(AT_BLANKS) && is_blank_char(text) && column < goal_column) {
-			farthest_blank = text;
-			last_blank_col = column;
+		if (ISSET(AT_BLANKS) && is_blank_char(*text) && *column < goal_column) {
+			farthest_blank = *text;
+			last_blank_col = *column;
 		}
 
-		breaking_col = (*text == '\t' ? goal_column : column);
-		text += advance_over(text, &column);
+		breaking_col = (**text == '\t' ? goal_column : *column);
+		*text += advance_over(*text, column);
 	}
 
 	/* If we didn't overshoot the limit, we've found a breaking point;
 	 * and we've reached EOL if we didn't even *reach* the limit. */
-	if (column <= goal_column) {
-		*end_of_line = (column < goal_column);
-		return column;
+	if (*column <= goal_column) {
+		*end_of_line = (*column < goal_column);
+		return *column;
 	}
 
 	/* If we're softwrapping at blanks and we found at least one blank, break
@@ -3122,7 +3132,16 @@ size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
 	}
 
 	/* Otherwise, break at the last character that doesn't overshoot. */
-	return (editwincols > 1) ? breaking_col : column - 1;
+	return (editwincols > 1) ? breaking_col : *column - 1;
+}
+
+/* Wrapper for above for when storing the end of the breakpoint isn't important. */
+size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
+										bool *end_of_line)
+{
+	const char *ptr = text;
+	size_t start_col = 0;
+	return next_softwrap_breakpoint(&ptr, &start_col, leftedge, end_of_line);
 }
 
 /* Get the row of the softwrapped chunk of the given line that column is on,
@@ -3132,9 +3151,10 @@ size_t get_chunk_and_edge(size_t column, linestruct *line, size_t *leftedge)
 {
 	size_t current_chunk = 0, start_col = 0, end_col;
 	bool end_of_line = FALSE;
-
+	size_t real_col = 0;
+	const char *ptr = line->data;
 	while (TRUE) {
-		end_col = get_softwrap_breakpoint(line->data, start_col, &end_of_line);
+		end_col = next_softwrap_breakpoint(&ptr, &real_col, start_col, &end_of_line);
 
 		/* We reached the end of the line and/or found column, so get out. */
 		if (end_of_line || (start_col <= column && column < end_col)) {
@@ -3481,8 +3501,10 @@ void spotlight_softwrapped(size_t from_col, size_t to_col)
 	place_the_cursor();
 	row = openfile->current_y;
 
+	const char *ptr = openfile->current->data;
+	size_t real_col = 0;
 	while (row < editwinrows) {
-		break_col = get_softwrap_breakpoint(openfile->current->data,
+		break_col = next_softwrap_breakpoint(&ptr, &real_col,
 												leftedge, &end_of_line);
 
 		/* If the highlighting ends on this chunk, we can stop after it. */
-- 
2.34.1

