Skip to main content

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);