From 2dba228c766e5e9e4b0fe0c12d30909f51faa28e Mon Sep 17 00:00:00 2001
From: Nathan Vegdahl <cessen@cessen.com>
Date: Fri, 25 Jun 2021 10:13:51 -0700
Subject: [PATCH] Fix highlight code splitting graphemes.

This resulted in phantom blank lines in files with CRLF line
endings, but could potentially have manifested with other
graphemes as well.
---
 helix-core/src/graphemes.rs | 24 ++++++++++++++++++++++++
 helix-term/src/ui/editor.rs |  8 +++++---
 2 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/helix-core/src/graphemes.rs b/helix-core/src/graphemes.rs
index 2e6a925f..e2f7c3f3 100644
--- a/helix-core/src/graphemes.rs
+++ b/helix-core/src/graphemes.rs
@@ -121,6 +121,30 @@ pub fn next_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
     nth_next_grapheme_boundary(slice, char_idx, 1)
 }
 
+/// Returns the passed char index if it's already a grapheme boundary,
+/// or the next grapheme boundary char index if not.
+pub fn ensure_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
+    if char_idx == 0 {
+        0
+    } else {
+        next_grapheme_boundary(slice, char_idx - 1)
+    }
+}
+
+/// Returns the passed byte index if it's already a grapheme boundary,
+/// or the next grapheme boundary byte index if not.
+pub fn ensure_grapheme_boundary_byte(slice: RopeSlice, byte_idx: usize) -> usize {
+    // TODO: we can avoid the byte/char conversions entirely
+    // if we also make byte versions of the other functions.
+    let char_idx = slice.byte_to_char(byte_idx);
+    let fixed_char_idx = ensure_grapheme_boundary(slice, char_idx);
+    if fixed_char_idx == char_idx {
+        byte_idx
+    } else {
+        slice.char_to_byte(fixed_char_idx)
+    }
+}
+
 /// Returns whether the given char position is a grapheme boundary.
 pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
     // Bounds check
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 93f000e7..14cfc7dd 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -8,6 +8,7 @@ use crate::{
 
 use helix_core::{
     coords_at_pos,
+    graphemes::{ensure_grapheme_boundary, ensure_grapheme_boundary_byte},
     syntax::{self, HighlightEvent},
     LineEnding, Position, Range,
 };
@@ -141,7 +142,8 @@ impl EditorView {
 
         'outer: for event in highlights {
             match event.unwrap() {
-                HighlightEvent::HighlightStart(span) => {
+                HighlightEvent::HighlightStart(mut span) => {
+                    span.0 = ensure_grapheme_boundary_byte(text, span.0);
                     spans.push(span);
                 }
                 HighlightEvent::HighlightEnd => {
@@ -151,8 +153,8 @@ impl EditorView {
                     // TODO: filter out spans out of viewport for now..
 
                     // TODO: do these before iterating
-                    let start = text.byte_to_char(start);
-                    let end = text.byte_to_char(end);
+                    let start = ensure_grapheme_boundary(text, text.byte_to_char(start));
+                    let end = ensure_grapheme_boundary(text, text.byte_to_char(end));
 
                     let text = text.slice(start..end);