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,