From bc8d60e822af61a0239d1795ee1776cefb38b9c6 Mon Sep 17 00:00:00 2001
From: David Lawrence Ramsey <pooka109@gmail.com>
Date: Fri, 4 Aug 2017 15:43:14 -0500
Subject: [PATCH] softwrap: don't break softwrapped chunks before partial tabs

Tabs are of variable length depending on position, so allow them to be
displayed partially on one chunk and partially on another.  Break chunks
at the edge of the screen when they end in tabs, and adjust the index to
stay on the same chunk when tabs begin on the previous chunk (which is
needed for Up, Down, PageUp, PageDown, and Home; End doesn't need to
care about the previous chunk).

Also, properly handle multi-column blanks (such as Unicode 003000,
Ideographic Space) in atblanks mode, by breaking before them if they go
over the width of the screen.

This fixes http://savannah.gnu.org/bugs/?51621.
---
 src/move.c  | 26 +++++++++++++++++++++-----
 src/winio.c | 33 +++++++++++++++++++++++++++++++--
 2 files changed, 52 insertions(+), 7 deletions(-)

diff --git a/src/move.c b/src/move.c
index 719aa26..fdee8bf 100644
--- a/src/move.c
+++ b/src/move.c
@@ -63,6 +63,22 @@ void get_edge_and_target(size_t *leftedge, size_t *target_column)
     }
 }
 
+/* Return the index in text that corresponds to the given column on the chunk
+ * starting on the given leftedge.  This is the same as actual_x(), unless the
+ * column is in the middle of a tab at the start of a chunk: in that case, the
+ * index is shifted to after the tab, so we can navigate it properly. */
+size_t proper_x(const char *text, size_t leftedge, size_t column)
+{
+    size_t index = actual_x(text, column);
+
+#ifndef NANO_TINY
+    if (ISSET(SOFTWRAP) && leftedge % tabsize != 0 && text[index] == '\t')
+	index++;
+#endif
+
+    return index;
+}
+
 /* Move up nearly one screenful. */
 void do_page_up(void)
 {
@@ -89,7 +105,7 @@ void do_page_up(void)
     }
 
     openfile->placewewant = leftedge + target_column;
-    openfile->current_x = actual_x(openfile->current->data,
+    openfile->current_x = proper_x(openfile->current->data, leftedge,
 				actual_last_column(leftedge, target_column));
 
     /* Move the viewport so that the cursor stays immobile, if possible. */
@@ -123,7 +139,7 @@ void do_page_down(void)
     }
 
     openfile->placewewant = leftedge + target_column;
-    openfile->current_x = actual_x(openfile->current->data,
+    openfile->current_x = proper_x(openfile->current->data, leftedge,
 				actual_last_column(leftedge, target_column));
 
     /* Move the viewport so that the cursor stays immobile, if possible. */
@@ -353,7 +369,7 @@ void do_home(bool be_clever)
 
     if (ISSET(SOFTWRAP)) {
 	leftedge = leftedge_for(was_column, openfile->current);
-	leftedge_x = actual_x(openfile->current->data, leftedge);
+	leftedge_x = proper_x(openfile->current->data, leftedge, leftedge);
     }
 
     if (ISSET(SMART_HOME) && be_clever) {
@@ -483,7 +499,7 @@ void do_up(bool scroll_only)
 	return;
 
     openfile->placewewant = leftedge + target_column;
-    openfile->current_x = actual_x(openfile->current->data,
+    openfile->current_x = proper_x(openfile->current->data, leftedge,
 				actual_last_column(leftedge, target_column));
 
     /* When the cursor was on the first line of the edit window (or when just
@@ -527,7 +543,7 @@ void do_down(bool scroll_only)
 	return;
 
     openfile->placewewant = leftedge + target_column;
-    openfile->current_x = actual_x(openfile->current->data,
+    openfile->current_x = proper_x(openfile->current->data, leftedge,
 				actual_last_column(leftedge, target_column));
 
     /* When the cursor was on the last line of the edit window (or when just
diff --git a/src/winio.c b/src/winio.c
index 9c5d1e2..f3c40d7 100644
--- a/src/winio.c
+++ b/src/winio.c
@@ -2975,6 +2975,8 @@ size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
 	/* Current index in text. */
     size_t column = 0;
 	/* Current column position in text. */
+    size_t prev_index = 0;
+	/* Previous index in text. */
     size_t prev_column = 0;
 	/* Previous column position in text. */
     size_t goal_column;
@@ -2991,8 +2993,10 @@ size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
     while (*text != '\0' && column < leftedge)
 	text += parse_mbchar(text, NULL, &column);
 
-    /* Use a full screen row for text. */
-    goal_column = column + editwincols;
+    /* Use a full screen row for text.  If a tab begins on the previous chunk
+     * and ends on this one, column will not be the first column of the screen
+     * row, so use leftedge instead. */
+    goal_column = leftedge + editwincols;
 
     while (*text != '\0' && column <= goal_column) {
 	/* When breaking at blanks, do it *before* the target column. */
@@ -3002,6 +3006,7 @@ size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
 	    lastblank_column = column;
 	}
 
+	prev_index = index;
 	prev_column = column;
 	char_len = parse_mbchar(text, NULL, &column);
 	text += char_len;
@@ -3018,11 +3023,35 @@ size_t get_softwrap_breakpoint(const char *text, size_t leftedge,
     /* If we're softwrapping at blanks and we found at least one blank, move
      * the pointer back to the last blank, step beyond it, and we're done. */
     if (found_blank) {
+	size_t prev_lastblank_column = lastblank_column;
+
 	text = text - index + lastblank_index;
 	parse_mbchar(text, NULL, &lastblank_column);
+
+	if (lastblank_column > goal_column) {
+	    /* If a tab begins on this chunk and ends on the next one, break at
+	     * the last column of this screen row. */
+	    if (*text == '\t')
+		lastblank_column = goal_column;
+	    /* If a multi-column blank (such as Unicode 003000, Ideographic
+	     * Space) begins on this chunk and ends on the next one, break at
+	     * the column of the last character that doesn't overshoot the
+	     * target, since we can't break the text anywhere else. */
+	    else
+		lastblank_column = (editwincols > 1) ?
+		    prev_lastblank_column : lastblank_column - 1;
+	}
+
 	return lastblank_column;
     }
 
+    text = text - index + prev_index;
+
+    /* If a tab begins on this chunk and ends on the next one, break at the
+     * last column of this screen row. */
+    if (*text == '\t')
+	prev_column = goal_column;
+
     /* Otherwise, return the column of the last character that doesn't
      * overshoot the target, since we can't break the text anywhere else. */
     return (editwincols > 1) ? prev_column : column - 1;
-- 
2.9.0

