From c17dcb8633550a4efde56ca5b4e2b00fbf9f45e1 Mon Sep 17 00:00:00 2001
From: Kirawi <67773714+kirawi@users.noreply.github.com>
Date: Fri, 4 Jun 2021 23:49:19 -0400
Subject: [PATCH] Fixing Multiple Panics (#121)

* init

* wip

* wip
---
 helix-core/src/movement.rs    |  5 ++++-
 helix-core/src/selection.rs   |  2 +-
 helix-core/src/transaction.rs | 20 +++++++++++++++++++-
 helix-term/src/commands.rs    |  6 ++++--
 helix-term/src/ui/editor.rs   |  2 +-
 helix-term/src/ui/prompt.rs   |  2 +-
 helix-view/src/view.rs        |  2 +-
 7 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index 387b59b1..d6745fff 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -45,7 +45,10 @@ pub fn move_vertically(
 
     let new_line = match dir {
         Direction::Backward => row.saturating_sub(count),
-        Direction::Forward => std::cmp::min(row.saturating_add(count), text.len_lines() - 2),
+        Direction::Forward => std::cmp::min(
+            row.saturating_add(count),
+            text.len_lines().saturating_sub(2),
+        ),
     };
 
     // convert to 0-indexed, subtract another 1 because len_chars() counts \n
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index 67a20934..7dafc97a 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -383,7 +383,7 @@ pub fn split_on_matches(
             // TODO: retain range direction
 
             let end = text.byte_to_char(start_byte + mat.start());
-            result.push(Range::new(start, end - 1));
+            result.push(Range::new(start, end.saturating_sub(1)));
             start = text.byte_to_char(start_byte + mat.end());
         }
 
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs
index e61063f0..085f40b7 100644
--- a/helix-core/src/transaction.rs
+++ b/helix-core/src/transaction.rs
@@ -90,7 +90,8 @@ impl ChangeSet {
             return;
         }
 
-        self.len_after += fragment.len();
+        // Avoiding std::str::len() to account for UTF-8 characters.
+        self.len_after += fragment.chars().count();
 
         let new_last = match self.changes.as_mut_slice() {
             [.., Insert(prev)] | [.., Insert(prev), Delete(_)] => {
@@ -754,4 +755,21 @@ mod test {
         use Operation::*;
         assert_eq!(changes.changes, &[Insert("a".into())]);
     }
+
+    #[test]
+    fn combine_with_utf8() {
+        const TEST_CASE: &'static str = "Hello, これはヒレクスエディターです!";
+
+        let empty = Rope::from("");
+        let mut a = ChangeSet::new(&empty);
+
+        let mut b = ChangeSet::new(&empty);
+        b.insert(TEST_CASE.into());
+
+        let changes = a.compose(b);
+
+        use Operation::*;
+        assert_eq!(changes.changes, &[Insert(TEST_CASE.into())]);
+        assert_eq!(changes.len_after, TEST_CASE.chars().count());
+    }
 }
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index bc11d0fe..12ec5e19 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -669,7 +669,7 @@ fn _search(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, e
             return;
         }
 
-        let head = end - 1;
+        let head = end;
 
         let selection = if extend {
             selection.clone().push(Range::new(start, head))
@@ -749,7 +749,9 @@ pub fn select_line(cx: &mut Context) {
 
     let line = text.char_to_line(pos.head);
     let start = text.line_to_char(line);
-    let end = text.line_to_char(line + count).saturating_sub(1);
+    let end = text
+        .line_to_char(std::cmp::min(doc.text().len_lines(), line + count))
+        .saturating_sub(1);
 
     doc.set_selection(view.id, Selection::single(start, end));
 }
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index f474cb36..6c39088e 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -341,7 +341,7 @@ impl EditorView {
         let info: Style = theme.get("info");
         let hint: Style = theme.get("hint");
 
-        for (i, line) in (view.first_line..last_line).enumerate() {
+        for (i, line) in (view.first_line..=last_line).enumerate() {
             use helix_core::diagnostic::Severity;
             if let Some(diagnostic) = doc.diagnostics.iter().find(|d| d.line == line) {
                 surface.set_stringn(
diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs
index 1f424878..cdf303b8 100644
--- a/helix-term/src/ui/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -114,7 +114,7 @@ impl Prompt {
         let selected_color = theme.get("ui.menu.selected");
         // completion
 
-        let max_col = area.width / BASE_WIDTH;
+        let max_col = std::cmp::max(1, area.width / BASE_WIDTH);
         let height = ((self.completion.len() as u16 + max_col - 1) / max_col);
         let completion_area = Rect::new(
             area.x,
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index b7bfaa17..8eccb9ef 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -106,7 +106,7 @@ impl View {
     /// Calculates the last visible line on screen
     #[inline]
     pub fn last_line(&self, doc: &Document) -> usize {
-        let height = self.area.height.saturating_sub(1); // - 1 for statusline
+        let height = self.area.height.saturating_sub(2); // - 2 for statusline
         std::cmp::min(
             self.first_line + height as usize,
             doc.text().len_lines() - 1,