Skip to main content

thedes_app/session/
dev.rs

1use thedes_dev::{CommandContext, ScriptTable};
2use thedes_tui::{
3    core::{
4        App,
5        color::BasicColor,
6        event::{Event, Key, KeyEvent},
7        screen::{self, FlushError},
8    },
9    text,
10};
11use thiserror::Error;
12
13pub type KeyBindingMap = thedes_tui::key_bindings::KeyBindingMap<Command>;
14
15pub fn default_key_bindings() -> KeyBindingMap {
16    KeyBindingMap::new()
17        .with(Key::Esc, Command::Exit)
18        .with(Key::Enter, Command::RunPrevious)
19}
20
21#[derive(Debug, Error)]
22pub enum Error {
23    #[error("Failed to render message")]
24    RenderMessage(
25        #[source]
26        #[from]
27        text::Error,
28    ),
29    #[error("TUI cancelled")]
30    Cancelled,
31    #[error("Failed to run the script")]
32    Script(
33        #[from]
34        #[source]
35        thedes_dev::Error,
36    ),
37    #[error("Failed to flush canvas")]
38    FlushCanvas(
39        #[from]
40        #[source]
41        FlushError,
42    ),
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub enum Command {
47    Run(char),
48    RunPrevious,
49    Exit,
50}
51
52#[derive(Debug, Clone)]
53pub struct Component {
54    prev: char,
55    key_bindings: KeyBindingMap,
56}
57
58impl Component {
59    pub const DEFAULT_KEY: char = '.';
60
61    pub fn new() -> Self {
62        Self { prev: Self::DEFAULT_KEY, key_bindings: default_key_bindings() }
63    }
64
65    pub fn with_keybindings(mut self, map: KeyBindingMap) -> Self {
66        self.set_keybindings(map);
67        self
68    }
69
70    pub fn set_keybindings(&mut self, map: KeyBindingMap) -> &mut Self {
71        self.key_bindings = map;
72        self
73    }
74
75    pub async fn run(
76        &mut self,
77        app: &mut App,
78        context: &mut CommandContext<'_, '_>,
79    ) -> Result<(), Error> {
80        loop {
81            match self.handle_input(app, context).await {
82                Ok(false) => break,
83                Ok(true) => (),
84                Err(Error::Script(e))
85                    if matches!(
86                        e.kind(),
87                        thedes_dev::ErrorKind::UnknownKey(_)
88                    ) =>
89                {
90                    ()
91                },
92                Err(e) => Err(e)?,
93            }
94            self.render(app)?;
95            tokio::select! {
96                _ = app.tick_session.tick() => (),
97                _ = app.cancel_token.cancelled() => Err(Error::Cancelled)?,
98            }
99        }
100        Ok(())
101    }
102
103    fn render(&mut self, app: &mut App) -> Result<(), Error> {
104        app.canvas
105            .queue([screen::Command::ClearScreen(BasicColor::Black.into())]);
106        text::styled(
107            app,
108            "Development Script Mode",
109            &text::Style::default().with_top_margin(1).with_align(1, 2),
110        )?;
111        text::styled(
112            app,
113            "Press any character key to run a corresponding script.",
114            &text::Style::default().with_top_margin(4).with_align(1, 2),
115        )?;
116        text::styled(
117            app,
118            "Press enter to run previous script or the default one.",
119            &text::Style::default().with_top_margin(5).with_align(1, 2),
120        )?;
121        text::styled(
122            app,
123            "Press ESC to cancel.",
124            &text::Style::default().with_top_margin(7).with_align(1, 2),
125        )?;
126        app.canvas.flush()?;
127        Ok(())
128    }
129
130    async fn handle_input(
131        &mut self,
132        app: &mut App,
133        context: &mut CommandContext<'_, '_>,
134    ) -> Result<bool, Error> {
135        let Ok(mut events) = app.events.read_until_now() else {
136            Err(Error::Cancelled)?
137        };
138
139        let mut should_continue = true;
140        let mut hit = false;
141        while let Some(event) = events.next().filter(|_| should_continue) {
142            let Event::Key(key) = event else { continue };
143
144            let command = self.key_bindings.command_for(key).cloned();
145
146            should_continue = match command {
147                Some(command) => {
148                    hit = true;
149                    self.run_command(command, context).await?
150                },
151                None => match key {
152                    KeyEvent {
153                        main_key: Key::Char(ch),
154                        ctrl: false,
155                        alt: false,
156                        shift: false,
157                    } => {
158                        hit = true;
159                        self.run_command(Command::Run(ch), context).await?
160                    },
161                    _ => true,
162                },
163            };
164        }
165        Ok(!hit)
166    }
167
168    async fn run_command(
169        &mut self,
170        command: Command,
171        context: &mut CommandContext<'_, '_>,
172    ) -> Result<bool, Error> {
173        match command {
174            Command::Run(ch) => ScriptTable::run_reading(ch, context).await?,
175            Command::RunPrevious => {
176                ScriptTable::run_reading(self.prev, context).await?
177            },
178            Command::Exit => return Ok(false),
179        }
180        Ok(true)
181    }
182}