1use super::script;
4use std::sync::Arc;
5use texlang::prelude as txl;
6use texlang::traits::*;
7use texlang::*;
8use texlang_common as common;
9
10pub struct RunOptions<'a> {
11 pub prompt: &'a str,
12 pub help: &'a str,
13}
14
15#[derive(Default)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct Component {
18 help: String,
19 quit_requested: bool,
20}
21
22#[cfg(feature = "repl")]
24pub fn start<S: HasComponent<script::Component> + HasComponent<Component>>(
25 vm: &mut vm::VM<S>,
26 opts: RunOptions,
27) {
28 use linefeed::{Interface, ReadResult};
29
30 let c = HasComponent::<Component>::component_mut(&mut vm.state);
31 c.help = opts.help.into();
32 c.quit_requested = false;
33
34 let reader = Interface::new("").unwrap();
35 reader.set_prompt(opts.prompt).unwrap();
36 script::set_io_writer(vm, std::io::stdout());
37
38 let mut num_commands: Option<usize> = None;
39 loop {
40 if Some(vm.commands_map.len()) != num_commands {
51 let mut names: Vec<String> = vm
52 .get_commands_as_map_slow()
53 .into_keys()
54 .map(|s| s.to_string())
55 .collect();
56 names.sort();
57 num_commands = Some(names.len());
58 let a = Arc::new(ControlSequenceCompleter { names });
59 reader.set_completer(a);
60 }
61
62 let ReadResult::Input(input) = reader.read_line().unwrap() else {
63 break;
64 };
65 reader.add_history(input.clone());
66
67 vm.clear_sources();
68 vm.push_source("".to_string(), input).unwrap();
69 match script::run(vm) {
70 Ok(()) => (),
71 Err(err) => {
72 println!("{err}");
73 continue;
74 }
75 };
76 if HasComponent::<Component>::component(&vm.state).quit_requested {
77 return;
78 }
79 println!();
81 }
82}
83
84struct ControlSequenceCompleter {
85 names: Vec<String>,
86}
87
88#[cfg(feature = "repl")]
89impl<Term: linefeed::Terminal> linefeed::Completer<Term> for ControlSequenceCompleter {
90 fn complete(
91 &self,
92 word: &str,
93 prompter: &linefeed::Prompter<Term>,
94 start: usize,
95 _end: usize,
96 ) -> Option<Vec<linefeed::Completion>> {
97 if !prompter.buffer()[..start].ends_with('\\') {
98 return None;
99 }
100 let mut completions = Vec::new();
101 for name in &self.names {
102 if name.starts_with(word) {
103 completions.push(linefeed::Completion {
104 completion: name.to_string(),
105 display: Some(format!["\\{name}"]),
106 suffix: linefeed::Suffix::Default,
107 });
108 }
109 }
110 Some(completions)
111 }
112}
113
114pub fn get_exit<S: HasComponent<Component>>() -> command::BuiltIn<S> {
118 command::BuiltIn::new_execution(
119 |_: token::Token, input: &mut vm::ExecutionInput<S>| -> txl::Result<()> {
120 HasComponent::<Component>::component_mut(input.state_mut()).quit_requested = true;
121 Err(input.shutdown())
122 },
123 )
124}
125
126pub fn get_help<S: HasComponent<Component> + common::HasLogging>() -> command::BuiltIn<S> {
130 command::BuiltIn::new_execution(
131 |token: token::Token, input: &mut vm::ExecutionInput<S>| -> txl::Result<()> {
132 let help = HasComponent::<Component>::component(input.state())
133 .help
134 .clone();
135 match writeln![input.state().terminal_out().borrow_mut(), "{help}"] {
136 Ok(_) => Ok(()),
137 Err(err) => Err(input.fatal_error(error::SimpleTokenError::new(
138 token,
139 format!["failed to write help text: {err}"],
140 ))),
141 }
142 },
143 )
144}
145
146pub fn get_doc<S: TexlangState + common::HasLogging>() -> command::BuiltIn<S> {
150 command::BuiltIn::new_execution(
151 |token: token::Token, input: &mut vm::ExecutionInput<S>| -> txl::Result<()> {
152 let Some(cmd_ref) = Option::<token::CommandRef>::parse(input)? else {
153 return Ok(());
154 };
155 let name = cmd_ref.to_string(input.vm().cs_name_interner());
156 let doc = match input.commands_map().get_command_slow(&cmd_ref) {
157 None => format!["Unknown command {name}"],
158 Some(cmd) => match cmd.doc() {
159 None => format!["No documentation available for the {name} command"],
160 Some(doc) => format!["{name} {doc}"],
161 },
162 };
163 match writeln![input.state().terminal_out().borrow_mut(), "{doc}"] {
164 Ok(_) => Ok(()),
165 Err(err) => Err(input.fatal_error(error::SimpleTokenError::new(
166 token,
167 format!["failed to write doc text: {err}"],
168 ))),
169 }
170 },
171 )
172}