1use anyhow::{bail, Context, Result};
2use arcstr::{literal, ArcStr};
3use derive_builder::Builder;
4use enumflags2::BitFlags;
5use graphix_compiler::{
6    expr::{ExprId, ModPath, ModuleResolver},
7    typ::{format_with_flags, PrintFlag, TVal, Type},
8    ExecCtx, NoUserEvent,
9};
10use graphix_rt::{CompExp, CouldNotResolve, GXConfig, GXCtx, GXHandle, RtEvent};
11use graphix_stdlib::Module;
12use input::InputReader;
13use netidx::{
14    path::Path,
15    pool::Pooled,
16    publisher::{Publisher, Value},
17    subscriber::Subscriber,
18};
19use reedline::Signal;
20use std::{collections::HashMap, path::PathBuf, sync::LazyLock, time::Duration};
21use tokio::{select, sync::mpsc};
22use triomphe::Arc;
23use tui::Tui;
24
25mod completion;
26mod input;
27mod tui;
28
29type Env = graphix_compiler::env::Env<GXCtx, NoUserEvent>;
30
31const TUITYP: LazyLock<Type> = LazyLock::new(|| Type::Ref {
32    scope: ModPath::root(),
33    name: ModPath::from(["tui", "Tui"]),
34    params: Arc::from_iter([]),
35});
36
37enum Output {
38    None,
39    Tui(Tui),
40    Text(CompExp),
41}
42
43impl Output {
44    fn from_expr(gx: &GXHandle, env: &Env, e: CompExp) -> Self {
45        if TUITYP.contains(env, &e.typ).unwrap() {
46            Self::Tui(Tui::start(gx, env.clone(), e))
47        } else {
48            Self::Text(e)
49        }
50    }
51
52    async fn clear(&mut self) {
53        match self {
54            Self::None | Self::Text(_) => (),
55            Self::Tui(tui) => tui.stop().await,
56        }
57        *self = Self::None
58    }
59
60    async fn process_update(&mut self, env: &Env, id: ExprId, v: Value) {
61        match self {
62            Self::None => (),
63            Self::Tui(tui) => tui.update(id, v).await,
64            Self::Text(e) => {
65                if e.id == id {
66                    println!("{}", TVal { env: &env, typ: &e.typ, v: &v })
67                }
68            }
69        }
70    }
71}
72
73fn tui_mods() -> ModuleResolver {
74    ModuleResolver::VFS(HashMap::from_iter([
75        (Path::from("/tui"), literal!(include_str!("tui/mod.gx"))),
76        (
77            Path::from("/tui/input_handler"),
78            literal!(include_str!("tui/input_handler.gx")),
79        ),
80        (Path::from("/tui/text"), literal!(include_str!("tui/text.gx"))),
81        (Path::from("/tui/paragraph"), literal!(include_str!("tui/paragraph.gx"))),
82        (Path::from("/tui/block"), literal!(include_str!("tui/block.gx"))),
83        (Path::from("/tui/scrollbar"), literal!(include_str!("tui/scrollbar.gx"))),
84        (Path::from("/tui/layout"), literal!(include_str!("tui/layout.gx"))),
85        (Path::from("/tui/tabs"), literal!(include_str!("tui/tabs.gx"))),
86        (Path::from("/tui/barchart"), literal!(include_str!("tui/barchart.gx"))),
87        (Path::from("/tui/chart"), literal!(include_str!("tui/chart.gx"))),
88        (Path::from("/tui/sparkline"), literal!(include_str!("tui/sparkline.gx"))),
89        (Path::from("/tui/line_gauge"), literal!(include_str!("tui/line_gauge.gx"))),
90        (Path::from("/tui/gauge"), literal!(include_str!("tui/gauge.gx"))),
91        (Path::from("/tui/list"), literal!(include_str!("tui/list.gx"))),
92        (Path::from("/tui/table"), literal!(include_str!("tui/table.gx"))),
93        (Path::from("/tui/calendar"), literal!(include_str!("tui/calendar.gx"))),
94        (Path::from("/tui/canvas"), literal!(include_str!("tui/canvas.gx"))),
95        (Path::from("/tui/browser"), literal!(include_str!("tui/browser.gx"))),
96    ]))
97}
98
99#[derive(Debug, Clone)]
100pub enum Mode {
101    Repl,
106    File(PathBuf),
110    Static(ArcStr),
113}
114
115impl Mode {
116    fn file_mode(&self) -> bool {
117        match self {
118            Self::Repl => false,
119            Self::File(_) | Self::Static(_) => true,
120        }
121    }
122}
123
124#[derive(Builder)]
125pub struct Shell {
126    #[builder(default = "false")]
128    no_init: bool,
129    #[builder(setter(strip_option), default)]
131    publish_timeout: Option<Duration>,
132    #[builder(setter(strip_option), default)]
135    resolve_timeout: Option<Duration>,
136    #[builder(default)]
138    module_resolvers: Vec<ModuleResolver>,
139    #[builder(default = "BitFlags::all()")]
141    stdlib_modules: BitFlags<Module>,
142    #[builder(default = "Mode::Repl")]
144    mode: Mode,
145    publisher: Publisher,
149    subscriber: Subscriber,
153}
154
155impl Shell {
156    async fn init(
157        &mut self,
158        sub: mpsc::Sender<Pooled<Vec<RtEvent>>>,
159    ) -> Result<GXHandle> {
160        let publisher = self.publisher.clone();
161        let subscriber = self.subscriber.clone();
162        let mut ctx = ExecCtx::new(GXCtx::new(publisher, subscriber));
163        let (root, mods) = graphix_stdlib::register(&mut ctx, self.stdlib_modules)?;
164        let root = ArcStr::from(format!("{root};\nmod tui"));
165        let mut mods = vec![mods, tui_mods()];
166        for res in self.module_resolvers.drain(..) {
167            mods.push(res);
168        }
169        let mut gx = GXConfig::builder(ctx, sub);
170        if let Some(s) = self.publish_timeout {
171            gx = gx.publish_timeout(s);
172        }
173        if let Some(s) = self.resolve_timeout {
174            gx = gx.resolve_timeout(s);
175        }
176        Ok(gx
177            .root(root)
178            .resolvers(mods)
179            .build()
180            .context("building rt config")?
181            .start()
182            .await
183            .context("loading initial modules")?)
184    }
185
186    async fn load_env(
187        &mut self,
188        gx: &GXHandle,
189        newenv: &mut Option<Env>,
190        output: &mut Output,
191        exprs: &mut Vec<CompExp>,
192    ) -> Result<Env> {
193        let env;
194        macro_rules! file_mode {
195            ($r:expr) => {{
196                exprs.extend($r.exprs);
197                env = gx.get_env().await?;
198                if let Some(e) = exprs.pop() {
199                    *output = Output::from_expr(&gx, &env, e);
200                }
201                *newenv = None
202            }};
203        }
204        match &self.mode {
205            Mode::File(file) => {
206                let r = gx.load(file.clone()).await?;
207                file_mode!(r)
208            }
209            Mode::Static(s) => {
210                let r = gx.compile(s.clone()).await?;
211                file_mode!(r)
212            }
213            Mode::Repl if !self.no_init => match gx.compile("mod init".into()).await {
214                Ok(res) => {
215                    env = res.env;
216                    exprs.extend(res.exprs);
217                    *newenv = Some(env.clone())
218                }
219                Err(e) if e.is::<CouldNotResolve>() => {
220                    env = gx.get_env().await?;
221                    *newenv = Some(env.clone())
222                }
223                Err(e) => {
224                    eprintln!("error in init module: {e:?}");
225                    env = gx.get_env().await?;
226                    *newenv = Some(env.clone())
227                }
228            },
229            Mode::Repl => {
230                env = gx.get_env().await?;
231                *newenv = Some(env.clone());
232            }
233        }
234        Ok(env)
235    }
236
237    pub async fn run(mut self) -> Result<()> {
238        let (tx, mut from_gx) = mpsc::channel(100);
239        let gx = self.init(tx).await?;
240        let script = self.mode.file_mode();
241        let mut input = InputReader::new();
242        let mut output = Output::None;
243        let mut newenv = None;
244        let mut exprs = vec![];
245        let mut env = self.load_env(&gx, &mut newenv, &mut output, &mut exprs).await?;
246        if !script {
247            println!("Welcome to the graphix shell");
248            println!("Press ctrl-c to cancel, ctrl-d to exit, and tab for help")
249        }
250        loop {
251            select! {
252                batch = from_gx.recv() => match batch {
253                    None => bail!("graphix runtime is dead"),
254                    Some(mut batch) => {
255                        for e in batch.drain(..) {
256                            match e {
257                                RtEvent::Updated(id, v) => output.process_update(&env, id, v).await,
258                                RtEvent::Env(e) => {
259                                    env = e;
260                                    newenv = Some(env.clone());
261                                }
262                            }
263                        }
264                    }
265                },
266                input = input.read_line(&mut output, &mut newenv) => {
267                    match input {
268                        Err(e) => eprintln!("error reading line {e:?}"),
269                        Ok(Signal::CtrlC) if script => break Ok(()),
270                        Ok(Signal::CtrlC) => output.clear().await,
271                        Ok(Signal::CtrlD) => break Ok(()),
272                        Ok(Signal::Success(line)) => {
273                            match gx.compile(ArcStr::from(line)).await {
274                                Err(e) => eprintln!("error: {e:?}"),
275                                Ok(res) => {
276                                    env = res.env;
277                                    newenv = Some(env.clone());
278                                    exprs.extend(res.exprs);
279                                    if exprs.last().map(|e| e.output).unwrap_or(false) {
280                                        let e = exprs.pop().unwrap();
281                                        let typ = e.typ
282                                            .with_deref(|t| t.cloned())
283                                            .unwrap_or_else(|| e.typ.clone());
284                                        format_with_flags(
285                                            PrintFlag::DerefTVars | PrintFlag::ReplacePrims,
286                                            || println!("-: {}", typ)
287                                        );
288                                        output = Output::from_expr(&gx, &env, e);
289                                    } else {
290                                        output.clear().await
291                                    }
292                                }
293                            }
294                        }
295                    }
296                },
297            }
298        }
299    }
300}