texlang_stdlib/
repl.rs

1//! Support for running TeX REPLs
2
3use super::script;
4use std::sync::Arc;
5use texlang::parse::Command;
6use texlang::traits::*;
7use texlang::*;
8
9pub struct RunOptions<'a> {
10    pub prompt: &'a str,
11    pub help: &'a str,
12}
13
14#[derive(Default)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct Component {
17    help: String,
18    quit_requested: bool,
19}
20
21#[cfg(feature = "repl")]
22pub mod run {
23    use super::*;
24    use linefeed::{Interface, ReadResult};
25
26    pub fn run<S: HasComponent<script::Component> + HasComponent<Component>>(
27        vm: &mut vm::VM<S>,
28        opts: RunOptions,
29    ) {
30        let mut c = HasComponent::<Component>::component_mut(&mut vm.state);
31        c.help = opts.help.into();
32        c.quit_requested = false;
33        let reader = Interface::new("").unwrap();
34
35        reader.set_prompt(opts.prompt).unwrap();
36
37        let mut names: Vec<String> = vm.get_commands_as_map_slow().into_keys().collect();
38        names.sort();
39        let mut num_names = names.len();
40        let a = Arc::new(ControlSequenceCompleter { names });
41        reader.set_completer(a);
42
43        while let ReadResult::Input(input) = reader.read_line().unwrap() {
44            reader.add_history(input.clone());
45
46            vm.clear_sources();
47            vm.push_source("".to_string(), input).unwrap();
48            let tokens = match script::run(vm) {
49                Ok(s) => s,
50                Err(err) => {
51                    if HasComponent::<Component>::component(&vm.state).quit_requested {
52                        return;
53                    }
54                    println!("{err}");
55                    continue;
56                }
57            };
58            let pretty = token::write_tokens(&tokens, vm.cs_name_interner());
59            if !pretty.trim().is_empty() {
60                println!("{pretty}\n");
61            }
62
63            if vm.commands_map.len() != num_names {
64                let mut names: Vec<String> = vm.get_commands_as_map_slow().into_keys().collect();
65                names.sort();
66                num_names = names.len();
67                let a = Arc::new(ControlSequenceCompleter { names });
68                reader.set_completer(a);
69            }
70        }
71    }
72
73    struct ControlSequenceCompleter {
74        names: Vec<String>,
75    }
76
77    impl<Term: linefeed::Terminal> linefeed::Completer<Term> for ControlSequenceCompleter {
78        fn complete(
79            &self,
80            word: &str,
81            prompter: &linefeed::Prompter<Term>,
82            start: usize,
83            _end: usize,
84        ) -> Option<Vec<linefeed::Completion>> {
85            if prompter.buffer()[..start].chars().rev().next() != Some('\\') {
86                return None;
87            }
88            let mut completions = Vec::new();
89            for name in &self.names {
90                if name.starts_with(word) {
91                    completions.push(linefeed::Completion {
92                        completion: name.clone(),
93                        display: None,
94                        suffix: linefeed::Suffix::Default,
95                    });
96                }
97            }
98            Some(completions)
99        }
100    }
101}
102
103/// Get the `\exit` command.
104///
105/// This exits the REPL.
106pub fn get_exit<S: HasComponent<Component>>() -> command::BuiltIn<S> {
107    command::BuiltIn::new_execution(
108        |_: token::Token, input: &mut vm::ExecutionInput<S>| -> command::Result<()> {
109            HasComponent::<Component>::component_mut(input.state_mut()).quit_requested = true;
110            Err(error::SimpleEndOfInputError::new(
111                input.vm(),
112                "quitting Texcraft REPL. This error should never be seen!",
113            )
114            .into())
115        },
116    )
117}
118
119/// Get the `\help` command.
120///
121/// This prints help text for the REPL.
122pub fn get_help<S: HasComponent<Component>>() -> command::BuiltIn<S> {
123    command::BuiltIn::new_execution(
124        |token: token::Token, input: &mut vm::ExecutionInput<S>| -> command::Result<()> {
125            let help = HasComponent::<Component>::component(input.state())
126                .help
127                .clone();
128            match writeln![input.vm().terminal.borrow_mut(), "{help}"] {
129                Ok(_) => Ok(()),
130                Err(err) => Err(error::SimpleTokenError::new(
131                    input.vm(),
132                    token,
133                    format!["failed to write help text: {err}"],
134                )
135                .into()),
136            }
137        },
138    )
139}
140
141/// Get the `\doc` command.
142///
143/// This prints the documentation for a TeX command.
144pub fn get_doc<S: TexlangState>() -> command::BuiltIn<S> {
145    command::BuiltIn::new_execution(
146        |token: token::Token, input: &mut vm::ExecutionInput<S>| -> command::Result<()> {
147            let Command::ControlSequence(cs_name) = Command::parse(input)?;
148            let cs_name_s = input.vm().cs_name_interner().resolve(cs_name).unwrap();
149            let doc = match input.commands_map().get_command_slow(&cs_name) {
150                None => format!["Unknown command \\{cs_name_s}"],
151                Some(cmd) => match cmd.doc() {
152                    None => format!["No documentation available for the \\{cs_name_s} command"],
153                    Some(doc) => format!["\\{cs_name_s}  {doc}"],
154                },
155            };
156            match writeln![input.vm().terminal.borrow_mut(), "{doc}"] {
157                Ok(_) => Ok(()),
158                Err(err) => Err(error::SimpleTokenError::new(
159                    input.vm(),
160                    token,
161                    format!["failed to write doc text: {err}"],
162                )
163                .into()),
164            }
165        },
166    )
167}