From 1cb79635bc3a846bd20a2e8308a4fcbb89aa0735 Mon Sep 17 00:00:00 2001
From: Benno Schulenberg <bensberg@telfort.nl>
Date: Tue, 10 Nov 2020 18:27:52 +0100
Subject: [PATCH 1/2] moving: skip combining characters and other zero-width
 characters

This makes the cursor move smoothly left and right -- instead of
"stuttering" when passing over a zero-width character.

Pressing <Delete> on a normal (spacing) character also deletes
any zero-width characters after it.  But pressing <Backspace>
while the cursor is placed after a zero-width character, just
deletes that zero-width character.  The latter behavior allows
deleting and retyping just the combining diacritic of a character
instead of the whole, combined character.

This addresses https://savannah.gnu.org/bugs/?50773.
Requested-by: Mike Frysinger <vapier@gentoo.org>
---
 src/chars.c      |  7 +++++++
 src/cut.c        | 11 ++++++++++-
 src/move.c       | 29 +++++++++++++++++++++++++++++
 src/prototypes.h |  1 +
 4 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/src/chars.c b/src/chars.c
index 93ffde19..cc0ae357 100644
--- a/src/chars.c
+++ b/src/chars.c
@@ -144,6 +144,13 @@ bool is_word_char(const char *c, bool allow_punct)
 		return FALSE;
 }
 
+#ifdef ENABLE_UTF8
+bool is_zerowidth(const char *ch)
+{
+	return (use_utf8 && mbwidth(ch) == 0);
+}
+#endif
+
 /* Return the visible representation of control character c. */
 char control_rep(const signed char c)
 {
diff --git a/src/cut.c b/src/cut.c
index 63130c2a..8ad4a3d2 100644
--- a/src/cut.c
+++ b/src/cut.c
@@ -121,7 +121,13 @@ void do_delete(void)
 		zap_text();
 	else
 #endif
+	{
 		do_deletion(DEL);
+#ifdef ENABLE_UTF8
+		while (is_zerowidth(openfile->current->data + openfile->current_x))
+			do_deletion(DEL);
+#endif
+	}
 }
 
 /* Backspace over one character.  That is, move the cursor left one
@@ -133,7 +139,10 @@ void do_backspace(void)
 		zap_text();
 	else
 #endif
-	if (openfile->current_x > 0 || openfile->current != openfile->filetop) {
+	if (openfile->current_x > 0) {
+		openfile->current_x = step_left(openfile->current->data, openfile->current_x);
+		do_deletion(BACK);
+	} else if (openfile->current != openfile->filetop) {
 		do_left();
 		do_deletion(BACK);
 	}
diff --git a/src/move.c b/src/move.c
index ba675ba7..9b149855 100644
--- a/src/move.c
+++ b/src/move.c
@@ -291,6 +291,10 @@ void do_prev_word(bool allow_punct)
 			/* If at the head of a line now, this surely is a word start. */
 			if (openfile->current_x == 0)
 				break;
+#ifdef ENABLE_UTF8
+		} else if (is_zerowidth(openfile->current->data + openfile->current_x)) {
+			; /* skip */
+#endif
 		} else if (seen_a_word) {
 			/* This is space now: we've overshot the start of the word. */
 			step_forward = TRUE;
@@ -339,11 +343,20 @@ bool do_next_word(bool after_ends, bool allow_punct)
 			if (is_word_char(openfile->current->data + openfile->current_x,
 								allow_punct))
 				seen_word = TRUE;
+#ifdef ENABLE_UTF8
+			else if (is_zerowidth(openfile->current->data + openfile->current_x))
+				; /* skip */
+#endif
 			else if (seen_word)
 				break;
 		} else
 #endif
 		{
+#ifdef ENABLE_UTF8
+			if (is_zerowidth(openfile->current->data + openfile->current_x))
+				; /* skip */
+			else
+#endif
 			/* If this is not a word character, then it's a separator; else
 			 * if we've already seen a separator, then it's a word start. */
 			if (!is_word_char(openfile->current->data + openfile->current_x,
@@ -582,8 +595,16 @@ void do_left(void)
 	linestruct *was_current = openfile->current;
 
 	if (openfile->current_x > 0)
+	{
 		openfile->current_x = step_left(openfile->current->data,
 												openfile->current_x);
+#ifdef ENABLE_UTF8
+		while (is_zerowidth(openfile->current->data + openfile->current_x) &&
+												openfile->current_x > 0)
+			openfile->current_x = step_left(openfile->current->data,
+												openfile->current_x);
+#endif
+	}
 	else if (openfile->current != openfile->filetop) {
 		openfile->current = openfile->current->prev;
 		openfile->current_x = strlen(openfile->current->data);
@@ -598,8 +619,16 @@ void do_right(void)
 	linestruct *was_current = openfile->current;
 
 	if (openfile->current->data[openfile->current_x] != '\0')
+	{
 		openfile->current_x = step_right(openfile->current->data,
 												openfile->current_x);
+#ifdef ENABLE_UTF8
+		while (is_zerowidth(openfile->current->data + openfile->current_x) &&
+						openfile->current->data[openfile->current_x] != '\0')
+			openfile->current_x = step_right(openfile->current->data,
+												openfile->current_x);
+#endif
+	}
 	else if (openfile->current != openfile->filebot) {
 		openfile->current = openfile->current->next;
 		openfile->current_x = 0;
diff --git a/src/prototypes.h b/src/prototypes.h
index d1b2ece6..6e5c9f6f 100644
--- a/src/prototypes.h
+++ b/src/prototypes.h
@@ -201,6 +201,7 @@ bool is_alpha_char(const char *c);
 bool is_blank_char(const char *c);
 bool is_cntrl_char(const char *c);
 bool is_word_char(const char *c, bool allow_punct);
+bool is_zerowidth(const char *ch);
 char control_mbrep(const char *c, bool isdata);
 #ifdef ENABLE_UTF8
 int mbwidth(const char *c);
-- 
2.25.4

