datui_lib/widgets/textarea/
mod.rs1mod cursor;
15mod edit;
16mod history;
17mod input;
18mod render;
19mod wrap;
20
21#[cfg(test)]
22mod tests;
23
24use std::cell::Cell;
25
26use ratatui::style::{Modifier, Style};
27
28pub use cursor::CursorMove;
29pub use input::{Input, Key};
30
31use history::History;
32
33#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
39pub(super) struct Viewport {
40 pub row: usize,
42 pub col: usize,
44 pub height: u16,
45 pub width: u16,
46}
47
48#[derive(Debug, Clone)]
50pub struct TextArea {
51 lines: Vec<String>,
52 cursor: (usize, usize),
53 selection_anchor: Option<(usize, usize)>,
56 yank: String,
58 history: History,
59 style: Style,
60 cursor_style: Style,
61 selection_style: Style,
62 cursor_visible: bool,
63 tab_len: usize,
64 wrap: bool,
66 viewport: Cell<Viewport>,
67}
68
69impl Default for TextArea {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl TextArea {
76 pub fn new() -> Self {
78 Self {
79 lines: vec![String::new()],
80 cursor: (0, 0),
81 selection_anchor: None,
82 yank: String::new(),
83 history: History::default(),
84 style: Style::default(),
85 cursor_style: Style::default().add_modifier(Modifier::REVERSED),
86 selection_style: Style::default().add_modifier(Modifier::REVERSED),
87 cursor_visible: true,
88 tab_len: 4,
89 wrap: false,
90 viewport: Cell::new(Viewport::default()),
91 }
92 }
93
94 pub fn from_text(text: &str) -> Self {
96 let mut ta = Self::new();
97 ta.set_text(text);
98 ta
99 }
100
101 pub fn set_text(&mut self, text: &str) {
104 self.lines = split_lines(text);
105 let row = self.lines.len() - 1;
106 let col = char_count(&self.lines[row]);
107 self.cursor = (row, col);
108 self.selection_anchor = None;
109 self.history.clear();
110 let size = self.viewport.get();
112 self.viewport.set(Viewport {
113 width: size.width,
114 height: size.height,
115 ..Viewport::default()
116 });
117 }
118
119 pub fn clear(&mut self) {
121 self.set_text("");
122 }
123
124 pub fn lines(&self) -> &[String] {
126 &self.lines
127 }
128
129 pub fn text(&self) -> String {
131 self.lines.join("\n")
132 }
133
134 pub fn line_count(&self) -> usize {
136 self.lines.len()
137 }
138
139 pub fn line(&self, row: usize) -> Option<&str> {
141 self.lines.get(row).map(String::as_str)
142 }
143
144 pub fn is_empty(&self) -> bool {
146 self.lines.len() == 1 && self.lines[0].is_empty()
147 }
148
149 pub fn cursor(&self) -> (usize, usize) {
151 self.cursor
152 }
153
154 pub fn set_cursor(&mut self, row: usize, col: usize) {
157 self.selection_anchor = None;
158 self.cursor = self.clamp_position((row, col));
159 }
160
161 pub fn selection(&self) -> Option<((usize, usize), (usize, usize))> {
164 let anchor = self.selection_anchor?;
165 if anchor == self.cursor {
166 return None;
167 }
168 Some(if anchor <= self.cursor {
169 (anchor, self.cursor)
170 } else {
171 (self.cursor, anchor)
172 })
173 }
174
175 pub fn start_selection(&mut self) {
177 self.selection_anchor = Some(self.cursor);
178 }
179
180 pub fn cancel_selection(&mut self) {
182 self.selection_anchor = None;
183 }
184
185 pub fn select_all(&mut self) {
187 self.selection_anchor = Some((0, 0));
188 let row = self.lines.len() - 1;
189 self.cursor = (row, char_count(&self.lines[row]));
190 }
191
192 pub fn yanked_text(&self) -> &str {
194 &self.yank
195 }
196
197 pub fn set_yank(&mut self, text: impl Into<String>) {
199 self.yank = text.into();
200 }
201
202 pub fn style(&self) -> Style {
204 self.style
205 }
206
207 pub fn set_style(&mut self, style: Style) {
208 self.style = style;
209 }
210
211 pub fn set_cursor_style(&mut self, style: Style) {
212 self.cursor_style = style;
213 }
214
215 pub fn set_selection_style(&mut self, style: Style) {
216 self.selection_style = style;
217 }
218
219 pub fn set_cursor_visible(&mut self, visible: bool) {
221 self.cursor_visible = visible;
222 }
223
224 pub fn cursor_visible(&self) -> bool {
225 self.cursor_visible
226 }
227
228 pub fn set_tab_len(&mut self, len: usize) {
230 self.tab_len = len.max(1);
231 }
232
233 pub fn tab_len(&self) -> usize {
234 self.tab_len
235 }
236
237 pub fn scroll_offsets(&self) -> (usize, usize) {
240 let vp = self.viewport.get();
241 (vp.row, vp.col)
242 }
243
244 fn line_len(&self, row: usize) -> usize {
246 self.lines.get(row).map(|l| char_count(l)).unwrap_or(0)
247 }
248
249 fn clamp_position(&self, (row, col): (usize, usize)) -> (usize, usize) {
251 let row = row.min(self.lines.len() - 1);
252 (row, col.min(self.line_len(row)))
253 }
254}
255
256fn split_lines(text: &str) -> Vec<String> {
259 if text.is_empty() {
260 return vec![String::new()];
261 }
262 text.replace("\r\n", "\n")
263 .split('\n')
264 .map(|s| s.replace('\r', ""))
265 .collect()
266}
267
268fn char_count(line: &str) -> usize {
270 line.chars().count()
271}
272
273fn byte_of_char(line: &str, col: usize) -> usize {
275 line.char_indices()
276 .nth(col)
277 .map(|(i, _)| i)
278 .unwrap_or(line.len())
279}