1use 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
103pub 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
119pub 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
141pub 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}