From 6e6a3183be41ce5261f506ca354d639b0f0da138 Mon Sep 17 00:00:00 2001 From: rhysd Date: Fri, 10 Jun 2022 20:36:23 +0900 Subject: [PATCH] remove trailing spaces invariant --- examples/minimal.rs | 2 +- src/cursor.rs | 15 ++++------ src/edit.rs | 5 +--- src/textarea.rs | 73 ++++++++++++++++++++++----------------------- 4 files changed, 44 insertions(+), 51 deletions(-) diff --git a/examples/minimal.rs b/examples/minimal.rs index 4b4a102..521d3aa 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -45,6 +45,6 @@ fn main() -> io::Result<()> { )?; term.show_cursor()?; - println!("Lines: {:?}", textarea.lines().collect::>()); + println!("Lines: {:?}", textarea.lines()); Ok(()) } diff --git a/src/cursor.rs b/src/cursor.rs index 8219f6b..2c9ae61 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,3 +1,5 @@ +use std::cmp; + #[derive(Clone, Copy, Debug)] pub enum CursorMove { Forward, @@ -17,16 +19,11 @@ impl CursorMove { lines: &[String], ) -> Option<(usize, usize)> { fn fit_col(col: usize, line: &str) -> usize { - let end = line.chars().count(); - if end <= col { - end - 1 - } else { - col - } + cmp::min(col, line.chars().count()) } match self { - CursorMove::Forward if col + 1 >= lines[row].chars().count() => { + CursorMove::Forward if col >= lines[row].chars().count() => { if row + 1 < lines.len() { Some((row + 1, 0)) } else { @@ -36,7 +33,7 @@ impl CursorMove { CursorMove::Forward => Some((row, col + 1)), CursorMove::Back if col == 0 => { if row > 0 { - Some((row - 1, lines[row - 1].chars().count() - 1)) + Some((row - 1, lines[row - 1].chars().count())) } else { None } @@ -47,7 +44,7 @@ impl CursorMove { CursorMove::Down if row + 1 >= lines.len() => None, CursorMove::Down => (Some((row + 1, fit_col(col, &lines[row + 1])))), CursorMove::Head => Some((row, 0)), - CursorMove::End => Some((row, lines[row].chars().count() - 1)), + CursorMove::End => Some((row, lines[row].chars().count())), CursorMove::Top => Some((0, fit_col(col, &lines[0]))), CursorMove::Bottom => { let row = lines.len() - 1; diff --git a/src/edit.rs b/src/edit.rs index ea233d4..c5dfb9e 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -21,15 +21,12 @@ impl EditKind { let line = &mut lines[row]; let next_line = line[*i..].to_string(); line.truncate(*i); - line.push(' '); lines.insert(row + 1, next_line); } EditKind::DeleteNewline(_) => { if row > 0 { let line = lines.remove(row); - let prev_line = &mut lines[row - 1]; - prev_line.pop(); // Remove trailing space - prev_line.push_str(&line); + lines[row - 1].push_str(&line); } } EditKind::Insert(s, i) => { diff --git a/src/textarea.rs b/src/textarea.rs index 24f9480..9dd08f4 100644 --- a/src/textarea.rs +++ b/src/textarea.rs @@ -23,7 +23,7 @@ pub struct TextArea<'a> { impl<'a> Default for TextArea<'a> { fn default() -> Self { Self { - lines: vec![" ".to_string()], + lines: vec!["".to_string()], block: None, style: Style::default(), cursor: (0, 0), @@ -173,15 +173,6 @@ impl<'a> TextArea<'a> { // Check invariants debug_assert!(!self.lines.is_empty(), "no line after {:?}", input); - for (i, l) in self.lines.iter().enumerate() { - debug_assert!( - l.ends_with(' '), - "line {} does not end with space after {:?}: {:?}", - i + 1, - input, - l, - ); - } let (r, c) = self.cursor; debug_assert!( self.lines.len() > r, @@ -191,7 +182,7 @@ impl<'a> TextArea<'a> { input, ); debug_assert!( - self.lines[r].chars().count() > c, + self.lines[r].chars().count() >= c, "cursor {:?} exceeds max col {} at line {:?} after {:?}", self.cursor, self.lines[r].chars().count(), @@ -208,11 +199,14 @@ impl<'a> TextArea<'a> { pub fn insert_char(&mut self, c: char) { let (row, col) = self.cursor; let line = &mut self.lines[row]; - if let Some((i, _)) = line.char_indices().nth(col) { - line.insert(i, c); - self.cursor.1 += 1; - self.push_history(EditKind::InsertChar(c, i), (row, col)); - } + let i = line + .char_indices() + .nth(col) + .map(|(i, _)| i) + .unwrap_or(line.len()); + line.insert(i, c); + self.cursor.1 += 1; + self.push_history(EditKind::InsertChar(c, i), (row, col)); } pub fn insert_str(&mut self, s: &str) { @@ -223,11 +217,14 @@ impl<'a> TextArea<'a> { None, "string given to insert_str must not contain newline", ); - if let Some((i, _)) = line.char_indices().nth(col) { - line.insert_str(i, s); - self.cursor.1 += s.chars().count(); - self.push_history(EditKind::Insert(s.to_string(), i), (row, col)); - } + let i = line + .char_indices() + .nth(col) + .map(|(i, _)| i) + .unwrap_or(line.len()); + line.insert_str(i, s); + self.cursor.1 += s.chars().count(); + self.push_history(EditKind::Insert(s.to_string(), i), (row, col)); } pub fn delete_str(&mut self, col: usize, chars: usize) { @@ -266,10 +263,9 @@ impl<'a> TextArea<'a> { .char_indices() .nth(col) .map(|(i, _)| i) - .unwrap_or(line.len() - 1); + .unwrap_or(line.len()); let next_line = line[idx..].to_string(); line.truncate(idx); - line.push(' '); self.lines.insert(row + 1, next_line); self.cursor = (row + 1, 0); self.push_history(EditKind::InsertNewline(idx), (row, col)); @@ -281,10 +277,9 @@ impl<'a> TextArea<'a> { if row > 0 { let line = self.lines.remove(row); let prev_line = &mut self.lines[row - 1]; - prev_line.pop(); // Remove trailing space let prev_line_end = prev_line.len(); + self.cursor = (row - 1, prev_line.chars().count()); prev_line.push_str(&line); - self.cursor = (row - 1, prev_line.chars().count() - 1); self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col)); } return; @@ -328,16 +323,20 @@ impl<'a> TextArea<'a> { let mut lines = Vec::with_capacity(self.lines.len()); for (i, l) in self.lines.iter().enumerate() { if i == self.cursor.0 { - let (i, c) = l - .char_indices() - .nth(self.cursor.1) - .unwrap_or((l.len() - 1, ' ')); - let j = i + c.len_utf8(); - lines.push(Spans::from(vec![ - Span::styled(&l[..i], self.cursor_line_style), - Span::styled(&l[i..j], Style::default().add_modifier(Modifier::REVERSED)), - Span::styled(&l[j..], self.cursor_line_style), - ])); + if let Some((i, c)) = l.char_indices().nth(self.cursor.1) { + let j = i + c.len_utf8(); + lines.push(Spans::from(vec![ + Span::styled(&l[..i], self.cursor_line_style), + Span::styled(&l[i..j], Style::default().add_modifier(Modifier::REVERSED)), + Span::styled(&l[j..], self.cursor_line_style), + ])); + } else { + // When cursor is at the end of line + lines.push(Spans::from(vec![ + Span::styled(l.as_str(), self.cursor_line_style), + Span::styled(" ", Style::default().add_modifier(Modifier::REVERSED)), + ])); + } } else { lines.push(Spans::from(l.as_str())); } @@ -380,8 +379,8 @@ impl<'a> TextArea<'a> { self.cursor_line_style = style; } - pub fn lines(&'a self) -> impl Iterator { - self.lines.iter().map(|l| &l[..l.len() - 1]) // Trim last whitespace + pub fn lines(&'a self) -> &'a [String] { + &self.lines } /// 0-base character-wise (row, col) cursor position.