зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-08 00:15:50 +03:00
remove trailing spaces invariant
Этот коммит содержится в:
@@ -45,6 +45,6 @@ fn main() -> io::Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
term.show_cursor()?;
|
term.show_cursor()?;
|
||||||
|
|
||||||
println!("Lines: {:?}", textarea.lines().collect::<Vec<_>>());
|
println!("Lines: {:?}", textarea.lines());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::cmp;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum CursorMove {
|
pub enum CursorMove {
|
||||||
Forward,
|
Forward,
|
||||||
@@ -17,16 +19,11 @@ impl CursorMove {
|
|||||||
lines: &[String],
|
lines: &[String],
|
||||||
) -> Option<(usize, usize)> {
|
) -> Option<(usize, usize)> {
|
||||||
fn fit_col(col: usize, line: &str) -> usize {
|
fn fit_col(col: usize, line: &str) -> usize {
|
||||||
let end = line.chars().count();
|
cmp::min(col, line.chars().count())
|
||||||
if end <= col {
|
|
||||||
end - 1
|
|
||||||
} else {
|
|
||||||
col
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
CursorMove::Forward if col + 1 >= lines[row].chars().count() => {
|
CursorMove::Forward if col >= lines[row].chars().count() => {
|
||||||
if row + 1 < lines.len() {
|
if row + 1 < lines.len() {
|
||||||
Some((row + 1, 0))
|
Some((row + 1, 0))
|
||||||
} else {
|
} else {
|
||||||
@@ -36,7 +33,7 @@ impl CursorMove {
|
|||||||
CursorMove::Forward => Some((row, col + 1)),
|
CursorMove::Forward => Some((row, col + 1)),
|
||||||
CursorMove::Back if col == 0 => {
|
CursorMove::Back if col == 0 => {
|
||||||
if row > 0 {
|
if row > 0 {
|
||||||
Some((row - 1, lines[row - 1].chars().count() - 1))
|
Some((row - 1, lines[row - 1].chars().count()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -47,7 +44,7 @@ impl CursorMove {
|
|||||||
CursorMove::Down if row + 1 >= lines.len() => None,
|
CursorMove::Down if row + 1 >= lines.len() => None,
|
||||||
CursorMove::Down => (Some((row + 1, fit_col(col, &lines[row + 1])))),
|
CursorMove::Down => (Some((row + 1, fit_col(col, &lines[row + 1])))),
|
||||||
CursorMove::Head => Some((row, 0)),
|
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::Top => Some((0, fit_col(col, &lines[0]))),
|
||||||
CursorMove::Bottom => {
|
CursorMove::Bottom => {
|
||||||
let row = lines.len() - 1;
|
let row = lines.len() - 1;
|
||||||
|
|||||||
@@ -21,15 +21,12 @@ impl EditKind {
|
|||||||
let line = &mut lines[row];
|
let line = &mut lines[row];
|
||||||
let next_line = line[*i..].to_string();
|
let next_line = line[*i..].to_string();
|
||||||
line.truncate(*i);
|
line.truncate(*i);
|
||||||
line.push(' ');
|
|
||||||
lines.insert(row + 1, next_line);
|
lines.insert(row + 1, next_line);
|
||||||
}
|
}
|
||||||
EditKind::DeleteNewline(_) => {
|
EditKind::DeleteNewline(_) => {
|
||||||
if row > 0 {
|
if row > 0 {
|
||||||
let line = lines.remove(row);
|
let line = lines.remove(row);
|
||||||
let prev_line = &mut lines[row - 1];
|
lines[row - 1].push_str(&line);
|
||||||
prev_line.pop(); // Remove trailing space
|
|
||||||
prev_line.push_str(&line);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EditKind::Insert(s, i) => {
|
EditKind::Insert(s, i) => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub struct TextArea<'a> {
|
|||||||
impl<'a> Default for TextArea<'a> {
|
impl<'a> Default for TextArea<'a> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
lines: vec![" ".to_string()],
|
lines: vec!["".to_string()],
|
||||||
block: None,
|
block: None,
|
||||||
style: Style::default(),
|
style: Style::default(),
|
||||||
cursor: (0, 0),
|
cursor: (0, 0),
|
||||||
@@ -173,15 +173,6 @@ impl<'a> TextArea<'a> {
|
|||||||
|
|
||||||
// Check invariants
|
// Check invariants
|
||||||
debug_assert!(!self.lines.is_empty(), "no line after {:?}", input);
|
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;
|
let (r, c) = self.cursor;
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
self.lines.len() > r,
|
self.lines.len() > r,
|
||||||
@@ -191,7 +182,7 @@ impl<'a> TextArea<'a> {
|
|||||||
input,
|
input,
|
||||||
);
|
);
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
self.lines[r].chars().count() > c,
|
self.lines[r].chars().count() >= c,
|
||||||
"cursor {:?} exceeds max col {} at line {:?} after {:?}",
|
"cursor {:?} exceeds max col {} at line {:?} after {:?}",
|
||||||
self.cursor,
|
self.cursor,
|
||||||
self.lines[r].chars().count(),
|
self.lines[r].chars().count(),
|
||||||
@@ -208,11 +199,14 @@ impl<'a> TextArea<'a> {
|
|||||||
pub fn insert_char(&mut self, c: char) {
|
pub fn insert_char(&mut self, c: char) {
|
||||||
let (row, col) = self.cursor;
|
let (row, col) = self.cursor;
|
||||||
let line = &mut self.lines[row];
|
let line = &mut self.lines[row];
|
||||||
if let Some((i, _)) = line.char_indices().nth(col) {
|
let i = line
|
||||||
line.insert(i, c);
|
.char_indices()
|
||||||
self.cursor.1 += 1;
|
.nth(col)
|
||||||
self.push_history(EditKind::InsertChar(c, i), (row, 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) {
|
pub fn insert_str(&mut self, s: &str) {
|
||||||
@@ -223,11 +217,14 @@ impl<'a> TextArea<'a> {
|
|||||||
None,
|
None,
|
||||||
"string given to insert_str must not contain newline",
|
"string given to insert_str must not contain newline",
|
||||||
);
|
);
|
||||||
if let Some((i, _)) = line.char_indices().nth(col) {
|
let i = line
|
||||||
line.insert_str(i, s);
|
.char_indices()
|
||||||
self.cursor.1 += s.chars().count();
|
.nth(col)
|
||||||
self.push_history(EditKind::Insert(s.to_string(), i), (row, 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) {
|
pub fn delete_str(&mut self, col: usize, chars: usize) {
|
||||||
@@ -266,10 +263,9 @@ impl<'a> TextArea<'a> {
|
|||||||
.char_indices()
|
.char_indices()
|
||||||
.nth(col)
|
.nth(col)
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.unwrap_or(line.len() - 1);
|
.unwrap_or(line.len());
|
||||||
let next_line = line[idx..].to_string();
|
let next_line = line[idx..].to_string();
|
||||||
line.truncate(idx);
|
line.truncate(idx);
|
||||||
line.push(' ');
|
|
||||||
self.lines.insert(row + 1, next_line);
|
self.lines.insert(row + 1, next_line);
|
||||||
self.cursor = (row + 1, 0);
|
self.cursor = (row + 1, 0);
|
||||||
self.push_history(EditKind::InsertNewline(idx), (row, col));
|
self.push_history(EditKind::InsertNewline(idx), (row, col));
|
||||||
@@ -281,10 +277,9 @@ impl<'a> TextArea<'a> {
|
|||||||
if row > 0 {
|
if row > 0 {
|
||||||
let line = self.lines.remove(row);
|
let line = self.lines.remove(row);
|
||||||
let prev_line = &mut self.lines[row - 1];
|
let prev_line = &mut self.lines[row - 1];
|
||||||
prev_line.pop(); // Remove trailing space
|
|
||||||
let prev_line_end = prev_line.len();
|
let prev_line_end = prev_line.len();
|
||||||
|
self.cursor = (row - 1, prev_line.chars().count());
|
||||||
prev_line.push_str(&line);
|
prev_line.push_str(&line);
|
||||||
self.cursor = (row - 1, prev_line.chars().count() - 1);
|
|
||||||
self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col));
|
self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -328,16 +323,20 @@ impl<'a> TextArea<'a> {
|
|||||||
let mut lines = Vec::with_capacity(self.lines.len());
|
let mut lines = Vec::with_capacity(self.lines.len());
|
||||||
for (i, l) in self.lines.iter().enumerate() {
|
for (i, l) in self.lines.iter().enumerate() {
|
||||||
if i == self.cursor.0 {
|
if i == self.cursor.0 {
|
||||||
let (i, c) = l
|
if let Some((i, c)) = l.char_indices().nth(self.cursor.1) {
|
||||||
.char_indices()
|
let j = i + c.len_utf8();
|
||||||
.nth(self.cursor.1)
|
lines.push(Spans::from(vec![
|
||||||
.unwrap_or((l.len() - 1, ' '));
|
Span::styled(&l[..i], self.cursor_line_style),
|
||||||
let j = i + c.len_utf8();
|
Span::styled(&l[i..j], Style::default().add_modifier(Modifier::REVERSED)),
|
||||||
lines.push(Spans::from(vec![
|
Span::styled(&l[j..], self.cursor_line_style),
|
||||||
Span::styled(&l[..i], self.cursor_line_style),
|
]));
|
||||||
Span::styled(&l[i..j], Style::default().add_modifier(Modifier::REVERSED)),
|
} else {
|
||||||
Span::styled(&l[j..], self.cursor_line_style),
|
// 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 {
|
} else {
|
||||||
lines.push(Spans::from(l.as_str()));
|
lines.push(Spans::from(l.as_str()));
|
||||||
}
|
}
|
||||||
@@ -380,8 +379,8 @@ impl<'a> TextArea<'a> {
|
|||||||
self.cursor_line_style = style;
|
self.cursor_line_style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lines(&'a self) -> impl Iterator<Item = &'a str> {
|
pub fn lines(&'a self) -> &'a [String] {
|
||||||
self.lines.iter().map(|l| &l[..l.len() - 1]) // Trim last whitespace
|
&self.lines
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0-base character-wise (row, col) cursor position.
|
/// 0-base character-wise (row, col) cursor position.
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user