Skip to main content

pito_tui/
shell.rs

1use std::any::Any;
2use std::borrow::Cow;
3use std::io;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicUsize, Ordering};
6use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender};
7use std::thread;
8use std::time::{Duration, Instant};
9
10use crossterm::event::{Event, KeyEvent, KeyEventKind, MouseButton, MouseEvent, MouseEventKind};
11use pito_footer::{
12    Answer, Footer, Guard, Help, Hint, Key, Mode, QuitGuard, Tone as Said, WINDOW, Wording,
13};
14use pito_header::{Action, Group, Header, Nav, NavKeys, Section, Step as Move};
15use pito_list::Step;
16use ratatui::layout::Rect;
17use ratatui::style::{Color, Style};
18
19use crate::activity::{Activities, Activity, Board, Change, State};
20use crate::command::{self, Exit, Heard, Report, Run};
21use crate::copy;
22use crate::head::{Focus, Head, Numbers, Row};
23use crate::label::{Label, Span};
24use crate::layer::{Event as Layered, Outcome, Toast};
25use crate::layout::Layout;
26use crate::model::{Item, Model};
27use crate::pace::{Pace, wait_until};
28use crate::palette::Palette;
29use crate::screen::{Band, Cx, Job, Outbox, Phase, Screen};
30use crate::signal;
31use crate::term::{Modes, Term, restore};
32use crate::wake::{Wake, Waker, listen};
33use crate::words::Words;
34
35const SETTLE: usize = 64;
36const QUIT: &[Key] = &[Key::Ctrl('c')];
37const STOP: &[Key] = &[Key::Esc];
38
39pub type HeaderLook = for<'a> fn(Header<'a>) -> Header<'a>;
40pub type FooterLook = for<'a> fn(Footer<'a>, u16) -> Footer<'a>;
41
42type KeyHook<E> = Box<dyn FnMut(KeyEvent, &mut Cx<'_, E>) -> bool>;
43type DrawHook = Box<dyn FnMut(Duration)>;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Flow {
47    Stay,
48    Quit,
49}
50
51pub(crate) enum Role {
52    Tab,
53    Overlay(Nav, &'static [Key]),
54    Gate,
55    App,
56}
57
58pub(crate) struct Slot<E> {
59    pub(crate) screen: Box<dyn Screen<E>>,
60    pub(crate) role: Role,
61    pub(crate) started: bool,
62    pub(crate) stale: bool,
63    pub(crate) generation: u64,
64    pub(crate) reading: Option<u64>,
65    pub(crate) since: Option<Instant>,
66    pub(crate) glass: Option<Instant>,
67    pub(crate) label: String,
68    pub(crate) ticked: Option<Instant>,
69}
70
71impl<E> Slot<E> {
72    fn new(screen: Box<dyn Screen<E>>, role: Role) -> Self {
73        Slot {
74            screen,
75            role,
76            started: false,
77            stale: true,
78            generation: 0,
79            reading: None,
80            since: None,
81            glass: None,
82            label: String::new(),
83            ticked: None,
84        }
85    }
86}
87
88enum Digit {
89    Nav,
90    Pass,
91    Done(Flow),
92}
93
94pub struct Tui<E> {
95    pub(crate) name: Cow<'static, str>,
96    pub(crate) version: Cow<'static, str>,
97    pub(crate) title: Vec<Span>,
98    pub(crate) palette: Palette,
99    pub(crate) drawn: Palette,
100    mono: bool,
101    roles: Vec<(Cow<'static, str>, Style)>,
102    role: Option<Cow<'static, str>>,
103    pub(crate) words: Words,
104    pub(crate) hints: Vec<Hint<'static>>,
105    pub(crate) min: (u16, u16),
106    pub(crate) model: Model,
107    pub(crate) head: Option<Head>,
108    pub(crate) layout: Layout,
109    pub(crate) focus: Focus,
110    focus_keys: (&'static [Key], &'static [Key]),
111    pub(crate) screens: Vec<Slot<E>>,
112    pub(crate) open: Option<usize>,
113    pub(crate) board: Option<usize>,
114    pub(crate) band: Option<Box<dyn Band>>,
115    pub(crate) top_band: Option<Box<dyn Band>>,
116    pub(crate) gated: bool,
117    pressed: Option<KeyEvent>,
118    through: &'static [Key],
119    running: Arc<AtomicUsize>,
120    commands: Vec<command::Handle>,
121    pub(crate) help: Option<Help>,
122    pub(crate) quit: QuitGuard,
123    mode: Mode,
124    window: Duration,
125    triggers: &'static [Key],
126    stop: &'static [Key],
127    sticky: bool,
128    pub(crate) quit_tone: Said,
129    pub(crate) quit_hints: bool,
130    eager: bool,
131    pub(crate) keep_facts: bool,
132    pub(crate) lit_again: bool,
133    pub(crate) out: Outbox<E>,
134    pub(crate) inbox: Receiver<Wake<E>>,
135    pub(crate) moment: Instant,
136    modes: Modes,
137    pub(crate) header_look: Option<HeaderLook>,
138    pub(crate) footer_look: Option<FooterLook>,
139    on_key: Option<KeyHook<E>>,
140    after: Option<DrawHook>,
141    pub(crate) content: Rect,
142    pub(crate) top: Rect,
143    pub(crate) rows: Vec<(Row, Rect)>,
144    pub(crate) toasts: Vec<(Instant, Toast)>,
145    pub(crate) taken: Option<Instant>,
146    pub(crate) delay: Duration,
147    pub(crate) least: Duration,
148    pub(crate) version_fit: bool,
149}
150
151impl<E: Send + 'static> Tui<E> {
152    pub fn new(
153        name: impl Into<Cow<'static, str>>,
154        version: impl Into<Cow<'static, str>>,
155        accent: Color,
156    ) -> Self {
157        let (sender, inbox) = mpsc::channel();
158        let words = Words::new();
159        let name = name.into();
160        let palette = Palette::new(accent);
161        let mut tui = Tui {
162            title: vec![(name.clone(), Style::new())],
163            name,
164            version: version.into(),
165            palette,
166            drawn: palette,
167            mono: false,
168            roles: Vec::new(),
169            role: None,
170            quit: QuitGuard::new(Wording::new(words.again.clone())),
171            words,
172            hints: Vec::new(),
173            min: (40, 12),
174            model: Model::new(),
175            head: None,
176            layout: Layout::new(),
177            focus: Focus::Content,
178            focus_keys: (&[], &[]),
179            screens: Vec::new(),
180            open: None,
181            board: None,
182            band: None,
183            top_band: None,
184            gated: false,
185            pressed: None,
186            through: &[],
187            running: Arc::new(AtomicUsize::new(0)),
188            commands: Vec::new(),
189            help: Some(Help::new(true)),
190            mode: Mode::Ask,
191            window: WINDOW,
192            triggers: QUIT,
193            stop: STOP,
194            sticky: false,
195            quit_tone: Said::Accent,
196            quit_hints: true,
197            eager: false,
198            keep_facts: false,
199            lit_again: true,
200            out: Outbox::new(sender),
201            inbox,
202            moment: Instant::now(),
203            modes: Modes::new(),
204            header_look: None,
205            footer_look: None,
206            on_key: None,
207            after: None,
208            content: Rect::default(),
209            top: Rect::default(),
210            rows: Vec::new(),
211            toasts: Vec::new(),
212            taken: None,
213            delay: Duration::ZERO,
214            least: Duration::ZERO,
215            version_fit: false,
216        };
217        tui.rearm();
218        tui
219    }
220
221    pub fn palette(mut self, palette: Palette) -> Self {
222        self.set_palette(palette);
223        self
224    }
225
226    pub fn words(mut self, words: Words) -> Self {
227        self.set_words(words);
228        self
229    }
230
231    pub fn hints(mut self, hints: impl IntoIterator<Item = Hint<'static>>) -> Self {
232        self.hints = hints.into_iter().collect();
233        self
234    }
235
236    pub fn min_size(mut self, width: u16, height: u16) -> Self {
237        self.min = (width, height);
238        self
239    }
240
241    pub fn title(mut self, text: impl Into<Cow<'static, str>>) -> Self {
242        self.title = vec![(text.into(), Style::new())];
243        self
244    }
245
246    pub fn title_spans<T: Into<Cow<'static, str>>>(
247        mut self,
248        spans: impl IntoIterator<Item = (T, Style)>,
249    ) -> Self {
250        self.title = spans
251            .into_iter()
252            .map(|(text, style)| (text.into(), style))
253            .collect();
254        self
255    }
256
257    pub fn layout(mut self, layout: Layout) -> Self {
258        self.layout = layout;
259        self
260    }
261
262    pub fn head(mut self, head: Head) -> Self {
263        self.head = Some(head);
264        self
265    }
266
267    pub fn role(mut self, name: impl Into<Cow<'static, str>>, style: Style) -> Self {
268        let name = name.into();
269        self.roles.retain(|(kept, _)| *kept != name);
270        self.roles.push((name, style));
271        self.repaint();
272        self
273    }
274
275    pub fn monochrome(mut self, mono: bool) -> Self {
276        self.mono = mono;
277        self.repaint();
278        self
279    }
280
281    pub fn group(mut self, label: impl Into<Label>) -> Self {
282        self.model.add_group(label.into());
283        self
284    }
285
286    pub fn group_keys(mut self, keys: &'static [Key]) -> Self {
287        self.model.group_keys(keys);
288        self
289    }
290
291    pub fn screen(mut self, label: impl Into<Label>, screen: impl Screen<E>) -> Self {
292        let index = self.model.tabs.len();
293        self.screens
294            .insert(index, Slot::new(Box::new(screen), Role::Tab));
295        if let Some(board) = self.board.as_mut() {
296            *board += 1;
297        }
298        self.model.add_tab(label.into(), index);
299        self
300    }
301
302    pub fn show(mut self, shown: impl Fn(Item) -> bool) -> Self {
303        self.model.show(&shown);
304        self
305    }
306
307    pub fn overlay(
308        mut self,
309        section: Section,
310        keys: &'static [Key],
311        screen: impl Screen<E>,
312    ) -> Self {
313        let role = Role::Overlay(lone(section), keys);
314        self.screens.push(Slot::new(Box::new(screen), role));
315        self
316    }
317
318    pub fn app(mut self, screen: impl Screen<E>) -> Self {
319        self.screens.retain(|slot| !matches!(slot.role, Role::App));
320        self.reboard();
321        self.screens.push(Slot::new(Box::new(screen), Role::App));
322        self
323    }
324
325    pub fn gate(mut self, screen: impl Screen<E>, shut: bool) -> Self {
326        self.screens.retain(|slot| !matches!(slot.role, Role::Gate));
327        self.reboard();
328        self.screens.push(Slot::new(Box::new(screen), Role::Gate));
329        self.gated = shut;
330        self
331    }
332
333    fn reboard(&mut self) {
334        let board = self.screens.iter().position(|slot| {
335            let any: &dyn Any = slot.screen.as_ref();
336            any.is::<Board>()
337        });
338        if self.board.is_some() {
339            self.board = board;
340        }
341    }
342
343    pub fn activities(mut self, words: Activities) -> Self {
344        let role = Role::Overlay(lone(words.section.clone()), words.keys);
345        match self.board {
346            Some(index) => {
347                if let Some(board) = self.board_mut() {
348                    board.words = words;
349                }
350                self.screens[index].role = role;
351            }
352            None => {
353                self.board = Some(self.screens.len());
354                let board = Board::new(words, self.moment);
355                self.screens.push(Slot::new(Box::new(board), role));
356            }
357        }
358        self
359    }
360
361    pub fn band(mut self, band: impl Band + 'static) -> Self {
362        self.band = Some(Box::new(band));
363        self
364    }
365
366    pub fn top_band(mut self, band: impl Band + 'static) -> Self {
367        self.top_band = Some(Box::new(band));
368        self
369    }
370
371    pub(crate) fn board(&self) -> Option<&Board> {
372        let slot = self.screens.get(self.board?)?;
373        let any: &dyn Any = slot.screen.as_ref();
374        any.downcast_ref::<Board>()
375    }
376
377    fn board_mut(&mut self) -> Option<&mut Board> {
378        let slot = self.screens.get_mut(self.board?)?;
379        let any: &mut dyn Any = slot.screen.as_mut();
380        any.downcast_mut::<Board>()
381    }
382
383    pub fn nav_keys(mut self, keys: NavKeys) -> Self {
384        self.model.set_keys(keys);
385        self
386    }
387
388    pub fn focus_keys(mut self, next: &'static [Key], previous: &'static [Key]) -> Self {
389        self.focus_keys = (next, previous);
390        self
391    }
392
393    pub fn help(mut self, help: Option<Help>) -> Self {
394        self.help = help;
395        self
396    }
397
398    pub fn quit(mut self, mode: Mode) -> Self {
399        self.mode = mode;
400        self.rearm();
401        self
402    }
403
404    pub fn quit_window(mut self, window: Duration) -> Self {
405        self.window = window;
406        self.rearm();
407        self
408    }
409
410    pub fn quit_keys(mut self, keys: &'static [Key]) -> Self {
411        self.triggers = keys;
412        self.rearm();
413        self
414    }
415
416    pub fn quit_sticky(mut self, sticky: bool) -> Self {
417        self.sticky = sticky;
418        self
419    }
420
421    pub fn quit_tone(mut self, tone: Said) -> Self {
422        self.quit_tone = tone;
423        self
424    }
425
426    pub fn quit_hints(mut self, shown: bool) -> Self {
427        self.quit_hints = shown;
428        self
429    }
430
431    pub fn stop_keys(mut self, keys: &'static [Key]) -> Self {
432        self.stop = keys;
433        self
434    }
435
436    pub fn through_keys(mut self, keys: &'static [Key]) -> Self {
437        self.through = keys;
438        self
439    }
440
441    pub fn eager(mut self, eager: bool) -> Self {
442        self.eager = eager;
443        self
444    }
445
446    pub fn keep_facts(mut self, keep: bool) -> Self {
447        self.keep_facts = keep;
448        self
449    }
450
451    pub fn lit_again(mut self, lit: bool) -> Self {
452        self.lit_again = lit;
453        self
454    }
455
456    pub fn hourglass_timing(mut self, delay: Duration, least: Duration) -> Self {
457        self.delay = delay;
458        self.least = least;
459        self
460    }
461
462    pub fn version_fit(mut self, fit: bool) -> Self {
463        self.version_fit = fit;
464        self
465    }
466
467    pub fn modes(mut self, modes: Modes) -> Self {
468        self.modes = modes;
469        self
470    }
471
472    pub fn header_look(mut self, look: HeaderLook) -> Self {
473        self.header_look = Some(look);
474        self
475    }
476
477    pub fn footer_look(mut self, look: FooterLook) -> Self {
478        self.footer_look = Some(look);
479        self
480    }
481
482    pub fn on_key(mut self, hook: impl FnMut(KeyEvent, &mut Cx<'_, E>) -> bool + 'static) -> Self {
483        self.on_key = Some(Box::new(hook));
484        self
485    }
486
487    pub fn after_draw(mut self, hook: impl FnMut(Duration) + 'static) -> Self {
488        self.after = Some(Box::new(hook));
489        self
490    }
491
492    fn rearm(&mut self) {
493        self.quit = QuitGuard::new(Wording::new(self.words.again.clone()))
494            .mode(self.mode)
495            .window(self.window)
496            .triggers(self.triggers);
497    }
498
499    fn repaint(&mut self) {
500        let mut palette = self.palette;
501        let role = self
502            .role
503            .as_ref()
504            .and_then(|name| self.roles.iter().find(|(kept, _)| kept == name));
505        if let Some((_, style)) = role {
506            palette.title = *style;
507            palette.glass = *style;
508            palette.shimmer = *style;
509        }
510        self.drawn = if self.mono { palette.mono() } else { palette };
511    }
512
513    pub fn set_palette(&mut self, palette: Palette) {
514        self.palette = palette;
515        self.repaint();
516    }
517
518    pub fn set_words(&mut self, words: Words) {
519        *self.quit.wording_mut() = Wording::new(words.again.clone());
520        self.words = words;
521    }
522
523    pub fn set_layout(&mut self, layout: Layout) {
524        self.layout = layout;
525    }
526
527    pub fn current_palette(&self) -> &Palette {
528        &self.palette
529    }
530
531    pub(crate) fn slot_of(&self, wanted: fn(&Role) -> bool) -> Option<usize> {
532        self.screens.iter().position(|slot| wanted(&slot.role))
533    }
534
535    pub(crate) fn gate_slot(&self) -> Option<usize> {
536        self.slot_of(|role| matches!(role, Role::Gate))
537    }
538
539    pub(crate) fn app_slot(&self) -> Option<usize> {
540        self.slot_of(|role| matches!(role, Role::App))
541    }
542
543    pub fn current(&self) -> Option<usize> {
544        if self.gated
545            && let Some(gate) = self.gate_slot()
546        {
547            return Some(gate);
548        }
549        self.open.or_else(|| self.model.current_screen())
550    }
551
552    pub fn names(&self) -> Vec<&str> {
553        self.screens
554            .iter()
555            .enumerate()
556            .map(|(index, slot)| match &slot.role {
557                Role::Tab => self
558                    .model
559                    .tabs
560                    .iter()
561                    .find(|tab| tab.screen == index)
562                    .map_or("", |tab| tab.label.text()),
563                Role::Overlay(nav, _) => nav.section().map_or("", Section::name),
564                Role::Gate | Role::App => "",
565            })
566            .collect()
567    }
568
569    pub(crate) fn walkable(&self, index: usize) -> bool {
570        self.screens
571            .get(index)
572            .is_some_and(|slot| matches!(slot.role, Role::Tab | Role::Overlay(..)))
573    }
574
575    pub fn find<T: Screen<E>>(&self) -> Option<&T> {
576        self.screens.iter().find_map(|slot| {
577            let any: &dyn Any = slot.screen.as_ref();
578            any.downcast_ref::<T>()
579        })
580    }
581
582    pub fn find_mut<T: Screen<E>>(&mut self) -> Option<&mut T> {
583        self.screens.iter_mut().find_map(|slot| {
584            let any: &mut dyn Any = slot.screen.as_mut();
585            any.downcast_mut::<T>()
586        })
587    }
588
589    pub fn sender(&self) -> Sender<Wake<E>> {
590        self.out.sender.clone()
591    }
592
593    pub fn waker(&self, screen: usize) -> Waker<E> {
594        Waker::new(self.out.sender.clone(), screen).epoch(Arc::clone(&self.out.epoch))
595    }
596
597    pub fn go(&mut self, screen: usize) -> bool {
598        match self.screens.get(screen).map(|slot| &slot.role) {
599            Some(Role::Tab) => {
600                if !self.model.go_screen(screen) {
601                    return false;
602                }
603                self.open = None;
604            }
605            Some(Role::Overlay(..)) => self.open = Some(screen),
606            Some(Role::Gate | Role::App) | None => return false,
607        }
608        self.focus = Focus::Content;
609        self.enter();
610        true
611    }
612
613    pub(crate) fn reveal(&mut self) {
614        self.gated = false;
615    }
616
617    pub fn changed(&mut self, source: &str) {
618        for slot in &mut self.screens {
619            if slot.screen.sources().contains(&source) {
620                slot.stale = true;
621            }
622        }
623    }
624
625    pub fn start(&mut self) {
626        if let Some(app) = self.app_slot() {
627            self.begin(app);
628        }
629        if let Some(index) = self.current() {
630            self.begin(index);
631        }
632        self.follow();
633    }
634
635    pub fn busy(&self) -> usize {
636        let screens: usize = self.screens.iter().map(|slot| slot.screen.busy()).sum();
637        screens + self.running.load(Ordering::SeqCst)
638    }
639
640    pub fn key(&mut self, key: KeyEvent) -> Flow {
641        if key.kind == KeyEventKind::Release {
642            return Flow::Stay;
643        }
644        self.pressed = Some(key);
645        let flow = self.route(key);
646        self.pressed = None;
647        flow
648    }
649
650    fn overlay_for(&self, key: Key) -> Option<usize> {
651        self.screens
652            .iter()
653            .position(|slot| matches!(&slot.role, Role::Overlay(_, keys) if keys.contains(&key)))
654    }
655
656    pub(crate) fn typing_now(&self) -> bool {
657        if let Some(top) = self.out.modals.last() {
658            return top.modal.value(top.modal.focused()).is_some();
659        }
660        self.current()
661            .and_then(|index| self.screens.get(index))
662            .is_some_and(|slot| slot.screen.typing() || matches!(slot.role, Role::Gate))
663    }
664
665    fn route(&mut self, key: KeyEvent) -> Flow {
666        let busy = self.busy();
667        let pressed: Key = key.into();
668        let sticky = self.sticky
669            && self.quit.armed()
670            && self.quit.asking().is_none()
671            && !self.triggers.contains(&pressed);
672        if !sticky {
673            match self.quit.key(pressed, self.moment, busy > 0) {
674                Guard::Quit => return Flow::Quit,
675                Guard::Held => {
676                    self.word_quit();
677                    return Flow::Stay;
678                }
679                _ => {}
680            }
681        }
682        self.out.said = None;
683        self.follow();
684        if let Some((owner, takeover)) = self.out.takeover.as_ref() {
685            if takeover.cancel.contains(&pressed) {
686                let (owner, id) = (*owner, takeover.id);
687                self.out.takeover = None;
688                self.call(owner, |screen, cx| screen.layer(id, Layered::Closed, cx));
689                return self.apply();
690            }
691            return Flow::Stay;
692        }
693        if !self.out.modals.is_empty() {
694            return self.modal_key(key);
695        }
696        let through = self.through.contains(&pressed);
697        if let Some(asking) = self.out.asking.as_mut().filter(|_| !through) {
698            if let Some(answer) = asking.confirm.key(pressed) {
699                let screen = asking.screen;
700                self.out.asking = None;
701                let yes = matches!(answer, Answer::Yes);
702                self.call(screen, |screen, cx| screen.answer(yes, cx));
703            }
704            return self.apply();
705        }
706        if let Some(picking) = self.out.picking.as_mut() {
707            let choice = if picking.pick.cancel.contains(&pressed) {
708                Some(None)
709            } else if let Step::Open(index) = picking.pick.rows.key(key.into()) {
710                Some(Some(index))
711            } else {
712                None
713            };
714            if let Some(choice) = choice {
715                let screen = picking.screen;
716                self.out.picking = None;
717                self.call(screen, |screen, cx| screen.picked(choice, cx));
718            }
719            return self.apply();
720        }
721        if let Some(app) = self.app_slot()
722            && self.call(app, |screen, cx| screen.claim(key, cx)) == Some(true)
723        {
724            return self.apply();
725        }
726        let Some(index) = self.current() else {
727            return Flow::Stay;
728        };
729        if self.gated {
730            self.call(index, |screen, cx| screen.key(key, cx));
731            return self.apply();
732        }
733        if self.screens[index].screen.typing() {
734            self.call(index, |screen, cx| screen.key(key, cx));
735            return self.apply();
736        }
737        if self.help.as_mut().is_some_and(|help| help.key(pressed)) {
738            return Flow::Stay;
739        }
740        if through && self.out.asking.is_some() {
741            return self.press(index, key);
742        }
743        if let Some(flow) = self.focused(pressed) {
744            return flow;
745        }
746        if let Some(overlay) = self.overlay_for(pressed) {
747            if self.open == Some(overlay) {
748                self.open = None;
749            } else {
750                self.go(overlay);
751            }
752            return self.apply();
753        }
754        if let Some(open) = self.open {
755            if self.model.keys.back.contains(&key.into())
756                && !(self.loading(open) && self.stop.contains(&pressed))
757            {
758                self.leave(open);
759                return self.apply();
760            }
761        } else {
762            if let Some(heading) = self.model.heading_for(pressed) {
763                if self.model.go_heading(heading) {
764                    self.enter();
765                }
766                return self.apply();
767            }
768            match self.digit(pressed) {
769                Digit::Done(flow) => return flow,
770                Digit::Pass => return self.press(index, key),
771                Digit::Nav => {}
772            }
773            if let Some(action) = self.model.nav.action(key.into()) {
774                self.navigate(action);
775                return self.apply();
776            }
777        }
778        if self.loading(index) && self.stop.contains(&pressed) {
779            self.cancel(index);
780            return Flow::Stay;
781        }
782        self.press(index, key)
783    }
784
785    fn digit(&mut self, key: Key) -> Digit {
786        let Some(head) = self.head.as_ref() else {
787            return Digit::Nav;
788        };
789        let Key::Char(digit @ '0'..='9') = key else {
790            return Digit::Nav;
791        };
792        if !self.model.keys.digits {
793            return Digit::Nav;
794        }
795        let number = if digit == '0' {
796            10
797        } else {
798            digit as usize - '0' as usize
799        };
800        let lone = head.lone;
801        let swallow = self.model.keys.swallow_digits;
802        let missing = if swallow {
803            Digit::Done(Flow::Stay)
804        } else {
805            Digit::Pass
806        };
807        match head.numbers {
808            Numbers::Off => Digit::Pass,
809            Numbers::Across => {
810                let Some(tab) = self.model.visible.get(number - 1) else {
811                    return Digit::Nav;
812                };
813                let heading = self.model.tabs[*tab].group;
814                let group = self.model.groups.iter().position(|kept| *kept == heading);
815                if lone && group.is_some_and(|group| self.model.lone(group)) {
816                    return missing;
817                }
818                Digit::Nav
819            }
820            Numbers::InGroup => {
821                let group = self.model.nav.place().group;
822                let count = self.model.group_tabs().len();
823                if (lone && count == 1) || number > count {
824                    return missing;
825                }
826                if self.model.nav.go_to(group, number - 1).is_some() {
827                    self.enter();
828                }
829                Digit::Done(self.apply())
830            }
831        }
832    }
833
834    pub(crate) fn ring(&self) -> Vec<Focus> {
835        let mut ring = Vec::new();
836        let Some(head) = self.head.as_ref() else {
837            return ring;
838        };
839        if self.open.is_some() || self.gated {
840            return ring;
841        }
842        let slot = self
843            .current()
844            .and_then(|index| self.screens.get(index))
845            .is_some_and(|slot| !slot.screen.after_tabs().is_empty());
846        for row in &head.rows {
847            match row {
848                Row::Groups if self.model.groups.len() > 1 => ring.push(Focus::Groups),
849                Row::Sections if self.model.nav.count() > 0 => {
850                    ring.push(Focus::Sections);
851                    if slot {
852                        ring.push(Focus::Slot);
853                    }
854                }
855                _ => {}
856            }
857        }
858        ring
859    }
860
861    fn focused(&mut self, key: Key) -> Option<Flow> {
862        let ring = self.ring();
863        if ring.is_empty() {
864            self.focus = Focus::Content;
865            return None;
866        }
867        let at = ring.iter().position(|focus| *focus == self.focus);
868        if at.is_none() {
869            self.focus = Focus::Content;
870        }
871        let stops = ring.len() + 1;
872        let index = at.unwrap_or(ring.len());
873        let step = |by: isize| {
874            let next = (index as isize + by).rem_euclid(stops as isize) as usize;
875            ring.get(next).copied().unwrap_or(Focus::Content)
876        };
877        let (next, previous) = self.focus_keys;
878        if next.contains(&key) {
879            self.focus = step(1);
880            return Some(Flow::Stay);
881        }
882        if previous.contains(&key) {
883            self.focus = step(-1);
884            return Some(Flow::Stay);
885        }
886        let action = match (self.focus, key) {
887            (Focus::Content, _) => return None,
888            (_, Key::Esc | Key::Enter) => {
889                self.focus = Focus::Content;
890                return Some(Flow::Stay);
891            }
892            (_, Key::Up) => {
893                if index > 0 {
894                    self.focus = ring[index - 1];
895                }
896                return Some(Flow::Stay);
897            }
898            (_, Key::Down) => {
899                self.focus = step(1);
900                return Some(Flow::Stay);
901            }
902            (Focus::Groups, Key::Left) => Action::PrevGroup,
903            (Focus::Groups, Key::Right) => Action::NextGroup,
904            (Focus::Sections, Key::Left) => Action::PrevSection,
905            (Focus::Sections, Key::Right) => Action::NextSection,
906            _ => return None,
907        };
908        self.navigate(action);
909        Some(self.apply())
910    }
911
912    fn modal_key(&mut self, key: KeyEvent) -> Flow {
913        let Some(top) = self.out.modals.last_mut() else {
914            return Flow::Stay;
915        };
916        let (owner, id) = (top.screen, top.modal.id);
917        match top.modal.key(key) {
918            Outcome::Stay => Flow::Stay,
919            Outcome::Heard(event) => {
920                self.call(owner, |screen, cx| screen.layer(id, event, cx));
921                self.apply()
922            }
923            Outcome::Close => {
924                self.out.modals.pop();
925                self.call(owner, |screen, cx| screen.layer(id, Layered::Closed, cx));
926                self.apply()
927            }
928        }
929    }
930
931    fn press(&mut self, index: usize, key: KeyEvent) -> Flow {
932        if let Some(mut hook) = self.on_key.take() {
933            let typing = self.typing_now();
934            let mut cx = Cx {
935                screen: index,
936                now: self.moment,
937                key: self.pressed,
938                focus: self.focus,
939                typing,
940                out: &mut self.out,
941            };
942            let taken = hook(key, &mut cx);
943            self.on_key = Some(hook);
944            if taken {
945                return self.apply();
946            }
947        }
948        self.call(index, |screen, cx| screen.key(key, cx));
949        self.apply()
950    }
951
952    pub fn paste(&mut self, text: &str) {
953        if self.out.takeover.is_some()
954            || self.out.asking.is_some()
955            || self.out.picking.is_some()
956            || self.quit.asking().is_some()
957        {
958            return;
959        }
960        if let Some(top) = self.out.modals.last_mut() {
961            let (owner, id) = (top.screen, top.modal.id);
962            if let Outcome::Heard(event) = top.modal.paste(text) {
963                self.call(owner, |screen, cx| screen.layer(id, event, cx));
964                self.apply();
965            }
966            return;
967        }
968        if let Some(index) = self.current() {
969            self.call(index, |screen, cx| screen.paste(text, cx));
970            self.apply();
971        }
972    }
973
974    pub fn mouse(&mut self, mouse: MouseEvent) {
975        if self.out.takeover.is_some() || !self.out.modals.is_empty() {
976            return;
977        }
978        let Some(index) = self.current() else {
979            return;
980        };
981        let (column, row) = (mouse.column, mouse.row);
982        let down = mouse.kind == MouseEventKind::Down(MouseButton::Left);
983        if down && !self.gated && self.head.is_some() {
984            let hit = self
985                .rows
986                .iter()
987                .find(|(_, line)| line.contains((column, row).into()))
988                .copied();
989            if let Some((kind, line)) = hit {
990                self.click(kind, line, column);
991                return;
992            }
993        }
994        if down && !self.gated && self.head.is_none() && self.top.contains((column, row).into()) {
995            let facts = self.screens[index].screen.facts();
996            let title = self.title_text();
997            let place = self
998                .header(&title, &facts, &[], &[])
999                .hit(self.top, column, row);
1000            if let Some(place) = place
1001                && self.model.nav.go_to(place.group, place.section).is_some()
1002            {
1003                self.open = None;
1004                self.enter();
1005            }
1006            return;
1007        }
1008        let area = self.content;
1009        self.call(index, |screen, cx| screen.mouse(mouse, area, cx));
1010        self.apply();
1011    }
1012
1013    fn click(&mut self, kind: Row, line: Rect, column: u16) {
1014        let moved = match kind {
1015            Row::Groups => self
1016                .group_labels()
1017                .hit(line, column)
1018                .and_then(|group| self.model.groups.get(group).copied())
1019                .is_some_and(|heading| self.model.go_heading(heading)),
1020            Row::Sections if self.open.is_none() && self.model.nav.depth() == 0 => {
1021                let group = self.model.nav.place().group;
1022                let tail = self.tail();
1023                self.section_labels(&tail)
1024                    .hit(line, column)
1025                    .is_some_and(|section| self.model.nav.go_to(group, section).is_some())
1026            }
1027            _ => false,
1028        };
1029        if moved {
1030            self.open = None;
1031            self.enter();
1032        }
1033    }
1034
1035    pub fn event(&mut self, screen: usize, event: E) {
1036        self.call(screen, |target, cx| target.event(event, cx));
1037        self.apply();
1038    }
1039
1040    pub fn loaded(&mut self, screen: usize, generation: u64, event: E) -> bool {
1041        let Some(slot) = self.screens.get_mut(screen) else {
1042            return false;
1043        };
1044        if slot.reading == Some(generation) {
1045            slot.reading = None;
1046        }
1047        if generation != slot.generation {
1048            return false;
1049        }
1050        self.call(screen, |target, cx| target.loaded(event, cx));
1051        self.apply();
1052        true
1053    }
1054
1055    pub fn handle(&mut self, wake: Wake<E>) -> Flow {
1056        match wake {
1057            Wake::Input(Event::Key(key)) => return self.key(key),
1058            Wake::Input(Event::Paste(text)) => self.paste(&text),
1059            Wake::Input(Event::Mouse(mouse)) => self.mouse(mouse),
1060            Wake::Event(screen, event) => self.event(screen, event),
1061            Wake::Stamped(stamp, screen, event) => {
1062                if stamp == self.out.stamp() {
1063                    self.event(screen, event);
1064                }
1065            }
1066            Wake::Loaded(screen, generation, event) => {
1067                self.loaded(screen, generation, event);
1068            }
1069            Wake::Activity(activity) => {
1070                self.out.changes.push(Change::Put(activity));
1071                self.apply();
1072            }
1073            Wake::Command(screen, report) => return self.heard(screen, report),
1074            Wake::Signal(_) => return Flow::Quit,
1075            _ => {}
1076        }
1077        Flow::Stay
1078    }
1079
1080    fn heard(&mut self, screen: usize, report: Report) -> Flow {
1081        let Report { activity, heard } = report;
1082        let id = activity.id;
1083        let mut end = None;
1084        for heard in heard {
1085            if let Heard::Exit(exit) = &heard {
1086                end = Some(exit.state());
1087            }
1088            self.call(screen, |target, cx| target.heard(id, heard, cx));
1089        }
1090        self.out.changes.push(Change::Ran(activity));
1091        self.out
1092            .changes
1093            .extend(end.map(|state| Change::End(id, state)));
1094        self.apply()
1095    }
1096
1097    fn navigate(&mut self, action: Action) {
1098        match action {
1099            Action::Back => {
1100                if let Some(Move::Back { .. }) = self.model.nav.apply(Action::Back)
1101                    && let Some(index) = self.current()
1102                {
1103                    self.call(index, |screen, cx| screen.back(cx));
1104                }
1105            }
1106            action => {
1107                if let Some(Move::Moved(_)) = self.model.nav.apply(action) {
1108                    self.enter();
1109                }
1110            }
1111        }
1112    }
1113
1114    fn leave(&mut self, index: usize) {
1115        let Some(Role::Overlay(nav, _)) = self.screens.get_mut(index).map(|slot| &mut slot.role)
1116        else {
1117            return;
1118        };
1119        if nav.back().is_some() {
1120            self.call(index, |screen, cx| screen.back(cx));
1121        } else {
1122            self.open = None;
1123        }
1124    }
1125
1126    fn enter(&mut self) {
1127        if let Some(index) = self.current() {
1128            self.begin(index);
1129            self.call(index, |screen, cx| screen.entered(cx));
1130            self.apply();
1131        }
1132    }
1133
1134    fn begin(&mut self, index: usize) {
1135        if self.screens[index].started {
1136            return;
1137        }
1138        self.screens[index].started = true;
1139        self.call(index, |screen, cx| screen.start(cx));
1140        self.apply();
1141    }
1142
1143    pub(crate) fn follow(&mut self) {
1144        let Some(index) = self.current() else {
1145            return;
1146        };
1147        let slot = &mut self.screens[index];
1148        let crumb = slot.screen.crumb();
1149        let nav = match &mut slot.role {
1150            Role::Overlay(nav, _) => nav,
1151            Role::Tab => &mut self.model.nav,
1152            Role::Gate | Role::App => return,
1153        };
1154        if nav.title() == crumb.as_deref() {
1155            return;
1156        }
1157        let selected = slot.screen.selected();
1158        nav.close();
1159        if let Some(crumb) = crumb {
1160            nav.open(crumb, selected);
1161        }
1162    }
1163
1164    pub(crate) fn word_quit(&mut self) {
1165        if self.quit.asking().is_none() {
1166            return;
1167        }
1168        let ask = (self.words.busy)(self.busy());
1169        self.quit.wording_mut().ask = ask.into();
1170    }
1171
1172    pub(crate) fn loading(&self, index: usize) -> bool {
1173        self.screens
1174            .get(index)
1175            .is_some_and(|slot| matches!(slot.screen.phase(), Phase::Loading(_)))
1176    }
1177
1178    fn cancel(&mut self, index: usize) {
1179        let slot = &mut self.screens[index];
1180        slot.generation += 1;
1181        slot.reading = None;
1182        slot.screen.cancel();
1183    }
1184
1185    fn call<R>(
1186        &mut self,
1187        index: usize,
1188        work: impl FnOnce(&mut dyn Screen<E>, &mut Cx<'_, E>) -> R,
1189    ) -> Option<R> {
1190        let typing = self.typing_now();
1191        let slot = self.screens.get_mut(index)?;
1192        let mut cx = Cx {
1193            screen: index,
1194            now: self.moment,
1195            key: self.pressed,
1196            focus: self.focus,
1197            typing,
1198            out: &mut self.out,
1199        };
1200        Some(work(slot.screen.as_mut(), &mut cx))
1201    }
1202
1203    fn apply(&mut self) -> Flow {
1204        if let Some(palette) = self.out.palette.take() {
1205            self.palette = palette;
1206            self.repaint();
1207        }
1208        if let Some(role) = self.out.role.take() {
1209            self.role = role;
1210            self.repaint();
1211        }
1212        if let Some(words) = self.out.words.take() {
1213            self.set_words(words);
1214        }
1215        if let Some(hints) = self.out.hints.take() {
1216            self.hints = hints;
1217        }
1218        if let Some(title) = self.out.title.take() {
1219            self.title = title;
1220        }
1221        if let Some(layout) = self.out.layout.take() {
1222            self.layout = layout;
1223        }
1224        if std::mem::take(&mut self.out.bump) {
1225            self.out.epoch.fetch_add(1, Ordering::SeqCst);
1226            self.out.jobs.clear();
1227            for slot in &mut self.screens {
1228                slot.generation += 1;
1229                slot.reading = None;
1230                slot.stale = true;
1231            }
1232        }
1233        for index in std::mem::take(&mut self.out.reload) {
1234            if let Some(slot) = self.screens.get_mut(index) {
1235                slot.stale = true;
1236            }
1237        }
1238        for source in std::mem::take(&mut self.out.changed) {
1239            self.changed(&source);
1240        }
1241        let moment = self.moment;
1242        for change in std::mem::take(&mut self.out.changes) {
1243            if let Change::Forget(id) = change {
1244                for handle in self.commands.iter().filter(|handle| handle.id == id) {
1245                    handle.forget();
1246                }
1247            }
1248            if let Some(board) = self.board_mut() {
1249                board.change(change, moment);
1250            }
1251        }
1252        for id in std::mem::take(&mut self.out.stops) {
1253            let stopped = self.stop(id);
1254            self.show_stopped(stopped);
1255        }
1256        for toast in std::mem::take(&mut self.out.toasts) {
1257            self.toasts.push((moment + toast.lasting, toast));
1258        }
1259        match (&self.out.takeover, self.taken) {
1260            (Some(_), None) => self.taken = Some(moment),
1261            (None, Some(_)) => self.taken = None,
1262            _ => {}
1263        }
1264        if let Some(shown) = self.out.show.take()
1265            && self.model.show(&*shown)
1266            && self.open.is_none()
1267        {
1268            self.focus = Focus::Content;
1269            self.enter();
1270        }
1271        if let Some(heading) = self.out.group.take()
1272            && self.model.go_heading(heading)
1273        {
1274            self.open = None;
1275            self.focus = Focus::Content;
1276            self.enter();
1277        }
1278        if let Some(shut) = self.out.gate.take()
1279            && shut != self.gated
1280            && self.gate_slot().is_some()
1281        {
1282            self.gated = shut;
1283            self.focus = Focus::Content;
1284            self.enter();
1285        }
1286        if let Some(screen) = self.out.go.take() {
1287            self.go(screen);
1288        }
1289        if std::mem::take(&mut self.out.quit) {
1290            return Flow::Quit;
1291        }
1292        Flow::Stay
1293    }
1294
1295    fn stop(&mut self, id: u64) -> Vec<Activity> {
1296        let moment = self.moment;
1297        let mut stopped: Vec<Activity> = self
1298            .commands
1299            .iter()
1300            .filter(|handle| handle.id == id)
1301            .filter_map(|handle| handle.stop(moment))
1302            .collect();
1303        stopped.extend(self.unqueue(|run| run.activity.id == id));
1304        stopped
1305    }
1306
1307    fn unqueue(&mut self, which: impl Fn(&Run) -> bool) -> Vec<Activity> {
1308        let moment = self.moment;
1309        let (queued, runs): (Vec<Run>, Vec<Run>) = std::mem::take(&mut self.out.runs)
1310            .into_iter()
1311            .partition(which);
1312        self.out.runs = runs;
1313        queued
1314            .into_iter()
1315            .map(|run| {
1316                let mut activity = run.activity;
1317                command::halt(&mut activity, moment);
1318                let report = Report {
1319                    activity: activity.clone(),
1320                    heard: vec![Heard::Exit(Exit::Stopped)],
1321                };
1322                let _ = self.out.sender.send(Wake::Command(run.screen, report));
1323                activity
1324            })
1325            .collect()
1326    }
1327
1328    fn show_stopped(&mut self, stopped: Vec<Activity>) {
1329        let moment = self.moment;
1330        let Some(board) = self.board_mut() else {
1331            return;
1332        };
1333        for activity in stopped {
1334            let id = activity.id;
1335            board.change(Change::Ran(activity), moment);
1336            board.change(Change::End(id, State::Stopped), moment);
1337        }
1338    }
1339
1340    fn end(&mut self) {
1341        let moment = self.tick();
1342        let mut stopped = command::end(&self.commands, moment);
1343        stopped.extend(self.unqueue(|_| true));
1344        self.show_stopped(stopped);
1345    }
1346
1347    pub(crate) fn reads(&mut self) -> Vec<(usize, u64, Job<E>)> {
1348        let mut reads = Vec::new();
1349        for (index, slot) in self.screens.iter_mut().enumerate() {
1350            if !slot.stale || slot.reading.is_some() || !(slot.started || self.eager) {
1351                continue;
1352            }
1353            slot.stale = false;
1354            if let Some(job) = slot.screen.load() {
1355                slot.generation += 1;
1356                slot.reading = Some(slot.generation);
1357                reads.push((index, slot.generation, job));
1358            }
1359        }
1360        reads
1361    }
1362
1363    pub fn pump(&mut self) {
1364        for (screen, generation, job) in self.reads() {
1365            let sender = self.out.sender.clone();
1366            thread::spawn(move || {
1367                let _ = sender.send(Wake::Loaded(screen, generation, job()));
1368            });
1369        }
1370        for (screen, stamp, job) in self.out.jobs.drain(..) {
1371            let sender = self.out.sender.clone();
1372            thread::spawn(move || {
1373                let _ = sender.send(Wake::Stamped(stamp, screen, job()));
1374            });
1375        }
1376        self.commands.retain(|handle| !handle.over());
1377        let board = self.board.is_some();
1378        for mut run in self.out.runs.drain(..) {
1379            run.keep &= board;
1380            let sender = self.out.sender.clone();
1381            let handle = command::spawn(run, sender, Arc::clone(&self.running));
1382            self.commands.extend(handle);
1383        }
1384    }
1385
1386    pub fn settle(&mut self) {
1387        for _ in 0..SETTLE {
1388            let mut woke = false;
1389            for (screen, generation, job) in self.reads() {
1390                self.loaded(screen, generation, job());
1391                woke = true;
1392            }
1393            for (screen, stamp, job) in std::mem::take(&mut self.out.jobs) {
1394                let event = job();
1395                if stamp == self.out.stamp() {
1396                    self.event(screen, event);
1397                }
1398                woke = true;
1399            }
1400            while let Ok(wake) = self.inbox.try_recv() {
1401                self.handle(wake);
1402                woke = true;
1403            }
1404            if self.ticks().is_some() {
1405                woke = true;
1406            }
1407            if !woke {
1408                return;
1409            }
1410        }
1411    }
1412
1413    pub fn advance(&mut self, by: Duration) -> Flow {
1414        self.moment += by;
1415        self.quit.tick(self.moment);
1416        self.ticks().unwrap_or(Flow::Stay)
1417    }
1418
1419    pub(crate) fn tick(&mut self) -> Instant {
1420        self.moment = Instant::now();
1421        self.quit.tick(self.moment);
1422        self.moment
1423    }
1424
1425    fn due(&self, slot: &Slot<E>) -> Option<Instant> {
1426        if !(slot.started || self.eager) {
1427            return None;
1428        }
1429        slot.screen
1430            .deadline()
1431            .filter(|deadline| slot.ticked != Some(*deadline))
1432    }
1433
1434    pub(crate) fn ticks(&mut self) -> Option<Flow> {
1435        let moment = self.moment;
1436        let before = self.toasts.len();
1437        self.toasts.retain(|(until, _)| *until > moment);
1438        let mut flow = (self.toasts.len() != before).then_some(Flow::Stay);
1439        for index in 0..self.screens.len() {
1440            let Some(deadline) = self.due(&self.screens[index]) else {
1441                continue;
1442            };
1443            if deadline > self.moment {
1444                continue;
1445            }
1446            self.screens[index].ticked = Some(deadline);
1447            self.call(index, |screen, cx| screen.tick(cx));
1448            let quit = self.apply() == Flow::Quit || flow == Some(Flow::Quit);
1449            flow = Some(if quit { Flow::Quit } else { Flow::Stay });
1450        }
1451        flow
1452    }
1453
1454    pub(crate) fn animating(&self) -> bool {
1455        if self.out.takeover.is_some() {
1456            return true;
1457        }
1458        self.current()
1459            .and_then(|index| self.screens.get(index))
1460            .is_some_and(|slot| slot.screen.animating() || slot.glass.is_some())
1461    }
1462
1463    fn glass_due(&self) -> Option<Instant> {
1464        let slot = self.screens.get(self.current()?)?;
1465        let loading = matches!(slot.screen.phase(), Phase::Loading(_));
1466        match (loading, slot.since, slot.glass) {
1467            (true, Some(since), None) => Some(since + self.delay),
1468            (false, _, Some(shown)) => Some(shown + self.least),
1469            _ => None,
1470        }
1471    }
1472
1473    pub(crate) fn deadlines(&self) -> [Option<Instant>; 5] {
1474        let screens = self.screens.iter().filter_map(|slot| self.due(slot)).min();
1475        let open = self.open.is_some() && self.open == self.board;
1476        let board = self.board().and_then(|board| board.wake(self.moment, open));
1477        let bands = [&self.band, &self.top_band]
1478            .into_iter()
1479            .flatten()
1480            .filter_map(|band| band.deadline(self.moment));
1481        let bands = bands.chain(board).min();
1482        let toasts = self.toasts.iter().map(|(until, _)| *until).min();
1483        [
1484            self.quit.deadline(),
1485            screens,
1486            bands,
1487            toasts,
1488            self.glass_due(),
1489        ]
1490    }
1491
1492    pub fn run(mut self) -> io::Result<()> {
1493        if std::env::var_os("NO_COLOR").is_some_and(|value| !value.is_empty()) {
1494            self.mono = true;
1495            self.repaint();
1496        }
1497        let mut term = Term::enter(self.modes)?;
1498        listen(self.out.sender.clone());
1499        self.run_in(&mut term)
1500    }
1501
1502    pub fn run_in(&mut self, term: &mut Term) -> io::Result<()> {
1503        let sender = self.out.sender.clone();
1504        let live = signal::live(move |signal| {
1505            let _ = sender.send(Wake::Signal(signal));
1506        });
1507        let ran = self.serve(term);
1508        self.end();
1509        if let Some(signal) = live.end() {
1510            restore();
1511            signal::die(signal);
1512        }
1513        ran
1514    }
1515
1516    fn serve(&mut self, term: &mut Term) -> io::Result<()> {
1517        self.tick();
1518        self.start();
1519        self.pump();
1520        let mut pace = Pace::default();
1521        let mut dirty = true;
1522        loop {
1523            let moment = self.tick();
1524            if dirty && pace.ready(moment) {
1525                let began = Instant::now();
1526                term.draw(|frame| self.draw(frame))?;
1527                pace.drew(moment);
1528                dirty = false;
1529                if let Some(after) = self.after.as_mut() {
1530                    after(began.elapsed());
1531                }
1532            }
1533            let moment = self.tick();
1534            dirty = dirty || self.animating();
1535            let until = wait_until(moment, dirty, &pace, &self.deadlines());
1536            let first = match until {
1537                Some(at) => match self
1538                    .inbox
1539                    .recv_timeout(at.saturating_duration_since(moment))
1540                {
1541                    Ok(wake) => Some(wake),
1542                    Err(RecvTimeoutError::Timeout) => None,
1543                    Err(RecvTimeoutError::Disconnected) => return Ok(()),
1544                },
1545                None => match self.inbox.recv() {
1546                    Ok(wake) => Some(wake),
1547                    Err(_) => return Ok(()),
1548                },
1549            };
1550            dirty = true;
1551            let mut next = first;
1552            while let Some(wake) = next {
1553                self.tick();
1554                match wake {
1555                    Wake::Lost(error) => return Err(error),
1556                    wake => {
1557                        if self.handle(wake) == Flow::Quit {
1558                            return Ok(());
1559                        }
1560                    }
1561                }
1562                next = self.inbox.try_recv().ok();
1563            }
1564            self.tick();
1565            if self.ticks() == Some(Flow::Quit) {
1566                return Ok(());
1567            }
1568            self.pump();
1569            for text in std::mem::take(&mut self.out.copies) {
1570                copy::copy(&text);
1571            }
1572        }
1573    }
1574}
1575
1576fn lone(section: Section) -> Nav {
1577    Nav::new(vec![Group::new("").section(section)]).keys(NavKeys::NONE)
1578}