1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-09-08 08:25:51 +03:00

consider wide chars on calculating tab width

Этот коммит содержится в:
rhysd
2023-10-21 00:30:36 +09:00
родитель b5a9f405e8
Коммит 68275e4932
5 изменённых файлов: 57 добавлений и 16 удалений

Просмотреть файл

@@ -43,6 +43,7 @@ tui = { version = "0.19", default-features = false, optional = true }
arbitrary = { version = "1", features = ["derive"], optional = true } arbitrary = { version = "1", features = ["derive"], optional = true }
crossterm-027 = { package = "crossterm", version = "0.27", optional = true } crossterm-027 = { package = "crossterm", version = "0.27", optional = true }
ratatui = { version = "0.23.0", default-features = false, optional = true } ratatui = { version = "0.23.0", default-features = false, optional = true }
unicode-width = "0.1.11"
[[example]] [[example]]
name = "minimal" name = "minimal"

Просмотреть файл

@@ -5,7 +5,6 @@ use crate::tui::style::Style;
feature = "ratatui-your-backend", feature = "ratatui-your-backend",
))] ))]
use crate::tui::text::Line as Spans; use crate::tui::text::Line as Spans;
use crate::tui::text::Span; use crate::tui::text::Span;
#[cfg(not(any( #[cfg(not(any(
feature = "ratatui-crossterm", feature = "ratatui-crossterm",
@@ -13,11 +12,11 @@ use crate::tui::text::Span;
feature = "ratatui-your-backend", feature = "ratatui-your-backend",
)))] )))]
use crate::tui::text::Spans; use crate::tui::text::Spans;
use crate::util::{num_digits, spaces}; use crate::util::{num_digits, spaces};
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::iter; use std::iter;
use unicode_width::UnicodeWidthChar as _;
enum Boundary { enum Boundary {
Cursor(Style), Cursor(Style),
@@ -58,7 +57,7 @@ fn line_display_text(s: &str, tab_len: u8, mask: Option<char>) -> Cow<'_, str> {
let tab = spaces(tab_len); let tab = spaces(tab_len);
let mut buf = String::new(); let mut buf = String::new();
let mut col = 0; let mut width = 0;
for (i, c) in s.char_indices() { for (i, c) in s.char_indices() {
if c == '\t' { if c == '\t' {
if buf.is_empty() { if buf.is_empty() {
@@ -66,15 +65,15 @@ fn line_display_text(s: &str, tab_len: u8, mask: Option<char>) -> Cow<'_, str> {
buf.push_str(&s[..i]); buf.push_str(&s[..i]);
} }
if tab_len > 0 { if tab_len > 0 {
let len = tab_len as usize - (col % tab_len as usize); let len = tab_len as usize - (width % tab_len as usize);
buf.push_str(&tab[..len]); buf.push_str(&tab[..len]);
col += len; width += len;
} }
} else { } else {
if !buf.is_empty() { if !buf.is_empty() {
buf.push(c); buf.push(c);
} }
col += 1; width += c.width().unwrap_or(0);
} }
} }
@@ -245,5 +244,9 @@ mod tests {
assert_eq!(&line_display_text("ab\t\t", 4, None), "ab "); assert_eq!(&line_display_text("ab\t\t", 4, None), "ab ");
assert_eq!(&line_display_text("ab\t\t", 8, None), "ab "); assert_eq!(&line_display_text("ab\t\t", 8, None), "ab ");
assert_eq!(&line_display_text("abcd\t", 4, None), "abcd "); assert_eq!(&line_display_text("abcd\t", 4, None), "abcd ");
assert_eq!(&line_display_text( "\t", 0, None), "");
assert_eq!(&line_display_text( "\t", 4, None), "");
assert_eq!(&line_display_text( "🐶\t", 4, None), "🐶 ");
assert_eq!(&line_display_text( "\t", 4, Some('x')), "xx");
} }
} }

Просмотреть файл

@@ -23,6 +23,7 @@ use crate::tui::widgets::{Block, Widget};
use crate::util::spaces; use crate::util::spaces;
use crate::widget::{Renderer, Viewport}; use crate::widget::{Renderer, Viewport};
use crate::word::{find_word_end_forward, find_word_start_backward}; use crate::word::{find_word_end_forward, find_word_start_backward};
use unicode_width::UnicodeWidthChar as _;
/// A type to manage state of textarea. /// A type to manage state of textarea.
/// ///
@@ -686,12 +687,15 @@ impl<'a> TextArea<'a> {
/// Insert a tab at current cursor position. Note that this method does nothing when the tab length is 0. This /// Insert a tab at current cursor position. Note that this method does nothing when the tab length is 0. This
/// method returns if a tab string was inserted or not in the textarea. /// method returns if a tab string was inserted or not in the textarea.
/// textarea.
/// ``` /// ```
/// use tui_textarea::TextArea; /// use tui_textarea::{TextArea, CursorMove};
/// ///
/// let mut textarea = TextArea::from(["hi"]); /// let mut textarea = TextArea::from(["hi"]);
/// ///
/// textarea.move_cursor(CursorMove::End); // Move to the end of line
///
/// textarea.insert_tab();
/// assert_eq!(textarea.lines(), ["hi "]);
/// textarea.insert_tab(); /// textarea.insert_tab();
/// assert_eq!(textarea.lines(), ["hi "]); /// assert_eq!(textarea.lines(), ["hi "]);
/// ``` /// ```
@@ -699,13 +703,19 @@ impl<'a> TextArea<'a> {
if self.tab_len == 0 { if self.tab_len == 0 {
return false; return false;
} }
let tab = if self.hard_tab_indent {
"\t" if self.hard_tab_indent {
} else { return self.insert_str("\t");
let len = self.tab_len - (self.cursor.1 % self.tab_len as usize) as u8; }
spaces(len)
}; let (row, col) = self.cursor;
self.insert_str(tab) let width: usize = self.lines[row]
.chars()
.take(col)
.map(|c| c.width().unwrap_or(0))
.sum();
let len = self.tab_len - (width % self.tab_len as usize) as u8;
self.insert_str(spaces(len))
} }
/// Insert a newline at current cursor position. /// Insert a newline at current cursor position.

Просмотреть файл

@@ -1,2 +1,3 @@
mod cursor; mod cursor;
mod history; mod history;
mod textarea;

26
tests/textarea.rs Обычный файл
Просмотреть файл

@@ -0,0 +1,26 @@
use tui_textarea::{CursorMove, TextArea};
#[test]
fn test_insert_soft_tab() {
for test in [
("", 0, " "),
("a", 1, "a "),
("abcd", 4, "abcd "),
("a", 0, " a"),
("ab", 1, "a b"),
("abcdefgh", 4, "abcd efgh"),
("", 1, ""),
("🐶", 1, "🐶 "),
("", 0, ""),
("あい", 1, "あ い"),
] {
let (input, col, expected) = test;
let mut t = TextArea::from([input.to_string()]);
t.move_cursor(CursorMove::Jump(0, col));
assert!(t.insert_tab(), "{:?}", test);
let lines = t.into_lines();
assert_eq!(lines.len(), 1, "{:?}, {:?}", lines, test);
let line = &lines[0];
assert_eq!(line, expected, "{:?}", test);
}
}