1use std::cell::RefCell;
13use std::fmt;
14use std::io;
15use std::process::Command;
16use std::rc::Rc;
17use std::time::Duration;
18
19use rich::{ColorSystem, Console, Segment};
20
21use crate::component::{Component, Context, Flow, View};
22use crate::event::{Button, Event, Key, Mouse, MouseKind};
23use crate::paint::Painter;
24use crate::policy::{Fallback, LineIo, NotInteractive, Policy, StdLineIo};
25use crate::session::{Backend, Session, SessionOptions};
26
27#[derive(Clone, Debug, PartialEq, Eq)]
29pub enum Outcome<T> {
30 Done(T),
31 Cancelled,
33 Interrupted,
35}
36
37impl<T> Outcome<T> {
38 pub fn value(self) -> Option<T> {
40 match self {
41 Outcome::Done(value) => Some(value),
42 _ => None,
43 }
44 }
45}
46
47struct Shared<T> {
50 outcome: Option<Outcome<T>>,
51 finished: bool,
52}
53
54impl<T> Shared<T> {
55 fn finish(&mut self, outcome: Outcome<T>) {
56 if !self.finished {
57 self.outcome = Some(outcome);
58 self.finished = true;
59 }
60 }
61}
62
63pub struct Handle<T>(Rc<RefCell<Shared<T>>>);
65
66impl<T> Handle<T> {
67 pub fn take(&self) -> Option<Outcome<T>> {
70 self.0.borrow_mut().outcome.take()
71 }
72
73 pub fn is_finished(&self) -> bool {
75 self.0.borrow().finished
76 }
77}
78
79#[derive(Clone, Debug)]
81pub struct LoopOptions {
82 pub height: Option<usize>,
84 pub transient: bool,
87 pub color_system: Option<Option<ColorSystem>>,
90 pub no_color: bool,
91}
92
93impl Default for LoopOptions {
94 fn default() -> Self {
95 LoopOptions {
96 height: None,
97 transient: false,
98 color_system: None,
99 no_color: std::env::var_os("NO_COLOR").is_some_and(|value| !value.is_empty()),
100 }
101 }
102}
103
104enum Step {
106 Continue,
107 Finished,
108 Handoff(Command),
109}
110
111trait Mounted {
113 fn start(&mut self, context: &Context<'_>) -> Option<Step>;
115 fn handle(&mut self, event: &Event, context: &Context<'_>) -> Step;
116 fn render(&self, context: &Context<'_>) -> View;
117 fn tick(&self) -> Option<Duration>;
118 fn finished(&self) -> bool;
119 fn interrupt(&mut self);
120}
121
122struct Slot<C: Component> {
123 component: C,
124 result: Rc<RefCell<Shared<C::Output>>>,
125 started: bool,
126}
127
128impl<C: Component> Slot<C> {
129 fn step(&mut self, flow: Flow<C::Output>) -> Step {
130 let outcome = match flow {
131 Flow::Continue | Flow::Ignored => return Step::Continue,
132 Flow::Handoff(command) => return Step::Handoff(command),
133 Flow::Done(value) => Outcome::Done(value),
134 Flow::Cancel => Outcome::Cancelled,
135 };
136 self.result.borrow_mut().finish(outcome);
137 Step::Finished
138 }
139}
140
141impl<C: Component> Mounted for Slot<C> {
142 fn start(&mut self, context: &Context<'_>) -> Option<Step> {
143 if std::mem::replace(&mut self.started, true) {
144 return None;
145 }
146 let flow = self.component.start(context);
147 Some(self.step(flow))
148 }
149
150 fn handle(&mut self, event: &Event, context: &Context<'_>) -> Step {
151 let flow = self.component.handle(event, context);
152 if let (Flow::Ignored, Some(key)) = (&flow, event.key()) {
155 let keymap = self.component.keymap();
156 let forward = keymap.is(key, crate::compose::FOCUS_NEXT);
157 if forward || keymap.is(key, crate::compose::FOCUS_PREVIOUS) {
158 self.component.focus_enter(forward);
159 }
160 }
161 self.step(flow)
162 }
163
164 fn render(&self, context: &Context<'_>) -> View {
165 self.component.render(context)
166 }
167
168 fn tick(&self) -> Option<Duration> {
169 self.component.tick()
170 }
171
172 fn finished(&self) -> bool {
173 self.result.borrow().finished
174 }
175
176 fn interrupt(&mut self) {
177 self.result.borrow_mut().finish(Outcome::Interrupted);
178 }
179}
180
181struct Timer<'a> {
182 interval: Duration,
183 next: Duration,
184 callback: Box<dyn FnMut() -> bool + 'a>,
185}
186
187pub struct EventLoop<'a> {
190 backend: Box<dyn Backend + 'a>,
191 options: LoopOptions,
192 console: Console,
193 painter: Painter,
194 mounted: Vec<(Box<dyn Mounted + 'a>, Option<Duration>)>,
195 timers: Vec<Timer<'a>>,
196 size: (u16, u16),
197 rows: Vec<usize>,
201 shown: Vec<Vec<Segment>>,
202 grab: Option<usize>,
205 primed: bool,
208}
209
210impl<'a> EventLoop<'a> {
211 pub fn new(backend: impl Backend + 'a, options: LoopOptions) -> EventLoop<'a> {
212 let size = backend.size();
213 let system = options.color_system.unwrap_or(Some(ColorSystem::Truecolor));
216 let console = Self::console(size, system, options.no_color);
217 EventLoop {
218 painter: Painter::new(system, options.no_color),
219 backend: Box::new(backend),
220 options,
221 console,
222 mounted: Vec::new(),
223 timers: Vec::new(),
224 size,
225 rows: Vec::new(),
226 shown: Vec::new(),
227 grab: None,
228 primed: false,
229 }
230 }
231
232 pub fn terminal(session: SessionOptions, options: LoopOptions) -> io::Result<EventLoop<'a>> {
234 let session = Session::start(session)?;
235 let system = options
238 .color_system
239 .or_else(|| session.color_system())
240 .unwrap_or_else(|| {
241 Console::builder()
242 .force_terminal(true)
243 .build()
244 .color_system()
245 });
246 Ok(EventLoop::new(
247 session,
248 LoopOptions {
249 color_system: Some(system),
250 ..options
251 },
252 ))
253 }
254
255 fn console(size: (u16, u16), system: Option<ColorSystem>, no_color: bool) -> Console {
256 Console::builder()
257 .width(size.0.max(1) as usize)
258 .height(size.1.max(1) as usize)
259 .force_terminal(true)
260 .color_system(system)
261 .no_color(no_color)
262 .build()
263 }
264
265 pub fn mount<C: Component + 'a>(&mut self, component: C) -> Handle<C::Output> {
267 let result = Rc::new(RefCell::new(Shared {
268 outcome: None,
269 finished: false,
270 }));
271 let tick = component.tick();
272 let next = tick.map(|interval| self.backend.elapsed() + interval);
273 self.mounted.push((
274 Box::new(Slot {
275 component,
276 result: Rc::clone(&result),
277 started: false,
278 }),
279 next,
280 ));
281 Handle(result)
282 }
283
284 pub fn graphics(&mut self, source: std::sync::Arc<dyn rich_ext::frame::PlacementSource>) {
289 self.painter.set_graphics(Some(source));
290 }
291
292 pub fn every(&mut self, interval: Duration, callback: impl FnMut() -> bool + 'a) {
295 self.timers.push(Timer {
296 interval,
297 next: self.backend.elapsed() + interval,
298 callback: Box::new(callback),
299 });
300 }
301
302 fn space(&self) -> (usize, usize) {
304 let rows = self.size.1.max(1) as usize;
305 (
306 self.size.0.max(1) as usize,
307 self.options.height.map_or(rows, |height| height.min(rows)),
308 )
309 }
310
311 fn context(&self) -> Context<'_> {
312 let (width, height) = self.space();
313 Context {
314 console: &self.console,
315 width,
316 height,
317 }
318 }
319
320 fn paint(&mut self) -> io::Result<()> {
321 let context = self.context();
322 let max_rows = context.height;
323 let mut view = View::default();
324 let mut rows = Vec::with_capacity(self.mounted.len());
325 for (component, _) in &self.mounted {
326 rows.push(view.lines.len());
327 view.push(component.render(&context));
328 }
329 if let Some((row, column)) = view.cursor
332 && let Some(line) = view.lines.get(row)
333 {
334 let shown = crate::paint::sanitized_column(line, column);
335 let last = context.width.saturating_sub(1).max(column);
336 view.cursor = Some((row, shown.min(last)));
337 }
338 for line in &mut view.lines {
342 crate::paint::sanitize_line(line);
343 if line.iter().map(Segment::cell_length).sum::<usize>() > context.width {
344 *line = Segment::adjust_line_length(line, context.width, None);
345 }
346 }
347 let out = self.painter.paint(&view, max_rows);
348 view.lines.truncate(max_rows);
349 if !out.is_empty() {
350 let plain = view
351 .lines
352 .iter()
353 .map(|line| line.iter().map(|s| s.text.as_str()).collect::<String>())
354 .collect::<Vec<_>>()
355 .join("\n");
356 self.backend.painted(&plain);
357 self.backend.write_frame(&out)?;
358 }
359 self.rows = rows;
360 self.shown = view.lines;
361 Ok(())
362 }
363
364 fn locate(&self, index: usize, mouse: Mouse) -> Option<Event> {
368 let region_row = self.region_row(mouse);
369 let (start, end) = self.extent(index);
370 let inside = region_row.filter(|row| (start..end).contains(row));
371 if let (MouseKind::Down(_), None) = (mouse.kind, inside) {
372 return None;
373 }
374 if let (MouseKind::Down(Button::Left), Some(row)) = (mouse.kind, inside)
375 && let Some(url) = link_at(&self.shown[row], mouse.column as usize)
376 {
377 return Some(Event::Link(url));
378 }
379 let row = region_row.map_or(0, |row| row.saturating_sub(start));
380 Some(Event::Mouse(Mouse {
381 row: u16::try_from(row).unwrap_or(u16::MAX),
382 ..mouse
383 }))
384 }
385
386 fn region_row(&self, mouse: Mouse) -> Option<usize> {
388 let top = if self.backend.alternate_screen() {
389 0
390 } else {
391 let rows = self.space().1;
394 (self.backend.origin() as usize).min(rows.saturating_sub(self.painter.extent()))
395 };
396 (mouse.row as usize).checked_sub(top)
397 }
398
399 fn extent(&self, index: usize) -> (usize, usize) {
401 let start = self.rows.get(index).copied().unwrap_or(0);
402 let end = self
403 .rows
404 .get(index + 1)
405 .copied()
406 .unwrap_or(self.shown.len());
407 (start, end)
408 }
409
410 fn mouse_target(&mut self, mouse: Mouse) -> Option<usize> {
415 let running = |index: &usize| !self.mounted[*index].0.finished();
416 let grabbed = self.grab.filter(running);
417 let under = self.region_row(mouse).and_then(|row| {
418 (0..self.mounted.len()).filter(running).find(|&index| {
419 let (start, end) = self.extent(index);
420 (start..end).contains(&row)
421 })
422 });
423 let first = (0..self.mounted.len()).find(running);
424 match mouse.kind {
425 MouseKind::Down(_) => {
426 self.grab = under;
427 under
428 }
429 MouseKind::Drag(_) => grabbed.or(under).or(first),
430 MouseKind::Up(_) => {
431 self.grab = None;
432 grabbed.or(under).or(first)
433 }
434 _ => under.or(first),
435 }
436 }
437
438 fn running(&self) -> bool {
439 self.mounted
440 .iter()
441 .any(|(component, _)| !component.finished())
442 }
443
444 fn deadline(&self) -> Option<Duration> {
446 let ticks = self
447 .mounted
448 .iter()
449 .filter(|(component, _)| !component.finished())
450 .filter_map(|(_, next)| *next);
451 let timers = self.timers.iter().map(|timer| timer.next);
452 let animation = self
453 .painter
454 .graphics()
455 .and_then(|source| source.next_change())
456 .map(|wait| self.backend.elapsed() + wait);
457 ticks.chain(timers).chain(animation).min()
458 }
459
460 fn fire_due(&mut self) -> io::Result<()> {
461 let now = self.backend.elapsed();
462 let mut timers = std::mem::take(&mut self.timers);
463 timers.retain_mut(|timer| {
464 if timer.next > now {
465 return true;
466 }
467 timer.next = now + timer.interval;
468 (timer.callback)()
469 });
470 timers.append(&mut self.timers);
471 self.timers = timers;
472 for index in 0..self.mounted.len() {
473 let due = self.mounted[index].1.is_some_and(|next| next <= now);
474 if due && !self.mounted[index].0.finished() {
475 let interval = self.mounted[index].0.tick();
476 self.mounted[index].1 = interval.map(|interval| now + interval);
477 self.deliver(index, &Event::Tick)?;
478 }
479 }
480 Ok(())
481 }
482
483 fn start(&mut self) -> io::Result<()> {
485 for index in 0..self.mounted.len() {
486 if self.mounted[index].0.finished() {
487 continue;
488 }
489 let (width, height) = self.space();
490 let context = Context {
491 console: &self.console,
492 width,
493 height,
494 };
495 if let Some(step) = self.mounted[index].0.start(&context) {
496 self.apply(index, step)?;
497 }
498 }
499 Ok(())
500 }
501
502 fn deliver(&mut self, index: usize, event: &Event) -> io::Result<()> {
503 let (width, height) = self.space();
504 let context = Context {
507 console: &self.console,
508 width,
509 height,
510 };
511 crate::clipboard::open(self.backend.clipboard().err());
514 let step = self.mounted[index].0.handle(event, &context);
515 for text in crate::clipboard::close() {
516 self.backend.copy(&text)?;
517 }
518 self.apply(index, step)
519 }
520
521 fn apply(&mut self, index: usize, step: Step) -> io::Result<()> {
524 if let Step::Handoff(mut command) = step {
525 let finish = self.painter.finish(false);
526 self.backend.write_frame(&finish)?;
527 let code = self.backend.handoff(&mut command)?;
528 self.painter.reset();
529 self.deliver(index, &Event::Returned(code))?;
530 }
531 Ok(())
532 }
533
534 fn resized(&mut self, columns: u16, rows: u16) -> io::Result<()> {
537 self.size = (columns, rows);
538 self.console = Self::console(
539 self.size,
540 self.console.color_system(),
541 self.options.no_color,
542 );
543 self.painter.invalidate();
544 let event = Event::Resize { columns, rows };
545 for index in 0..self.mounted.len() {
546 if !self.mounted[index].0.finished() {
547 self.deliver(index, &event)?;
548 }
549 }
550 Ok(())
551 }
552
553 pub fn run(&mut self) -> io::Result<()> {
556 let result = self.run_inner();
557 let written = self.finish();
558 result.and(written)
559 }
560
561 fn run_inner(&mut self) -> io::Result<()> {
562 self.prime()?;
563 while self.running() {
564 let event = self.backend.read(self.timeout())?;
565 self.turn(event)?;
566 }
567 Ok(())
568 }
569
570 fn prime(&mut self) -> io::Result<()> {
572 if !std::mem::replace(&mut self.primed, true) {
573 self.start()?;
574 self.paint()?;
575 }
576 Ok(())
577 }
578
579 fn turn(&mut self, event: Option<Event>) -> io::Result<()> {
583 match event {
584 None => {}
585 Some(Event::Key(key)) if key.is_interrupt() => {
586 for (component, _) in &mut self.mounted {
587 component.interrupt();
588 }
589 }
590 Some(Event::Key(key)) if key == Key::ctrl('z') && self.backend.can_suspend() => {
595 let finish = self.painter.finish(false);
596 self.backend.write_frame(&finish)?;
597 self.backend.suspend()?;
598 self.painter.reset();
599 let (columns, rows) = self.backend.size();
600 self.resized(columns, rows)?;
601 }
602 Some(Event::Resize { columns, rows }) => {
603 if self.backend.take_resumed() {
606 self.painter.reset();
607 }
608 self.resized(columns, rows)?;
609 }
610 Some(Event::Mouse(mouse)) => {
611 if let Some(index) = self.mouse_target(mouse)
612 && let Some(event) = self.locate(index, mouse)
613 {
614 self.deliver(index, &event)?;
615 }
616 }
617 Some(event) => {
618 if let Some(index) = self
619 .mounted
620 .iter()
621 .position(|(component, _)| !component.finished())
622 {
623 self.deliver(index, &event)?;
624 }
625 }
626 }
627 self.fire_due()?;
628 self.start()?;
629 self.paint()
630 }
631
632 pub fn step(&mut self, event: Event) -> io::Result<bool> {
663 self.prime()?;
664 if self.running() {
665 self.turn(Some(event))?;
666 }
667 Ok(self.running())
668 }
669
670 pub fn pump(&mut self) -> io::Result<bool> {
677 if self.primed {
678 if self.running() {
679 self.turn(None)?;
680 }
681 } else {
682 self.prime()?;
683 }
684 Ok(self.running())
685 }
686
687 pub fn timeout(&self) -> Option<Duration> {
691 self.deadline()
692 .map(|deadline| deadline.saturating_sub(self.backend.elapsed()))
693 }
694
695 pub fn is_running(&self) -> bool {
697 self.running()
698 }
699
700 pub fn finish(&mut self) -> io::Result<()> {
705 let finish = self.painter.finish(self.options.transient);
706 self.backend.write_frame(&finish)
707 }
708}
709
710#[derive(Debug)]
712pub enum Error {
713 Io(io::Error),
714 NotInteractive(NotInteractive),
715}
716
717impl fmt::Display for Error {
718 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
719 match self {
720 Error::Io(error) => write!(f, "{error}"),
721 Error::NotInteractive(error) => write!(f, "{error}"),
722 }
723 }
724}
725
726impl std::error::Error for Error {}
727
728impl From<io::Error> for Error {
729 fn from(error: io::Error) -> Error {
730 Error::Io(error)
731 }
732}
733
734#[derive(Clone, Debug, Default)]
736pub struct RunOptions {
737 pub policy: Policy,
738 pub session: SessionOptions,
739 pub paint: LoopOptions,
740}
741
742pub fn run<C: Component>(component: C, options: &RunOptions) -> Result<Outcome<C::Output>, Error> {
746 run_drawing(component, options, None)
747}
748
749pub fn run_with_graphics<C: Component>(
754 component: C,
755 options: &RunOptions,
756 graphics: std::sync::Arc<dyn rich_ext::frame::PlacementSource>,
757) -> Result<Outcome<C::Output>, Error> {
758 run_drawing(component, options, Some(graphics))
759}
760
761fn run_drawing<C: Component>(
762 component: C,
763 options: &RunOptions,
764 graphics: Option<std::sync::Arc<dyn rich_ext::frame::PlacementSource>>,
765) -> Result<Outcome<C::Output>, Error> {
766 let mut component = component;
767 if let Err(reason) = options.policy.detect_for(options.session.output) {
768 return degrade(
769 &mut component,
770 options.policy.fallback,
771 reason,
772 &mut StdLineIo,
773 );
774 }
775 let mut session = options.session;
776 if component.mouse() {
777 session.mouse = true;
778 if session.output == crate::session::Output::Stderr {
782 session.alternate_screen = true;
783 }
784 }
785 let mut event_loop = EventLoop::terminal(session, options.paint.clone())?;
786 if let Some(graphics) = graphics {
787 event_loop.graphics(graphics);
788 }
789 let handle = event_loop.mount(component);
790 event_loop.run()?;
791 drop(event_loop);
792 Ok(handle.take().unwrap_or(Outcome::Interrupted))
793}
794
795pub fn degrade<C: Component>(
802 component: &mut C,
803 fallback: Fallback,
804 reason: crate::policy::Reason,
805 io: &mut dyn LineIo,
806) -> Result<Outcome<C::Output>, Error> {
807 let default = |component: &C| {
808 component
809 .default_value()
810 .map(Outcome::Done)
811 .ok_or(Error::NotInteractive(NotInteractive::NoDefault(reason)))
812 };
813 match fallback {
814 Fallback::Error => Err(Error::NotInteractive(NotInteractive::Terminal(reason))),
815 Fallback::Default => default(component),
816 Fallback::Prompt => match component.prompt(&mut crate::policy::Shown(io)) {
817 Ok(Some(value)) => Ok(Outcome::Done(value)),
818 Ok(None) => Ok(Outcome::Cancelled),
819 Err(NotInteractive::NoPrompt) => default(component),
820 Err(NotInteractive::Ended) => {
821 Err(Error::NotInteractive(NotInteractive::NoDefault(reason)))
822 }
823 Err(NotInteractive::Interrupted) => Ok(Outcome::Interrupted),
824 Err(error) => Err(Error::NotInteractive(error)),
825 },
826 }
827}
828
829fn link_at(line: &[Segment], column: usize) -> Option<String> {
831 let mut at = 0;
832 for segment in line.iter().filter(|segment| !segment.control) {
833 let width = segment.cell_length();
834 if column < at + width {
835 return segment
836 .style
837 .as_ref()
838 .and_then(|style| style.link())
839 .filter(|url| !url.is_empty())
840 .map(str::to_string);
841 }
842 at += width;
843 }
844 None
845}