1use std::any::Any;
6use std::sync::Arc;
7
8pub trait Component: Send + Sync {
13 fn render(&self, width: usize) -> Vec<String>;
21
22 fn render_document(&self, width: usize) -> RenderedDocument {
27 RenderedDocument::from_lines(self.render(width))
28 }
29
30 fn handle_input(&self, _data: &str) -> bool {
38 false
39 }
40
41 fn wants_key_release(&self) -> bool {
44 false
45 }
46
47 fn invalidate(&self);
50
51 fn as_any(&self) -> &dyn Any;
54}
55
56#[derive(Clone, Default)]
61pub struct RenderedDocument {
62 blocks: Vec<Arc<Vec<String>>>,
63 ends: Vec<usize>,
64}
65
66impl RenderedDocument {
67 pub fn from_lines(lines: Vec<String>) -> Self {
69 Self::from_shared(Arc::new(lines))
70 }
71
72 pub fn from_shared(lines: Arc<Vec<String>>) -> Self {
74 if lines.is_empty() {
75 return Self::default();
76 }
77 Self {
78 ends: vec![lines.len()],
79 blocks: vec![lines],
80 }
81 }
82
83 pub fn append(&mut self, other: Self) {
85 let offset = self.len();
86 self.ends
87 .extend(other.ends.into_iter().map(|end| offset + end));
88 self.blocks.extend(other.blocks);
89 }
90
91 pub fn len(&self) -> usize {
93 self.ends.last().copied().unwrap_or(0)
94 }
95
96 pub fn is_empty(&self) -> bool {
98 self.blocks.is_empty()
99 }
100
101 pub fn line(&self, index: usize) -> Option<&str> {
103 let block_index = self.ends.partition_point(|end| *end <= index);
104 let block = self.blocks.get(block_index)?;
105 let start = if block_index == 0 {
106 0
107 } else {
108 self.ends[block_index - 1]
109 };
110 block.get(index - start).map(String::as_str)
111 }
112
113 pub fn lines(&self, start: usize, height: usize) -> Vec<String> {
115 let mut index = self.ends.partition_point(|end| *end <= start);
116 let mut offset = start.saturating_sub(if index == 0 { 0 } else { self.ends[index - 1] });
117 let mut visible = Vec::with_capacity(height.min(self.len().saturating_sub(start)));
118 while index < self.blocks.len() && visible.len() < height {
119 let block = &self.blocks[index];
120 visible.extend(
121 block
122 .iter()
123 .skip(offset)
124 .take(height - visible.len())
125 .cloned(),
126 );
127 index += 1;
128 offset = 0;
129 }
130 visible
131 }
132}
133
134pub trait Focusable: Component {
140 fn set_focused(&self, focused: bool);
142
143 fn is_focused(&self) -> bool;
145}
146
147pub type ComponentRef = Arc<dyn Component>;
149
150pub type FocusableRef = Arc<dyn Focusable>;
152
153pub const CURSOR_MARKER: &str = "\x1b_pi:c\x07";
158
159pub fn is_focusable(component: &ComponentRef) -> bool {
161 component
162 .as_any()
163 .downcast_ref::<Arc<dyn Focusable>>()
164 .is_some()
165 || component.as_any().type_id() == std::any::TypeId::of::<Arc<dyn Focusable>>()
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[allow(dead_code)]
173 struct TestComponent {
174 content: String,
175 }
176
177 impl Component for TestComponent {
178 fn render(&self, _width: usize) -> Vec<String> {
179 vec![self.content.clone()]
180 }
181
182 fn invalidate(&self) {}
183
184 fn as_any(&self) -> &dyn Any {
185 self
186 }
187 }
188
189 #[test]
190 fn document_composition_retains_shared_rows_and_slices_across_blocks() {
191 let first = Arc::new(vec!["one".into(), "two".into()]);
192 let second = Arc::new(vec!["three".into(), "four".into()]);
193 let mut document = RenderedDocument::from_shared(first.clone());
194 document.append(RenderedDocument::default());
195 document.append(RenderedDocument::from_shared(second.clone()));
196 assert!(Arc::ptr_eq(&document.blocks[0], &first));
197 assert!(Arc::ptr_eq(&document.blocks[1], &second));
198 assert_eq!(document.len(), 4);
199 assert_eq!(document.lines(1, 2), ["two", "three"]);
200 assert_eq!(document.lines(2, usize::MAX), ["three", "four"]);
201 assert!(document.lines(4, 1).is_empty());
202 assert!(document.lines(usize::MAX, 1).is_empty());
203 assert!(document.lines(0, 0).is_empty());
204 }
205
206 #[test]
207 fn document_viewport_does_not_copy_large_history() {
208 for size in [1, 1_000, 10_000] {
209 let rows = Arc::new(vec!["historical row".repeat(64); size]);
210 let document = RenderedDocument::from_shared(rows.clone());
211 let copy = document.clone();
212 assert!(Arc::ptr_eq(©.blocks[0], &rows));
213 assert_eq!(copy.lines(size - 1, 1), [rows[size - 1].clone()]);
214 assert_eq!(Arc::strong_count(&rows), 3);
215 }
216 }
217
218 #[test]
219 fn test_cursor_marker() {
220 assert!(CURSOR_MARKER.starts_with("\x1b"));
221 assert!(CURSOR_MARKER.ends_with("\x07"));
222 }
223}