1use crossterm::{
13 cursor, execute,
14 terminal::{self, ClearType},
15};
16use std::{
17 collections::hash_map::DefaultHasher,
18 hash::{Hash, Hasher},
19 io::{self, BufWriter, Write},
20 sync::{Arc, Mutex},
21};
22static BUFFER_POOL: once_cell::sync::Lazy<Arc<Mutex<Vec<String>>>> =
26 once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
27
28#[inline(always)]
30fn get_pooled_buffer() -> String {
31 if let Ok(mut pool) = BUFFER_POOL.lock() {
32 pool.pop().unwrap_or_else(|| String::with_capacity(4096))
33 } else {
34 String::with_capacity(4096)
35 }
36}
37
38#[inline(always)]
40fn return_to_pool(mut buffer: String) {
41 buffer.clear();
42 if buffer.capacity() <= 16384 {
44 if let Ok(mut pool) = BUFFER_POOL.lock() {
45 if pool.len() < 8 {
46 pool.push(buffer);
48 }
49 }
50 }
51}
52
53#[inline(always)]
55fn fast_hash(content: &str) -> u64 {
56 let mut hasher = DefaultHasher::new();
57 content.hash(&mut hasher);
58 hasher.finish()
59}
60
61pub struct StringRenderer {
63 previous_content: String,
65 previous_lines: Vec<(usize, usize)>, width: u16,
69 height: u16,
70 previous_hash: u64,
72 buf_writer: Option<BufWriter<Box<dyn Write + Send>>>,
74 work_buffer: String,
76}
77
78impl StringRenderer {
79 #[inline]
81 pub fn new() -> Self {
82 let (width, height) = terminal::size().unwrap_or((80, 24));
83 Self {
84 previous_content: String::with_capacity(4096),
85 previous_lines: Vec::with_capacity(64),
86 width,
87 height,
88 previous_hash: 0,
89 buf_writer: None,
90 work_buffer: String::with_capacity(1024),
91 }
92 }
93
94 #[inline]
96 pub fn with_output(output: Box<dyn Write + Send>) -> Self {
97 let (width, height) = terminal::size().unwrap_or((80, 24));
98 Self {
99 previous_content: String::with_capacity(4096),
100 previous_lines: Vec::with_capacity(64),
101 width,
102 height,
103 previous_hash: 0,
104 buf_writer: Some(BufWriter::with_capacity(8192, output)),
105 work_buffer: String::with_capacity(1024),
106 }
107 }
108
109 pub fn render(&mut self, content: String) -> io::Result<()> {
111 let content_hash = fast_hash(&content);
113 if content_hash == self.previous_hash && content == self.previous_content {
114 return Ok(()); }
116
117 let mut work_buf = get_pooled_buffer();
119 let new_lines: Vec<&str> = content.lines().collect();
120
121 let (width, height) = terminal::size().unwrap_or((80, 24));
123 if width != self.width || height != self.height {
124 self.width = width;
125 self.height = height;
126 self.full_render_content(&new_lines, &mut work_buf)?;
128 } else {
129 self.diff_render_content(&new_lines, &mut work_buf)?;
131 }
132
133 self.update_line_cache(&content);
135 self.previous_content = content;
136 self.previous_hash = content_hash;
137
138 return_to_pool(work_buf);
140
141 if let Some(ref mut buf_writer) = self.buf_writer {
143 buf_writer.flush()?;
144 } else {
145 io::stdout().flush()?;
146 }
147 Ok(())
148 }
149
150 #[inline(always)]
152 fn update_line_cache(&mut self, content: &str) {
153 self.previous_lines.clear();
154 let est_lines = content.len() / 80 + 1; if self.previous_lines.capacity() < est_lines {
157 self.previous_lines
158 .reserve(est_lines - self.previous_lines.capacity());
159 }
160
161 let mut start = 0;
162 for line in content.lines() {
163 let end = start + line.len();
164 self.previous_lines.push((start, end));
165 start = end + 1; }
167 }
168
169 #[inline]
171 fn full_render_content(&mut self, lines: &[&str], work_buf: &mut String) -> io::Result<()> {
172 work_buf.clear();
174 work_buf.reserve(lines.len() * 80); for (i, line) in lines.iter().enumerate() {
177 if i > 0 {
178 work_buf.push('\n');
179 }
180 work_buf.push_str(line);
181 }
182
183 if let Some(ref mut buf_writer) = self.buf_writer {
185 execute!(buf_writer, cursor::MoveTo(0, 0))?;
186 execute!(buf_writer, terminal::Clear(ClearType::All))?;
187 write!(buf_writer, "{}", work_buf)?;
188 } else {
189 let mut stdout = io::stdout();
190 execute!(stdout, cursor::MoveTo(0, 0))?;
191 execute!(stdout, terminal::Clear(ClearType::All))?;
192 write!(stdout, "{}", work_buf)?;
193 }
194
195 Ok(())
196 }
197
198 fn diff_render_content(&mut self, new_lines: &[&str], work_buf: &mut String) -> io::Result<()> {
200 let max_lines = new_lines.len().max(self.previous_lines.len());
201 work_buf.clear();
202
203 let mut pending_updates = Vec::with_capacity(max_lines);
205
206 for i in 0..max_lines {
207 let new_line = new_lines.get(i).copied().unwrap_or("");
208 let old_line = self
209 .previous_lines
210 .get(i)
211 .and_then(|(start, end)| self.previous_content.get(*start..*end))
212 .unwrap_or("");
213
214 if new_line.len() != old_line.len() || !self.lines_equal(new_line, old_line) {
216 pending_updates.push((i, new_line));
217 }
218 }
219
220 if !pending_updates.is_empty() {
222 self.apply_line_updates_content(&pending_updates, work_buf)?;
223 }
224
225 if self.previous_lines.len() > new_lines.len() {
227 if let Some(ref mut buf_writer) = self.buf_writer {
228 for i in new_lines.len()..self.previous_lines.len() {
229 execute!(buf_writer, cursor::MoveTo(0, i as u16))?;
230 execute!(buf_writer, terminal::Clear(ClearType::CurrentLine))?;
231 }
232 } else {
233 let mut stdout = io::stdout();
234 for i in new_lines.len()..self.previous_lines.len() {
235 execute!(stdout, cursor::MoveTo(0, i as u16))?;
236 execute!(stdout, terminal::Clear(ClearType::CurrentLine))?;
237 }
238 }
239 }
240
241 Ok(())
242 }
243
244 #[inline(always)]
246 fn lines_equal(&self, a: &str, b: &str) -> bool {
247 a == b
249 }
250
251 #[inline]
253 fn apply_line_updates_content(
254 &mut self,
255 updates: &[(usize, &str)],
256 work_buf: &mut String,
257 ) -> io::Result<()> {
258 work_buf.clear();
260 work_buf.reserve(updates.len() * 100); for &(line_idx, content) in updates {
263 use std::fmt::Write;
265 write!(work_buf, "\x1B[{};1H\x1B[2K{}", line_idx + 1, content)
266 .map_err(|_| io::Error::other("Format error"))?;
267 }
268
269 if let Some(ref mut buf_writer) = self.buf_writer {
271 buf_writer.write_all(work_buf.as_bytes())?;
272 } else {
273 io::stdout().write_all(work_buf.as_bytes())?;
274 }
275 Ok(())
276 }
277
278 pub fn clear(&mut self) -> io::Result<()> {
280 if let Some(ref mut buf_writer) = self.buf_writer {
281 execute!(buf_writer, terminal::Clear(ClearType::All))?;
282 execute!(buf_writer, cursor::MoveTo(0, 0))?;
283 buf_writer.flush()?;
284 } else {
285 let mut stdout = io::stdout();
286 execute!(stdout, terminal::Clear(ClearType::All))?;
287 execute!(stdout, cursor::MoveTo(0, 0))?;
288 stdout.flush()?;
289 }
290
291 self.previous_content.clear();
292 self.previous_lines.clear();
293 self.previous_hash = 0;
294
295 Ok(())
296 }
297
298 #[inline]
300 pub fn stats(&self) -> RenderStats {
301 RenderStats {
302 previous_lines: self.previous_lines.len(),
303 content_size: self.previous_content.len(),
304 terminal_size: (self.width, self.height),
305 content_hash: self.previous_hash,
306 buffer_capacity: self.work_buffer.capacity(),
307 }
308 }
309
310 pub fn reserve_capacity(&mut self, content_size: usize) {
312 if self.previous_content.capacity() < content_size {
313 self.previous_content
314 .reserve(content_size - self.previous_content.capacity());
315 }
316 if self.work_buffer.capacity() < content_size / 2 {
317 self.work_buffer.reserve(content_size / 2);
318 }
319 }
320}
321
322#[derive(Debug, Clone, Copy)]
324pub struct RenderStats {
325 pub previous_lines: usize,
327 pub content_size: usize,
329 pub terminal_size: (u16, u16),
331 pub content_hash: u64,
333 pub buffer_capacity: usize,
335}
336
337impl Default for StringRenderer {
338 fn default() -> Self {
339 Self::new()
340 }
341}