duat_core/cmd/mod.rs
1//! Creation and execution of commands.
2//!
3//! Commands on Duat are bits of code that can be executed on the
4//! [`PromptLine`] widget. They can also be invoked from other parts
5//! of the code, but their use is mostly intended for user calls.
6//!
7//! # Running commands
8//!
9//! There are two environments where you'd run commands. When you have
10//! a [`Pass`] available, and when you don't.
11//!
12//! ## When you have a [`Pass`]
13//!
14//! If you have a `Pass` available, it means you are in the main
15//! thread of execution, and can safely execute commands. The
16//! advantage of using a `Pass` is that you can retrieve the value
17//! returned by the command:
18//!
19//! ```rust
20//! # duat_core::doc_duat!(duat);
21//! setup_duat!(setup);
22//! use duat::prelude::*;
23//! fn main_thread_function(pa: &mut Pass) {
24//! let result = cmd::call(pa, "colorscheme solarized");
25//! if result.is_ok() {
26//! context::info!("[a]Awesome!");
27//! }
28//! }
29//! ```
30//!
31//! The code above runs the `colorscheme` command. In this case, if
32//! the command succeds or fails, no notification will be shown, if
33//! you want notifications, you should use [`cmd::call_notify`], and
34//! the return value would still be acquired.
35//!
36//! ## When you don't have a [`Pass`]
37//!
38//! you may not have a `Pass` if, for example, you are not on the
39//! main thread of execution. In this case, there is [`cmd::queue`],
40//! which queues up a call to be executed later.
41//! This means that you can't retrieve the return value, since it will
42//! be executed asynchronously:
43//!
44//! ```rust
45//! # duat_core::doc_duat!(duat);
46//! setup_duat!(setup);
47//! use duat::prelude::*;
48//! fn on_a_thread_far_far_away() {
49//! cmd::queue("set-form --flag -abc punctuation.delimiter rgb 255 0 0 hsl 1");
50//! }
51//! ```
52//!
53//! The `set-form` command above will fail, since the hsl [`Color`]
54//! [`Parameter`] was not completely matched, missing the saturation
55//! and lightness arguments. It also shows two unused flag arguments,
56//! word flags (`"--flag"`) and blob flags (`"-abc"`), which will also
57//! cause the command to fail. Even failing, no notification will be
58//! sent, because I called [`queue`], if you want notifications, call
59//! [`queue_notify`].
60//!
61//! If you want to "react" to the result of a queued call, you can use
62//! the [`cmd::queue_and`] function, which lets you also send a
63//! function that takes [`CmdResult`] as parameter:
64//!
65//! ```rust
66//! # mod duat { pub mod prelude { pub use duat_core::cmd; } }
67//! # use std::sync::atomic::{AtomicUsize, Ordering};
68//! use duat::prelude::*;
69//!
70//! static FAILURE_COUNT: AtomicUsize = AtomicUsize::new(0);
71//!
72//! fn on_a_thread_far_far_away() {
73//! cmd::queue_and(
74//! "set-form --flag -abc punctuation.delimiter rgb 255 0 0 hsl 1",
75//! |res| {
76//! if res.is_err() {
77//! FAILURE_COUNT.fetch_add(1, Ordering::Relaxed);
78//! }
79//! },
80//! );
81//! }
82//! ```
83//!
84//! Note that, because this function might be sent to another thread,
85//! it must be [`Send + 'static`].
86//!
87//! # Adding commands
88//!
89//! Commands are added through the [`add`] function. This function
90//! takes a caller in the form of `&str` and a function to call.
91//!
92//! This function should have the following arguments:
93//!
94//! - One `&mut Pass`: This _must_ be annotated.
95//! - Up to 12 [`Parameter`] arguments. These will provide automatic
96//! checking on the `PromptLine`.
97//!
98//! If the second argument is a closure, all of the arguments _MUST_
99//! have type annotations *INCLUDING the `&mut Pass`*, otherwise type
100//! inference won't work. If this function fails to compile, it is
101//! most likely because you passed a closure with missing argument
102//! type annotations.
103//!
104//! Most Rust [`std`] types (that would make sense) are implemented as
105//! [`Parameter`]s, so you can place [`String`]s, [`f32`]s, [`bool`]s,
106//! and all sorts of other types as `Parameter`s for your command.
107//!
108//! Types like [`Vec`] and [`Option`] are also implemented as
109//! [`Parameter`]s, so you can have a list of `Parameter`s or an
110//! optional `Parameter`.
111//!
112//! ```rust
113//! # fn test() {
114//! # duat_core::doc_duat!(duat);
115//! use duat::prelude::*;
116//!
117//! let result = cmd::add("unset-form", |pa: &mut Pass, forms: Vec<cmd::FormName>| {
118//! for form in forms.iter() {
119//! form::set("form", Form::new());
120//! }
121//! // You can return a success message, but must
122//! // return an error message if there is a problem.
123//! // For those, you should use the `txt!` macro.
124//! Ok(Some(txt!("Unset [a]{}[] forms", forms.len())))
125//! });
126//! // You can also alias commands:
127//! cmd::alias("uf", "unset-form");
128//! # }
129//! ```
130//!
131//! In the command above, you'll notice that [`Ok`] values return
132//! [`Option<Text>`]. This is because you may not care about
133//! announcing that the command succedeed. For the [`Err`] variant,
134//! however, the return value is just [`Text`], because you should say
135//! what went wrong in the command. Most of the time, this happens
136//! because of something out of your control, like a buffer not
137//! existing. In these cases, the `?` is enough to return an
138//! appropriate `Text`.
139//!
140//! There are other builtin types of [`Parameter`]s in `cmd` that
141//! can be used on a variety of things. For example, the [`Remainder`]
142//! `Parameter` is one that just takes the remaining arguments and
143//! collects them into a single [`String`].
144//!
145//! ```rust
146//! # duat_core::doc_duat!(duat);
147//! setup_duat!(setup);
148//! use duat::prelude::*;
149//!
150//! cmd::add("pip", |_: &mut Pass, args: cmd::Remainder| {
151//! let child = std::process::Command::new("pip").spawn()?;
152//! let res = child.wait_with_output()?;
153//!
154//! Ok(Some(txt!("{res:?}")))
155//! });
156//! ```
157//!
158//! [`PromptLine`]: https://docs.rs/duat/latest/duat/widgets/struct.PromptLine.html
159//! [`cmd::call_notify`]: call_notify
160//! [`cmd::queue`]: queue
161//! [`cmd::queue_and`]: queue_and
162//! [`Send + 'static`]: Send
163//! [`Color`]: crate::form::Color
164//! [`txt!`]: crate::text::txt
165//! [`Ok(Some({Text}))`]: Ok
166//! [`Form`]: crate::form::Form
167//! [`Handle`]: crate::context::Handle
168use std::{
169 collections::HashMap,
170 fmt::Display,
171 ops::Range,
172 sync::{Arc, Mutex},
173};
174
175pub use self::{global::*, parameters::*};
176use crate::{
177 data::Pass,
178 form::FormId,
179 text::{Text, txt},
180 utils::catch_panic,
181};
182
183mod default;
184mod parameters;
185
186mod global {
187 use std::{
188 any::TypeId,
189 ops::Range,
190 sync::{Arc, Mutex},
191 };
192
193 use super::{CmdResult, Commands};
194 use crate::data::BulkDataWriter;
195 #[doc(inline)]
196 use crate::{cmd::CmdFn, context, data::Pass, form::FormId, session::DuatEvent, text::Text};
197
198 static COMMANDS: BulkDataWriter<Commands> = BulkDataWriter::new();
199
200 /// A builder for a command
201 ///
202 /// This struct is created by [`cmd::add`], and when it is
203 /// dropped, the command gets added.
204 ///
205 /// [`cmd::add`]: add
206 pub struct CmdBuilder {
207 command: Option<super::Command>,
208 param_n: usize,
209 }
210
211 impl CmdBuilder {
212 /// Adds documentation to this command
213 pub fn doc(mut self, short: Text, long: Option<Text>) -> Self {
214 let command = self.command.as_mut().unwrap();
215 command.doc.short = Some(Arc::new(short));
216 command.doc.long = long.map(Arc::new);
217 self
218 }
219
220 /// Adds documentation for the next [`Parameter`] of the
221 /// command
222 ///
223 /// You have to give a short description, and you may give a
224 /// long description and a name.
225 ///
226 /// The short and long descriptions should not present the
227 /// same information, the long description merely adds
228 /// additional context.
229 ///
230 /// The `name` argument will rename the argument to something
231 /// else. If it is excluded, then the default name (provided
232 /// by [`Parameter::arg_name`] will be used.
233 ///
234 /// [`Parameter`]: super::Parameter
235 /// [`Parameter::arg_name`]: super::Parameter::arg_name
236 #[track_caller]
237 pub fn doc_param(mut self, short: Text, long: Option<Text>, name: Option<Text>) -> Self {
238 assert!(
239 !self.command.as_ref().unwrap().doc.params.is_empty(),
240 "Command has no parameters to be documented"
241 );
242
243 assert!(
244 self.param_n < self.command.as_ref().unwrap().doc.params.len(),
245 "Too many docs for the number of Parameters",
246 );
247
248 let param = {
249 let params = &mut self.command.as_mut().unwrap().doc.params;
250 &mut Arc::get_mut(params).unwrap()[self.param_n]
251 };
252
253 param.short = Some(short);
254 param.long = long;
255 if let Some(name) = name {
256 param.arg_name = name;
257 }
258
259 self.param_n += 1;
260
261 self
262 }
263 }
264
265 impl Drop for CmdBuilder {
266 fn drop(&mut self) {
267 let command = self.command.take().unwrap();
268 COMMANDS.mutate(move |cmds| cmds.add(command));
269 }
270 }
271
272 /// The description for a [`Parameter`], which is used by Duat's
273 /// commands
274 ///
275 /// [`Parameter`]: super::Parameter
276 #[derive(Debug, Clone)]
277 pub struct ParamDoc {
278 /// The name for the parameter
279 ///
280 /// This will be used when formatting documentation for the
281 /// command, like `<path>` in `buffer <path>`.
282 pub arg_name: Text,
283 /// "Short" documentation for the `Parameter`
284 ///
285 /// This should be a short line of few words, meant for
286 /// possibly being displayed in a list.
287 ///
288 /// Note that this is _not_ a short version of
289 /// [`ParamDoc::long`]. So they shouldn't present the same
290 /// information.
291 pub short: Option<Text>,
292 /// "Long" documentation for a command
293 ///
294 /// This should add more details to the [`Text`] of
295 /// [`ParamDoc::short`]. This description is meant to be shown
296 /// when more information is required _on top_ of
297 /// [`ParamDoc::short`], so they shouldn't present duplicate
298 /// information.
299 pub long: Option<Text>,
300 }
301
302 /// Adds a command to Duat
303 ///
304 /// Anyone will be able to execute this command, and it can take
305 /// any number of [`Parameter`] arguments. These arguments will be
306 /// automatically matched and checked before the command starts
307 /// execution, providing feedback to the user as he is typing the
308 /// commands.
309 ///
310 /// # Examples
311 ///
312 /// In the config crate:
313 ///
314 /// ```rust
315 /// # duat_core::doc_duat!(duat);
316 /// setup_duat!(setup);
317 /// use duat::prelude::*;
318 ///
319 /// fn setup() {
320 /// let var = data::RwData::new(35);
321 ///
322 /// let var_clone = var.clone();
323 /// cmd::add("set-var", move |pa: &mut Pass, value: usize| {
324 /// *var_clone.write(pa) = value;
325 /// Ok(None)
326 /// });
327 ///
328 /// hook::add::<WindowOpened>(move |mut pa, window| {
329 /// status!("The value is currently {var}")
330 /// .above()
331 /// .push_on(pa, window);
332 /// });
333 /// }
334 /// ```
335 ///
336 /// Since `var` is an [`RwData`], it will be updated
337 /// automatically in the [`StatusLine`]
338 ///
339 /// [`StatusLine`]: https://docs.rs/duat/latest/duat/widgets/struct.StatusLine.html
340 /// [`RwData`]: crate::data::RwData
341 /// [`Parameter`]: super::Parameter
342 pub fn add<Cmd: CmdFn<impl std::any::Any>>(caller: &str, mut cmd: Cmd) -> CmdBuilder {
343 CmdBuilder {
344 command: Some(super::Command::new(
345 caller.to_string(),
346 Arc::new(Mutex::new(move |pa: &mut Pass, args: super::Args| {
347 cmd.call(pa, args)
348 })),
349 Cmd::check_args,
350 Cmd::param_arg_names(),
351 )),
352 param_n: 0,
353 }
354 }
355
356 /// Canonical way to quit Duat.
357 ///
358 /// By calling the quit command, all threads will finish their
359 /// tasks, and then Duat will execute a program closing
360 /// function, as defined by the [Ui].
361 ///
362 /// [Ui]: crate::ui::traits::RawUi
363 pub fn quit() {
364 queue("quit");
365 }
366
367 /// Switches to/opens a [`Buffer`] with the given name.
368 ///
369 /// If you wish to specifically switch to buffers that are already
370 /// open, use [`buffer`].
371 ///
372 /// If there are more arguments, they will be ignored.
373 ///
374 /// [`Buffer`]: crate::buffer::Buffer
375 pub fn edit(pa: &mut Pass, buffer: impl std::fmt::Display) -> CmdResult {
376 call(pa, format!("edit {buffer}"))
377 }
378
379 /// Switches to a [`Buffer`] with the given name.
380 ///
381 /// If there is no buffer open with that name, does nothing. Use
382 /// [`edit`] if you wish to open buffers.
383 ///
384 /// If there are more arguments, they will be ignored.
385 ///
386 /// [`Buffer`]: crate::buffer::Buffer
387 pub fn buffer(pa: &mut Pass, buffer: impl std::fmt::Display) -> CmdResult {
388 call(pa, format!("buffer {buffer}"))
389 }
390
391 /// Switches to the next [`Buffer`].
392 ///
393 /// This function will only look at buffers that are opened in the
394 /// current window. If you want to include other windows in the
395 /// search, use [`next_global_buffer`].
396 ///
397 /// [`Buffer`]: crate::buffer::Buffer
398 pub fn next_buffer(pa: &mut Pass) -> CmdResult {
399 call(pa, "next-buffer")
400 }
401
402 /// Switches to the previous [`Buffer`].
403 ///
404 /// This function will only look at buffers that are opened in the
405 /// current window. If you want to include other windows in the
406 /// search, use [`prev_global_buffer`].
407 ///
408 /// [`Buffer`]: crate::buffer::Buffer
409 pub fn prev_buffer(pa: &mut Pass) -> CmdResult {
410 call(pa, "prev-buffer")
411 }
412
413 /// Switches to the next [`Buffer`].
414 ///
415 /// This function will look for buffers in all windows. If you
416 /// want to limit the search to just the current window, use
417 /// [`next_buffer`].
418 ///
419 /// [`Buffer`]: crate::buffer::Buffer
420 pub fn next_global_buffer(pa: &mut Pass) -> CmdResult {
421 call(pa, "next-buffer --global")
422 }
423
424 /// Switches to the previous [`Buffer`].
425 ///
426 /// This function will look for buffers in all windows. If you
427 /// want to limit the search to just the current window, use
428 /// [`prev_buffer`].
429 ///
430 /// [`Buffer`]: crate::buffer::Buffer
431 pub fn prev_global_buffer(pa: &mut Pass) -> CmdResult {
432 call(pa, "prev-buffer --global")
433 }
434
435 /// Tries to alias a `caller` to an existing `command`.
436 pub fn alias(alias: impl ToString, command: impl ToString) {
437 let alias = alias.to_string();
438 let command = command.to_string();
439 COMMANDS.mutate(move |cmds| context::logs().push_cmd_result(cmds.alias(alias, command)))
440 }
441
442 /// Runs a full command synchronously, with a [`Pass`].
443 ///
444 /// If you call commands through this function, you will be able
445 /// to retrieve their return values, but because of the [`Pass`],
446 /// this function can only be used on the main thread of
447 /// execution. If you want to call commands from other threads,
448 /// see [`cmd::queue`].
449 ///
450 /// # Examples
451 ///
452 /// ```rust
453 /// # duat_core::doc_duat!(duat);
454 /// setup_duat!(setup);
455 /// use duat::prelude::*;
456 ///
457 /// fn main_thread_function(pa: &mut Pass) {
458 /// cmd::call(pa, "set-prompt new-prompt");
459 /// }
460 /// ```
461 ///
462 /// In this case we're running a command that will affect the most
463 /// relevant [`PromptLine`], and no notifications are sent. That's
464 /// because I used [`call`]. If you want notifications, see
465 /// [`cmd::call_notify`].
466 ///
467 /// [`cmd::queue`]: queue
468 /// [`cmd::call_notify`]: call_notify
469 /// [`PromptLine`]: https://docs.rs/duat/latest/duat/widgets/struct.PromptLine.html
470 pub fn call(pa: &mut Pass, call: impl std::fmt::Display) -> CmdResult {
471 COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa))
472 }
473
474 /// Like [`call`], but notifies the result
475 #[allow(unused_must_use)]
476 pub fn call_notify(pa: &mut Pass, call: impl std::fmt::Display) -> CmdResult {
477 let result = COMMANDS
478 .write(pa)
479 .get_cmd(call.to_string())
480 .and_then(|cmd| cmd(pa));
481 context::logs().push_cmd_result(result.clone());
482
483 result
484 }
485
486 /// Queues a command call
487 ///
488 /// You should only use this if you're not in the main thread of
489 /// execution, or if you don't have a [`Pass`] for some other
490 /// reason, like you are in the middle of accessing an [`RwData`]
491 /// or something like that.
492 ///
493 /// Since this function will run outside of the current scope, its
494 /// [`Result`] will not be returned.
495 ///
496 /// [`RwData`]: crate::data::RwData
497 pub fn queue(call: impl std::fmt::Display) {
498 let call = call.to_string();
499 crate::context::sender().send(DuatEvent::QueuedFunction(Box::new(move |pa| {
500 let _ = COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa));
501 })));
502 }
503
504 /// Like [`queue`], but notifies the result
505 pub fn queue_notify(call: impl std::fmt::Display) {
506 let call = call.to_string();
507 crate::context::sender().send(DuatEvent::QueuedFunction(Box::new(move |pa| {
508 context::logs()
509 .push_cmd_result(COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa)));
510 })))
511 }
512
513 /// Like [`queue`], but acts on the [`Result`]
514 pub fn queue_and(call: impl std::fmt::Display, map: impl FnOnce(CmdResult) + Send + 'static) {
515 let call = call.to_string();
516 crate::context::sender().send(DuatEvent::QueuedFunction(Box::new(move |pa| {
517 map(COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa)));
518 })))
519 }
520
521 /// Like [`queue_and`], but also notifies the [`Result`]
522 pub fn queue_notify_and(
523 call: impl std::fmt::Display,
524 map: impl FnOnce(CmdResult) + Send + 'static,
525 ) {
526 let call = call.to_string();
527 crate::context::sender().send(DuatEvent::QueuedFunction(Box::new(move |pa| {
528 let result = COMMANDS.write(pa).get_cmd(call).and_then(|cmd| cmd(pa));
529 context::logs().push_cmd_result(result.clone());
530
531 map(result)
532 })));
533 }
534
535 /// Check if the arguments for a given `caller` are correct
536 pub fn check_args(
537 pa: &mut Pass,
538 call: &str,
539 ) -> Option<(
540 Vec<(Range<usize>, Option<FormId>)>,
541 Option<(Range<usize>, Text)>,
542 )> {
543 COMMANDS.write(pa).check_args(call).map(|ca| ca(pa))
544 }
545
546 /// The [`TypeId`] of the last [`Parameter`]s that were being
547 /// parsed
548 ///
549 /// This is a list that may have multiple `Parameter`s because
550 /// some of those might only optionally take arguments, which
551 /// means that you'd be parsing both the optionally parseable
552 /// `Parameter` as well as the following ones.
553 ///
554 /// Returns [`None`] if the caller couldn't be found, or if the
555 /// call was an empty string.
556 ///
557 /// # Note
558 ///
559 /// You can pass in only a prefix of the full call in order to get
560 /// the parameter completion for the `n`th argument, as opposed to
561 /// just the last one. This is useful if you want to get the
562 /// `TypeId`s being parsed at a cursor's position, which may be in
563 /// the middle of the string as opposed to the end.
564 ///
565 /// [`Parameter`]: super::Parameter
566 pub fn last_parsed_parameters(pa: &mut Pass, call: &str) -> Option<Vec<TypeId>> {
567 Some(COMMANDS.write(pa).args_after_check(call)?(pa).last_parsed())
568 }
569
570 /// The description for a Duat command, which can be executed in
571 /// the `PromptLine`
572 #[derive(Debug, Clone)]
573 pub struct CmdDoc {
574 /// The caller for the command
575 pub caller: Arc<str>,
576 /// "Short" documentation for a command
577 ///
578 /// This should be a short line of few words, meant for
579 /// possibly being displayed in a list.
580 ///
581 /// Note that this is _not_ a short version of
582 /// [`CmdDoc::long`]. So they shouldn't present the same
583 /// information.
584 pub short: Option<Arc<Text>>,
585 /// "Long" documentation for a command
586 ///
587 /// This should add more details to the [`Text`] of
588 /// [`CmdDoc::short`]. This description is meant to be shown
589 /// when more information is required _on top_ of
590 /// [`CmdDoc::short`], so they shouldn't present the same
591 /// information.
592 ///
593 /// Note also that you _can_ add documentation about the
594 /// parameters, however, you should prioritize calling
595 /// [`CmdBuilder::doc_param`] for that purpose instead.
596 pub long: Option<Arc<Text>>,
597 /// Documentation about the command's parameters
598 pub params: Arc<[ParamDoc]>,
599 }
600
601 /// The description for a Duat alias, which can be executed in
602 /// the `PromptLine`, aliasing to a proper command
603 #[derive(Clone)]
604 pub struct AliasDescription {
605 /// The caller for the alias
606 pub caller: Arc<str>,
607 /// What the caller gets replaced by
608 pub replacement: Arc<str>,
609 /// The description of the original command
610 pub cmd: CmdDoc,
611 }
612
613 /// Description of a Duat command or alias
614 #[derive(Clone)]
615 pub enum Description {
616 /// The description of a command.
617 Command(CmdDoc),
618 /// The description of an alias for a command.
619 Alias(AliasDescription),
620 }
621
622 impl Description {
623 /// The caller for the command/alias
624 pub fn caller(&self) -> &str {
625 match self {
626 Description::Command(cmd_description) => &cmd_description.caller,
627 Description::Alias(alias_description) => &alias_description.caller,
628 }
629 }
630 }
631
632 /// A list of descriptions for all commands in Duat
633 ///
634 /// This list does not have any inherent sorting, with the
635 /// exception that aliases are listed after commands.
636 pub fn cmd_list(pa: &mut Pass) -> Vec<Description> {
637 let commands = COMMANDS.write(pa);
638 commands
639 .list
640 .iter()
641 .map(|cmd| Description::Command(cmd.doc.clone()))
642 .chain(commands.aliases.iter().map(|(caller, (cmd, replacement))| {
643 Description::Alias(AliasDescription {
644 caller: caller.clone(),
645 replacement: replacement.clone(),
646 cmd: cmd.doc.clone(),
647 })
648 }))
649 .collect()
650 }
651}
652
653/// A list of commands.
654///
655/// This list contains all of the commands that have been
656/// added to Duat, as well as info on the current [`Buffer`],
657/// [widget] and all of the [windows].
658///
659/// [`Buffer`]: crate::buffer::Buffer
660/// [widget]: crate::ui::Widget
661/// [windows]: crate::ui::Window
662struct Commands {
663 list: Vec<Command>,
664 aliases: HashMap<Arc<str>, (Command, Arc<str>)>,
665}
666
667impl Commands {
668 /// Aliases a command to a specific word
669 fn alias(&mut self, alias: String, call: String) -> CmdResult {
670 if alias.split_whitespace().count() != 1 {
671 return Err(txt!("Alias [a]{alias}[] is not a single word"));
672 }
673
674 let caller = call
675 .split_whitespace()
676 .next()
677 .ok_or(txt!("The command is empty"))?
678 .to_string();
679
680 let mut cmds = self.list.iter();
681
682 if let Some(command) = cmds.find(|cmd| cmd.doc.caller.as_ref() == caller) {
683 let entry = (command.clone(), Arc::from(call));
684 self.aliases.insert(Arc::from(alias), entry);
685 Ok(None)
686 } else {
687 Err(txt!("The caller [a]{caller}[] was not found"))
688 }
689 }
690
691 /// Runs a command from a call
692 fn get_cmd(
693 &self,
694 call: impl Display,
695 ) -> Result<impl for<'a> FnOnce(&'a mut Pass) -> CmdResult + 'static, Text> {
696 let call = call.to_string();
697 let mut args = call.split_whitespace();
698 let caller = args.next().ok_or(txt!("The command is empty"))?.to_string();
699
700 let (command, call) = {
701 if let Some(command) = self.aliases.get(caller.as_str()) {
702 let (alias, aliased_call) = command;
703 let mut aliased_call = aliased_call.to_string() + " ";
704 aliased_call.push_str(call.strip_prefix(&caller).unwrap());
705
706 (alias.clone(), aliased_call)
707 } else {
708 let command = self
709 .list
710 .iter()
711 .find(|cmd| cmd.doc.caller.as_ref() == caller)
712 .ok_or(txt!("[a]{caller}[]: No such command"))?;
713
714 (command.clone(), call.clone())
715 }
716 };
717
718 let silent = call.len() > call.trim_start().len();
719 let cmd = command.cmd.clone();
720 Ok(move |pa: &mut Pass| {
721 let args = Args::new(&call);
722
723 match catch_panic(move || cmd.lock().unwrap()(pa, args)) {
724 Some(result) => result
725 .map(|ok| ok.filter(|_| !silent))
726 .map_err(|err| txt!("[a]{caller}[]: {err}")),
727 None => Err(txt!("[a]{caller}[]: Command panicked")),
728 }
729 })
730 }
731
732 /// Adds a command to the list of commands
733 fn add(&mut self, command: Command) {
734 self.list.retain(|cmd| cmd.doc.caller != command.doc.caller);
735 self.list.push(command);
736 }
737
738 /// Gets the parameter checker for a command, if it exists
739 fn check_args<'a>(&self, call: &'a str) -> Option<impl FnOnce(&Pass) -> CheckedArgs + 'a> {
740 let mut args = call.split_whitespace();
741 let caller = args.next()?.to_string();
742
743 let check_args = if let Some((command, _)) = self.aliases.get(caller.as_str()) {
744 command.check_args
745 } else {
746 let command = self
747 .list
748 .iter()
749 .find(|cmd| cmd.doc.caller.as_ref() == caller)?;
750 command.check_args
751 };
752
753 Some(move |pa: &Pass| check_args(pa, &mut Args::new(call)))
754 }
755
756 /// Gets the last parsed [`Parameter`] of a call
757 fn args_after_check<'a>(&self, call: &'a str) -> Option<impl FnOnce(&Pass) -> Args<'a> + 'a> {
758 let mut args = call.split_whitespace();
759 let caller = args.next()?.to_string();
760
761 let check_args = if let Some((command, _)) = self.aliases.get(caller.as_str()) {
762 command.check_args
763 } else {
764 let command = self
765 .list
766 .iter()
767 .find(|cmd| cmd.doc.caller.as_ref() == caller)?;
768 command.check_args
769 };
770
771 Some(move |pa: &Pass| {
772 let mut args = Args::new(call);
773 check_args(pa, &mut args);
774 args
775 })
776 }
777}
778
779impl Default for Commands {
780 fn default() -> Self {
781 default::add_defalt_commands();
782 Self {
783 list: Default::default(),
784 aliases: Default::default(),
785 }
786 }
787}
788
789/// The standard error that should be returned when [`call`]ing
790/// commands.
791///
792/// This error _must_ include an error message in case of failure. It
793/// may also include a success message, but that is not required.
794///
795/// [`call`]: global::call
796pub type CmdResult = Result<Option<Text>, Text>;
797
798/// A function that can be called by name.
799#[derive(Clone)]
800struct Command {
801 cmd: InnerCmdFn,
802 check_args: CheckerFn,
803 doc: CmdDoc,
804}
805
806impl Command {
807 /// Returns a new instance of command.
808 fn new(
809 caller: String,
810 cmd: InnerCmdFn,
811 check_args: CheckerFn,
812 param_arg_names: Vec<Text>,
813 ) -> Self {
814 if caller.split_whitespace().count() != 1 {
815 panic!("Command caller \"{caller}\" contains more than one word");
816 }
817 Self {
818 cmd,
819 check_args,
820 doc: CmdDoc {
821 caller: caller.into(),
822 short: None,
823 long: None,
824 params: param_arg_names
825 .into_iter()
826 .map(|arg_name| ParamDoc { arg_name, short: None, long: None })
827 .collect(),
828 },
829 }
830 }
831}
832
833/// A list of names to call a command with
834pub trait Caller<'a>: Sized {
835 /// An [`Iterator`] over said callers
836 fn into_callers(self) -> impl Iterator<Item = &'a str>;
837}
838
839impl<'a> Caller<'a> for &'a str {
840 fn into_callers(self) -> impl Iterator<Item = &'a str> {
841 [self].into_iter()
842 }
843}
844
845impl<'a> Caller<'a> for &'a [&'a str] {
846 fn into_callers(self) -> impl Iterator<Item = &'a str> {
847 self.iter().cloned()
848 }
849}
850
851impl<'a, const N: usize> Caller<'a> for [&'a str; N] {
852 fn into_callers(self) -> impl Iterator<Item = &'a str> {
853 self.into_iter()
854 }
855}
856
857type InnerCmdFn = Arc<Mutex<dyn FnMut(&mut Pass, Args) -> CmdResult + Send + 'static>>;
858type CheckerFn = fn(&Pass, &mut Args) -> CheckedArgs;
859type CheckedArgs = (
860 Vec<(Range<usize>, Option<FormId>)>,
861 Option<(Range<usize>, Text)>,
862);
863
864trait CmdFn<Arguments>: Send + 'static {
865 fn call(&mut self, pa: &mut Pass, args: Args) -> CmdResult;
866
867 fn check_args(
868 pa: &Pass,
869 args: &mut Args,
870 ) -> (
871 Vec<(Range<usize>, Option<FormId>)>,
872 Option<(Range<usize>, Text)>,
873 );
874
875 fn param_arg_names() -> Vec<Text>;
876}
877
878impl<F: FnMut(&mut Pass) -> CmdResult + Send + 'static> CmdFn<()> for F {
879 fn call(&mut self, pa: &mut Pass, _: Args) -> CmdResult {
880 self(pa)
881 }
882
883 fn check_args(
884 _: &Pass,
885 args: &mut Args,
886 ) -> (
887 Vec<(Range<usize>, Option<FormId>)>,
888 Option<(Range<usize>, Text)>,
889 ) {
890 if let Some(start) = args.next_start() {
891 let err = txt!("Too many arguments");
892 return (Vec::new(), Some((start..args.param_range().end, err)));
893 }
894
895 (Vec::new(), None)
896 }
897
898 fn param_arg_names() -> Vec<Text> {
899 Vec::new()
900 }
901}
902
903macro_rules! implCmdFn {
904 ($($param:ident),+) => {
905 impl<$($param),+, F> CmdFn<($($param,)+)> for F
906 where
907 $($param: Parameter,)+
908 F: FnMut(&mut Pass, $($param),+) -> CmdResult + Send + 'static
909 {
910 #[allow(non_snake_case)]
911 fn call(&mut self, pa: &mut Pass, mut args: Args) -> CmdResult {
912 $(
913 let ($param, _) = $param::from_args(pa, &mut args)?;
914 )+
915
916 self(pa, $($param),+)
917 }
918
919 fn check_args(
920 pa: &Pass,
921 args: &mut Args,
922 ) -> (
923 Vec<(Range<usize>, Option<FormId>)>,
924 Option<(Range<usize>, Text)>,
925 ) {
926 let mut ok_ranges = Vec::new();
927
928 $(
929 let start = args.next_start();
930 args.use_completions_for::<$param>();
931 let result = args.next_as_with_form::<$param>(pa);
932 match result {
933 Ok((_, form)) => {
934 if let Some(start) = start.filter(|s| args.param_range().end > *s) {
935 ok_ranges.push((start..args.param_range().end, form));
936 }
937 }
938 Err(err) => match start.filter(|s| args.param_range().end > *s) {
939 Some(_) => return (ok_ranges, Some((args.param_range(), err))),
940 None => return (ok_ranges, None)
941 }
942 }
943 )+
944
945 if let Some(start) = args.next_start() {
946 let err = txt!("Too many arguments");
947 return (ok_ranges, Some((start..args.param_range().end, err)));
948 }
949
950 (ok_ranges, None)
951 }
952
953 fn param_arg_names() -> Vec<Text> {
954 vec![$($param::arg_name()),+]
955 }
956 }
957 }
958}
959
960implCmdFn!(P0);
961implCmdFn!(P0, P1);
962implCmdFn!(P0, P1, P2);
963implCmdFn!(P0, P1, P2, P3);
964implCmdFn!(P0, P1, P2, P3, P4);
965implCmdFn!(P0, P1, P2, P3, P4, P5);
966implCmdFn!(P0, P1, P2, P3, P4, P5, P6);
967implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7);
968implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7, P8);
969implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
970implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
971implCmdFn!(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);