1mod region;
9use crate::{
10 frame::{Change, Frame, Placement, PlacementSource, plan_graphics},
11 layout::{OverflowPolicy, fit_segments},
12 target::RenderTarget,
13};
14use region::Region;
15pub use region::RegionId;
16use rich::{
17 Console, Segment,
18 control::{Control, ControlType},
19 protocol::RenderEnvironment,
20};
21use std::{io::Write, sync::Arc};
22#[derive(Debug)]
23pub enum LiveError {
24 InvalidRegion,
25 ExhaustedIds,
26 UnsupportedControl,
27 Closed,
28 Io(std::io::Error),
29}
30impl std::fmt::Display for LiveError {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 Self::Io(e) => write!(f, "live output: {e}"),
34 other => write!(f, "live region error: {other:?}"),
35 }
36 }
37}
38impl std::error::Error for LiveError {
39 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
40 if let Self::Io(e) = self {
41 Some(e)
42 } else {
43 None
44 }
45 }
46}
47impl From<std::io::Error> for LiveError {
48 fn from(e: std::io::Error) -> Self {
49 Self::Io(e)
50 }
51}
52#[derive(Debug)]
56struct Row {
57 text: String,
58 frame: Frame,
59 uncertain: bool,
60}
61
62impl Row {
63 fn placements(&self) -> &[Placement] {
64 self.frame.placements()
65 }
66}
67
68fn uncertain(text: &str) -> bool {
78 use unicode_width::UnicodeWidthChar;
79 text.chars().any(|c| {
80 matches!(
81 c,
82 '\u{fe0e}' | '\u{fe0f}'
83 | '\u{200d}'
84 | '\u{20e3}'
85 | '\u{1f1e6}'..='\u{1f1ff}'
86 | '\u{1f3fb}'..='\u{1f3ff}'
87 | '\u{e0020}'..='\u{e007f}'
88 ) || (c.width() != c.width_cjk() && !matches!(c, '\u{2500}'..='\u{259f}'))
89 })
90}
91impl PartialEq for Row {
92 fn eq(&self, other: &Row) -> bool {
93 self.text == other.text
94 && self.placements().len() == other.placements().len()
95 && self
96 .placements()
97 .iter()
98 .zip(other.placements())
99 .all(|(a, b)| a.same(b))
100 }
101}
102pub struct LiveCoordinator<W: Write> {
103 writer: W,
104 target: RenderTarget,
105 owner: Arc<()>,
106 next: u64,
107 regions: Vec<Region>,
108 width: usize,
109 height: usize,
110 painted: Vec<Row>,
111 hidden: bool,
112 closed: bool,
113 graphics: Option<Arc<dyn PlacementSource>>,
114 retained: Vec<Placement>,
116}
117impl<W: Write> LiveCoordinator<W> {
118 pub fn new(writer: W, target: RenderTarget) -> Self {
119 let c = target.capabilities();
120 Self {
121 writer,
122 target,
123 owner: Arc::new(()),
124 next: 0,
125 regions: Vec::new(),
126 width: c.width,
127 height: c.height,
128 painted: Vec::new(),
129 hidden: false,
130 closed: false,
131 graphics: None,
132 retained: Vec::new(),
133 }
134 }
135 #[must_use]
138 pub fn with_graphics(mut self, source: Arc<dyn PlacementSource>) -> Self {
139 self.graphics = Some(source);
140 self
141 }
142 fn source(&self) -> Option<&Arc<dyn PlacementSource>> {
144 self.graphics
145 .as_ref()
146 .filter(|_| self.target.capabilities().interactive)
147 }
148 fn check(&self) -> Result<(), LiveError> {
149 if self.closed {
150 Err(LiveError::Closed)
151 } else {
152 Ok(())
153 }
154 }
155 pub fn add(&mut self, content: Vec<Segment>) -> Result<RegionId, LiveError> {
156 self.check()?;
157 validate(&content)?;
158 let serial = self.next;
159 self.next = self.next.checked_add(1).ok_or(LiveError::ExhaustedIds)?;
160 let id = RegionId {
161 owner: self.owner.clone(),
162 serial,
163 };
164 self.regions.push(Region {
165 id: id.clone(),
166 content,
167 });
168 Ok(id)
169 }
170 fn index(&self, id: &RegionId) -> Result<usize, LiveError> {
171 if !Arc::ptr_eq(&self.owner, &id.owner) {
172 return Err(LiveError::InvalidRegion);
173 }
174 self.regions
175 .iter()
176 .position(|r| r.id.serial == id.serial)
177 .ok_or(LiveError::InvalidRegion)
178 }
179 pub fn update(&mut self, id: RegionId, content: Vec<Segment>) -> Result<(), LiveError> {
180 self.check()?;
181 validate(&content)?;
182 let index = self.index(&id)?;
183 self.regions[index].content = content;
184 Ok(())
185 }
186 pub fn remove(&mut self, id: RegionId) -> Result<(), LiveError> {
187 self.check()?;
188 let index = self.index(&id)?;
189 self.regions.remove(index);
190 Ok(())
191 }
192 pub fn handle(&mut self) -> LiveHandle<'_, W> {
193 LiveHandle { live: self }
194 }
195 fn dynamic(&self) -> bool {
196 self.target.capabilities().interactive && self.width > 1 && self.height > 1
197 }
198 fn control(&mut self, codes: &[ControlType]) -> std::io::Result<()> {
199 self.writer
200 .write_all(Control::new(codes).as_str().as_bytes())
201 }
202 fn row(&self, console: &Console, mut row: Vec<Segment>) -> Row {
203 if let Some(source) = self.source() {
204 row = source.prepare(row);
205 if validate(&row).is_err() {
207 row.retain(|segment| !segment.control);
208 }
209 }
210 if !self.target.capabilities().hyperlinks {
211 for segment in &mut row {
212 segment.style = segment.style.as_ref().map(|style| style.update_link(None));
213 }
214 }
215 let text = console.segments_to_string(&row);
216 let mut frame = Frame::from_segments(&row);
217 if let Some(source) = self.source() {
218 let placements = source
220 .placements(&frame)
221 .into_iter()
222 .filter(|p| p.row == 0 && p.rows == 1)
223 .collect();
224 frame.set_placements(placements);
225 }
226 Row {
227 uncertain: row.iter().any(|segment| uncertain(&segment.text)),
228 text,
229 frame,
230 }
231 }
232 fn graphics(&self, new: &Row, old: Option<&Row>, whole: bool) -> Vec<u8> {
236 if self.source().is_none() {
237 return Vec::new();
238 }
239 let width = new
240 .frame
241 .row_width(0)
242 .max(old.map_or(0, |o| o.frame.row_width(0)));
243 let changes = match old {
244 Some(old) if !whole => new.frame.diff(&old.frame),
245 _ => vec![Change {
246 row: 0,
247 columns: 0..width.max(1),
248 }],
249 };
250 let previous = old.map_or(&[][..], Row::placements);
251 let plan = plan_graphics(previous, new.placements(), &changes);
252 let console = self.target.console();
253 let (system, no_color) = (console.color_system(), console.no_color());
254 let mut out = String::new();
255 for (_, columns) in &plan.repaint {
256 let end = columns.end.min(new.frame.row_width(0));
257 out.push_str(
258 Control::new(&[ControlType::CursorMoveToColumn(columns.start as u32)]).as_str(),
259 );
260 if columns.start < end && new.frame.height() > 0 {
261 out.push_str(
262 &new.frame
263 .encode_span(0, columns.start..end, system, no_color),
264 );
265 }
266 }
267 for placement in &plan.draw {
268 out.push_str(
269 Control::new(&[ControlType::CursorMoveToColumn(placement.column as u32)]).as_str(),
270 );
271 out.push_str(&placement.graphic.draw(placement.frame));
272 }
273 out.into_bytes()
274 }
275 fn rows(&self, width: usize, height: usize) -> Vec<Row> {
276 let console = self.target.console();
277 let mut result = Vec::new();
278 for region in &self.regions {
279 for row in fit_segments(®ion.content, width, OverflowPolicy::Crop) {
280 if result.len() == height {
281 return result;
282 }
283 result.push(self.row(&console, row));
284 }
285 }
286 result
287 }
288 fn repaint(&self, new: &Row, old: &Row) -> (Vec<u8>, bool) {
292 let console = self.target.console();
293 let (system, no_color) = (console.color_system(), console.no_color());
294 let width = if new.frame.height() == 0 {
295 0
296 } else {
297 new.frame.row_width(0)
298 };
299 let mut cells = String::new();
300 if new.frame.height() > 0 && old.frame.height() > 0 && !new.uncertain && !old.uncertain {
301 for change in new.frame.diff(&old.frame) {
302 let column = change.columns.start.min(u32::MAX as usize) as u32;
303 cells.push_str(Control::new(&[ControlType::CursorMoveToColumn(column)]).as_str());
304 if change.columns.start < width {
305 cells.push_str(&new.frame.encode_span(
306 0,
307 change.columns.start..change.columns.end.min(width),
308 system,
309 no_color,
310 ));
311 }
312 if change.columns.end > width {
313 cells.push_str(Control::new(&[ControlType::EraseInLine(0)]).as_str());
314 }
315 }
316 }
317 let mut whole = Control::new(&[ControlType::EraseInLine(2)])
318 .as_str()
319 .to_string();
320 whole.push_str(&new.text);
321 if new.text == old.text {
322 (Vec::new(), false)
324 } else if !cells.is_empty() && cells.len() < whole.len() {
325 (cells.into_bytes(), false)
326 } else {
327 (whole.into_bytes(), true)
328 }
329 }
330 fn clear(&mut self) -> std::io::Result<()> {
331 let count = self
332 .painted
333 .len()
334 .min(self.height.saturating_sub(1))
335 .min(u32::MAX as usize);
336 if count == 0 {
337 self.painted.clear();
338 return Ok(());
339 }
340 self.control(&[
341 ControlType::CarriageReturn,
342 ControlType::CursorUp(count as u32),
343 ])?;
344 for _ in 0..count {
345 self.control(&[ControlType::EraseInLine(2)])?;
346 self.writer.write_all(b"\n\r")?;
347 }
348 self.control(&[ControlType::CursorUp(count as u32)])?;
349 self.painted.clear();
350 Ok(())
351 }
352 fn paint(&mut self) -> std::io::Result<()> {
353 if !self.dynamic() {
354 return Ok(());
355 }
356 let rows = self.rows(self.width - 1, (self.height - 1).min(u32::MAX as usize));
357 if rows == self.painted {
358 return Ok(());
359 }
360 if !self.hidden && !rows.is_empty() {
361 self.hidden = true;
362 self.control(&[ControlType::HideCursor])?;
363 }
364 if rows.len() == self.painted.len() {
365 for (i, row) in rows.iter().enumerate() {
366 if row == &self.painted[i] {
367 continue;
368 }
369 let distance = (rows.len() - i) as u32;
370 let (bytes, whole) = self.repaint(row, &self.painted[i]);
371 let graphics = self.graphics(row, Some(&self.painted[i]), whole);
372 self.control(&[ControlType::CarriageReturn, ControlType::CursorUp(distance)])?;
373 self.writer.write_all(&bytes)?;
374 self.writer.write_all(&graphics)?;
375 self.control(&[
376 ControlType::CarriageReturn,
377 ControlType::CursorDown(distance),
378 ])?;
379 }
380 } else {
381 self.clear()?;
382 for row in &rows {
383 self.control(&[ControlType::CarriageReturn, ControlType::EraseInLine(2)])?;
384 self.writer.write_all(row.text.as_bytes())?;
385 let graphics = self.graphics(row, None, true);
386 self.writer.write_all(&graphics)?;
387 self.writer.write_all(b"\n\r")?;
388 }
389 }
390 if rows.is_empty() && self.hidden {
391 self.hidden = false;
392 self.control(&[ControlType::ShowCursor])?;
393 }
394 self.painted = rows;
395 self.writer.flush()
396 }
397 fn fail(&mut self, error: std::io::Error) -> LiveError {
398 let _ = self.finish();
399 LiveError::Io(error)
400 }
401 pub fn refresh(&mut self) -> Result<(), LiveError> {
402 self.check()?;
403 self.paint().map_err(|e| self.fail(e))
404 }
405 pub fn resize(&mut self, width: usize, height: usize) -> Result<(), LiveError> {
406 self.check()?;
407 self.width = width;
408 self.height = height;
409 let result = (|| {
410 if !self.dynamic() {
411 self.painted.clear();
412 if self.hidden {
413 self.hidden = false;
414 self.control(&[ControlType::ShowCursor])?;
415 }
416 return self.writer.flush();
417 }
418 self.clear()?;
419 self.paint()
420 })();
421 result.map_err(|e| self.fail(e))
422 }
423 pub fn print(&mut self, content: &[Segment]) -> Result<(), LiveError> {
424 self.check()?;
425 validate(content)?;
426 let result = (|| {
427 self.clear()?;
428 let interactive = self.target.capabilities().interactive;
429 let width = if interactive {
432 self.width.saturating_sub(1)
433 } else {
434 self.width
435 }
436 .max(1);
437 let console = self.target.console();
438 for row in fit_segments(content, width, OverflowPolicy::Fold) {
439 let row = self.row(&console, row);
440 self.writer.write_all(row.text.as_bytes())?;
441 let graphics = self.graphics(&row, None, true);
442 self.writer.write_all(&graphics)?;
443 self.retained.extend(row.placements().iter().cloned());
444 self.writer
445 .write_all(if interactive { b"\n\r" } else { b"\n" })?;
446 }
447 self.paint()
448 })();
449 result.map_err(|e| self.fail(e))
450 }
451 pub fn finish(&mut self) -> Result<(), LiveError> {
452 if self.closed {
453 return Ok(());
454 }
455 self.closed = true;
456 let mut error = None;
457 if !self.target.capabilities().interactive {
458 for row in self.rows(self.width, usize::MAX) {
461 if let Err(e) = writeln!(self.writer, "{}", row.text) {
462 error = Some(e);
463 break;
464 }
465 }
466 } else if let Err(e) = self.clear() {
467 error = Some(e);
468 } else if let Some(source) = self.source() {
469 let release = source.release(&self.retained);
470 if let Err(e) = self.writer.write_all(release.as_bytes()) {
471 error = Some(e);
472 }
473 }
474 if self.hidden {
475 self.hidden = false;
476 if let Err(e) = self.control(&[ControlType::ShowCursor])
477 && error.is_none()
478 {
479 error = Some(e);
480 }
481 }
482 if let Err(e) = self.writer.flush()
483 && error.is_none()
484 {
485 error = Some(e);
486 }
487 error.map_or(Ok(()), |e| Err(LiveError::Io(e)))
488 }
489}
490impl<W: Write> Drop for LiveCoordinator<W> {
491 fn drop(&mut self) {
492 let _ = self.finish();
493 }
494}
495pub struct LiveHandle<'a, W: Write> {
496 live: &'a mut LiveCoordinator<W>,
497}
498impl<W: Write> LiveHandle<'_, W> {
499 pub fn print(&mut self, content: &[Segment]) -> Result<(), LiveError> {
500 self.live.print(content)
501 }
502 pub fn update(&mut self, id: RegionId, content: Vec<Segment>) -> Result<(), LiveError> {
503 self.live.update(id, content)
504 }
505 pub fn refresh(&mut self) -> Result<(), LiveError> {
506 self.live.refresh()
507 }
508}
509fn validate(content: &[Segment]) -> Result<(), LiveError> {
513 if content.iter().any(|s| {
514 s.control
515 || s.text
516 .chars()
517 .any(|c| c.is_control() && c != '\n' && c != '\t')
518 || s.style
519 .as_ref()
520 .and_then(|style| style.link())
521 .is_some_and(|link| link.chars().any(char::is_control))
522 }) {
523 Err(LiveError::UnsupportedControl)
524 } else {
525 Ok(())
526 }
527}