зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-08-28 11:36:17 +03:00
feat: set lines measure cache (#16)
* feat(textarea): add bulk replace API and transparent measure() caching - add TextArea::set_lines(lines, cursor) for direct whole-buffer replacement - preserve widget configuration while resetting history, selection, highlights, viewport scroll, and cached measurement state - cache measure(width_cols) results by outer width and reuse them until dependencies change - invalidate cached measurement on content edits, undo/redo, and measure-affecting setters - add focused tests for set_lines behavior and cache correctness * chore(release): bump to v0.10.2 and refresh docs - prepare the v0.10.2 release with updated crate version and changelog entry - refresh README coverage for fork-added APIs including wrapping, measurement, bulk replace, and custom highlights
Этот коммит содержится в:
117
tests/measure.rs
117
tests/measure.rs
@@ -1,3 +1,4 @@
|
||||
use ratatui::layout::Alignment;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::widgets::{Block, Borders};
|
||||
use tui_textarea::{TextArea, TextAreaMeasure, WrapMode};
|
||||
@@ -105,3 +106,119 @@ fn block_intrinsic_min_rows_overrides_configured_bounds() {
|
||||
assert_eq!(have.min_rows, 3);
|
||||
assert_eq!(have.max_rows, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeated_measure_at_same_width_returns_same_result() {
|
||||
let mut textarea = TextArea::from(["abcdef"]);
|
||||
textarea.set_wrap_mode(WrapMode::WordOrGlyph);
|
||||
|
||||
let first = textarea.measure(4);
|
||||
let second = textarea.measure(4);
|
||||
assert_eq!(first, second);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn measuring_at_a_different_width_recomputes() {
|
||||
let mut textarea = TextArea::from(["abcdef"]);
|
||||
textarea.set_wrap_mode(WrapMode::WordOrGlyph);
|
||||
|
||||
assert_eq!(textarea.measure(4).content_rows, 2);
|
||||
assert_eq!(textarea.measure(8).content_rows, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn content_insertion_invalidates_measure_cache() {
|
||||
let mut textarea = TextArea::from([""]);
|
||||
textarea.set_wrap_mode(WrapMode::WordOrGlyph);
|
||||
|
||||
assert_eq!(textarea.measure(4).content_rows, 1);
|
||||
assert!(textarea.insert_str("abcdef"));
|
||||
assert_eq!(textarea.measure(4).content_rows, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undo_and_redo_invalidate_measure_cache() {
|
||||
let mut textarea = TextArea::from([""]);
|
||||
textarea.set_wrap_mode(WrapMode::WordOrGlyph);
|
||||
|
||||
textarea.measure(4);
|
||||
assert!(textarea.insert_str("abcdef"));
|
||||
assert_eq!(textarea.measure(4).content_rows, 2);
|
||||
|
||||
assert!(textarea.undo());
|
||||
assert_eq!(textarea.measure(4).content_rows, 1);
|
||||
|
||||
assert!(textarea.redo());
|
||||
assert_eq!(textarea.measure(4).content_rows, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_wrap_mode_invalidates_measure_cache() {
|
||||
let mut textarea = TextArea::from(["abcdef"]);
|
||||
|
||||
assert_eq!(textarea.measure(4).content_rows, 1);
|
||||
textarea.set_wrap_mode(WrapMode::WordOrGlyph);
|
||||
assert_eq!(textarea.measure(4).content_rows, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_line_number_style_invalidates_measure_cache() {
|
||||
let mut textarea = TextArea::from(["abcdefg"]);
|
||||
textarea.set_wrap_mode(WrapMode::WordOrGlyph);
|
||||
|
||||
assert_eq!(textarea.measure(8).content_rows, 1);
|
||||
textarea.set_line_number_style(Style::default());
|
||||
assert_eq!(textarea.measure(8).content_rows, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_block_and_remove_block_invalidate_measure_cache() {
|
||||
let mut textarea = TextArea::from(["line"]);
|
||||
|
||||
assert_eq!(textarea.measure(12).preferred_rows, 1);
|
||||
textarea.set_block(Block::default().borders(Borders::ALL));
|
||||
assert_eq!(textarea.measure(12).preferred_rows, 3);
|
||||
|
||||
textarea.remove_block();
|
||||
assert_eq!(textarea.measure(12).preferred_rows, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min_and_max_row_setters_invalidate_measure_cache() {
|
||||
let mut textarea = TextArea::from(["line"]);
|
||||
|
||||
assert_eq!(textarea.measure(12).preferred_rows, 1);
|
||||
|
||||
textarea.set_min_rows(4);
|
||||
let min_measured = textarea.measure(12);
|
||||
assert_eq!(min_measured.preferred_rows, 4);
|
||||
assert_eq!(min_measured.min_rows, 4);
|
||||
|
||||
let mut textarea: TextArea<'_> = (0..10).map(|n| n.to_string()).collect();
|
||||
textarea.measure(12);
|
||||
textarea.set_max_rows(2);
|
||||
let max_measured = textarea.measure(12);
|
||||
assert_eq!(max_measured.preferred_rows, 2);
|
||||
assert_eq!(max_measured.max_rows, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_alignment_invalidates_when_it_disables_line_numbers() {
|
||||
let mut textarea = TextArea::from(["abcdefg"]);
|
||||
textarea.set_wrap_mode(WrapMode::WordOrGlyph);
|
||||
textarea.set_line_number_style(Style::default());
|
||||
assert_eq!(textarea.measure(8).content_rows, 2);
|
||||
|
||||
textarea.set_alignment(Alignment::Right);
|
||||
assert_eq!(textarea.measure(8).content_rows, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_lines_updates_measurement_immediately() {
|
||||
let mut textarea = TextArea::from(["line"]);
|
||||
textarea.set_wrap_mode(WrapMode::WordOrGlyph);
|
||||
|
||||
assert_eq!(textarea.measure(4).content_rows, 1);
|
||||
textarea.set_lines(vec!["abcdef".to_string()], (0, 6));
|
||||
assert_eq!(textarea.measure(4).content_rows, 2);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use ratatui::layout::Alignment;
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::widgets::{Block, Borders};
|
||||
use std::cmp;
|
||||
use std::fmt::Debug;
|
||||
use tui_textarea::{CursorMove, TextArea};
|
||||
@@ -1604,6 +1607,81 @@ fn test_selection_range() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_lines_preserves_non_content_configuration() {
|
||||
let mut t = TextArea::from(["hello"]);
|
||||
let base_style = Style::default().fg(Color::Green);
|
||||
let cursor_style = Style::default().bg(Color::Red);
|
||||
let cursor_line_style = Style::default().add_modifier(Modifier::BOLD);
|
||||
let selection_style = Style::default().bg(Color::Blue);
|
||||
let line_number_style = Style::default().fg(Color::Yellow);
|
||||
let placeholder_style = Style::default().fg(Color::Cyan);
|
||||
|
||||
t.set_wrap_mode(tui_textarea::WrapMode::WordOrGlyph);
|
||||
t.set_block(Block::default().borders(Borders::ALL).title("Input"));
|
||||
t.set_style(base_style);
|
||||
t.set_cursor_style(cursor_style);
|
||||
t.set_cursor_line_style(cursor_line_style);
|
||||
t.set_selection_style(selection_style);
|
||||
t.set_tab_length(8);
|
||||
t.set_alignment(Alignment::Right);
|
||||
t.set_line_number_style(line_number_style);
|
||||
t.set_placeholder_text("placeholder");
|
||||
t.set_placeholder_style(placeholder_style);
|
||||
t.set_mask_char('*');
|
||||
t.set_yank_text("copied");
|
||||
t.set_min_rows(2);
|
||||
t.set_max_rows(6);
|
||||
t.set_max_histories(3);
|
||||
|
||||
t.set_lines(vec!["abc".to_string()], (0, 1));
|
||||
|
||||
assert_eq!(t.lines(), ["abc"]);
|
||||
assert_eq!(t.cursor(), (0, 1));
|
||||
assert_eq!(t.wrap_mode(), tui_textarea::WrapMode::WordOrGlyph);
|
||||
assert!(t.block().is_some());
|
||||
assert_eq!(t.style(), base_style);
|
||||
assert_eq!(t.cursor_style(), cursor_style);
|
||||
assert_eq!(t.cursor_line_style(), cursor_line_style);
|
||||
assert_eq!(t.selection_style(), selection_style);
|
||||
assert_eq!(t.tab_length(), 8);
|
||||
assert_eq!(t.alignment(), Alignment::Right);
|
||||
assert_eq!(t.line_number_style(), Some(line_number_style));
|
||||
assert_eq!(t.placeholder_text(), "placeholder");
|
||||
assert_eq!(t.placeholder_style(), Some(placeholder_style));
|
||||
assert_eq!(t.mask_char(), Some('*'));
|
||||
assert_eq!(t.yank_text(), "copied");
|
||||
assert_eq!(t.min_rows(), 2);
|
||||
assert_eq!(t.max_rows(), 6);
|
||||
assert_eq!(t.max_histories(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_lines_clamps_cursor_and_clears_selection_and_history() {
|
||||
let mut t = TextArea::from(["hello", "world"]);
|
||||
assert!(t.insert_str("!"));
|
||||
assert!(t.undo());
|
||||
assert!(t.redo());
|
||||
t.move_cursor(CursorMove::Jump(0, 1));
|
||||
t.start_selection();
|
||||
t.move_cursor(CursorMove::Jump(1, 3));
|
||||
assert!(t.is_selecting());
|
||||
|
||||
t.set_lines(vec!["ab".to_string(), "c".to_string()], (9, 9));
|
||||
|
||||
assert_eq!(t.lines(), ["ab", "c"]);
|
||||
assert_eq!(t.cursor(), (1, 1));
|
||||
assert!(!t.is_selecting());
|
||||
assert!(!t.undo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "lines must not be empty; use vec![String::new()] for empty content")]
|
||||
fn test_set_lines_panics_on_empty_lines() {
|
||||
let mut t = TextArea::default();
|
||||
t.set_lines(vec![], (0, 0));
|
||||
}
|
||||
|
||||
struct DeleteTester(&'static [&'static str], fn(&mut TextArea) -> bool);
|
||||
impl DeleteTester {
|
||||
fn test(&self, before: (usize, usize), after: (usize, usize, &[&str], &str)) {
|
||||
|
||||
Ссылка в новой задаче
Block a user