Skip to main content

duat_core/cmd/
parameters.rs

1//! Defines the processing of parameters in commands
2//!
3//! This processing first separates the [`Args`] of the call and then
4//! transforms the list of arguments into a list of [`Parameter`]s, as
5//! defined by the command. Each [`Parameter`] may take multiple
6//! words, which makes this structure very flexible for
7//! multiple branching paths on how to read the arguments, all from
8//! the same command.
9use std::{
10    any::TypeId,
11    iter::Peekable,
12    ops::Range,
13    path::PathBuf,
14    sync::atomic::{AtomicBool, Ordering},
15};
16
17use crossterm::style::Color;
18
19use crate::{
20    context::Handle,
21    data::Pass,
22    form::{self, FormId},
23    text::{Text, txt},
24};
25
26macro_rules! implDeref {
27    ($type:ty, $target:ty $(, $($args:tt)+)?) => {
28        impl$(<$($args)+>)? std::ops::Deref for $type$(<$($args)+>)? {
29            type Target = $target;
30
31            fn deref(&self) -> &Self::Target {
32                &self.0
33            }
34        }
35
36        impl$(<$($args)+>)? std::ops::DerefMut for $type$(<$($args)+>)? {
37            fn deref_mut(&mut self) -> &mut Self::Target {
38                &mut self.0
39            }
40        }
41    }
42}
43
44/// A parameter for commands that can be called
45///
46/// This parameter must be parseable from [`Args`], which come from a
47/// `&str`. It can take multiple words, and can be composed of other
48/// [`Parameter`]s. An example of this is the [`Form`], which is
49/// composed of multiple [`Color`] parameters, which are then composed
50/// of some format (rgb, hsl), which is then composed of more
51/// parameters, like rgb values, for example.
52///
53/// Other types of [`Parameter`] are just a "list" of other
54/// [`Parameter`]s. For example, [`Vec<P>`] can be used as a
55/// [`Parameter`] to capture any number of `P` arguments.
56/// Additionally, there is the [`Between<MIN, MAX, P>`], which is
57/// _like_ [`Vec<P>`], but takes at least `MIN` `P`s and at most `MAX`
58/// `P`s.
59///
60/// [`Form`]: crate::form::Form
61pub trait Parameter: Sized + 'static {
62    /// Tries to consume arguments until forming a parameter
63    ///
64    /// Since parameters shouldn't mutate data, pa is just a regular
65    /// shared reference.
66    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text>;
67
68    /// A short descriptive name
69    ///
70    /// This is for the purpose of writing documentation, and will be
71    /// used like `<path>` in `buffer <path>`, displaying what type it
72    /// is.
73    fn arg_name() -> Text {
74        txt!("[param]arg")
75    }
76}
77
78/// Command [`Parameter`]: A flag passed to the command
79///
80/// `Flag`s in duat differ from those of UNIX like operating system
81/// commands, since a flag can show up anywhere, not just before some
82/// standalone `--` which separates flags and "not flags". Instead,
83/// what determines if an argument starting with `--` or `-` is a flag
84/// or not is if said argument is _quoted_:
85///
86/// ```text
87/// mycmd --this-is-a-flag "--this-is not a flag" -blobflag -- --flag
88/// ```
89pub enum Flag<S: AsRef<str> = String> {
90    /// A word flag is prefixed by `--` and represents only one thing
91    ///
92    /// Examples of this are the `--cfg` and `--cfg-manifest`, which
93    /// are used by the `edit` and `open` commands to open Duat
94    /// configuration files.
95    Word(S),
96    /// A blob flag is prefixed by `-` and represents one thing per
97    /// `char`
98    ///
99    /// An example, coming from UNIX like operating systems is `rm
100    /// -rf`, witch will forcefully (`f`) remove files recursively
101    /// (`r`).
102    Blob(S),
103}
104
105impl Parameter for Flag {
106    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
107        let arg = args.next()?;
108        if !arg.is_quoted {
109            if let Some(word) = arg.strip_prefix("--") {
110                Ok((
111                    Flag::Word(word.to_string()),
112                    Some(form::id_of!("param.flag")),
113                ))
114            } else if let Some(blob_chars) = arg.strip_prefix("-") {
115                let mut blob = String::new();
116                for char in blob_chars.chars() {
117                    if !blob.chars().any(|c| c == char) {
118                        blob.push(char);
119                    }
120                }
121
122                Ok((Flag::Blob(blob), Some(form::id_of!("param.flag"))))
123            } else {
124                Err(txt!("[param.info]Flag[]s must start with `-` or `--`"))
125            }
126        } else {
127            Err(txt!("Quoted arguments can't be [param.info]Flag[]s"))
128        }
129    }
130
131    fn arg_name() -> Text {
132        txt!("[param.flag]flag")
133    }
134}
135
136impl<S: AsRef<str>> Flag<S> {
137    /// Returns `Ok` only if the `Flag` is of type [`Flag::Word`]
138    pub fn as_word(self) -> Result<S, Text> {
139        match self {
140            Flag::Word(word) => Ok(word),
141            Flag::Blob(_) => Err(txt!(
142                "[param.info]Flag[] is of type [param.info]blob[], not [param.info]word"
143            )),
144        }
145    }
146
147    /// Returns true if this `Flag` is a `Flag::Word(word)`
148    pub fn is_word(&self, word: &str) -> bool {
149        self.as_str().as_word().ok().is_some_and(|w| w == word)
150    }
151
152    /// Returns `Ok` only if the `Flag` is of type [`Flag::Blob`]
153    pub fn as_blob(self) -> Result<S, Text> {
154        match self {
155            Flag::Blob(blob) => Ok(blob),
156            Flag::Word(_) => Err(txt!(
157                "[param.info]Flag[] is of type [param.info]word[], not [param.info]blob"
158            )),
159        }
160    }
161
162    /// Returns true if this `Flag` is a [`Flag::Blob`] with all
163    /// characters in `blob`
164    pub fn has_blob(&self, blob: &str) -> bool {
165        self.as_str()
166            .as_blob()
167            .ok()
168            .is_some_and(|b| blob.chars().all(|char| b.chars().any(|c| c == char)))
169    }
170
171    /// Returns an [`Err`] if the `Flag` is a blob or doesn't belong
172    /// on the list
173    ///
174    /// this is useful for quickly matching against a fixed list of
175    /// possible words, while having a "catchall `_ => {}`, which will
176    /// never match.
177    pub fn word_from_list<const N: usize>(self, list: [&str; N]) -> Result<&str, Text> {
178        let word = self.as_word()?;
179        if let Some(word) = list.into_iter().find(|w| w == &word.as_ref()) {
180            Ok(word)
181        } else {
182            Err(txt!("Word not in list of valid options"))
183        }
184    }
185
186    /// Returns a new `Flag<&str>`
187    ///
188    /// This is particularly useful in pattern matching.
189    pub fn as_str(&self) -> Flag<&str> {
190        match self {
191            Flag::Word(word) => Flag::Word(word.as_ref()),
192            Flag::Blob(blob) => Flag::Blob(blob.as_ref()),
193        }
194    }
195}
196
197/// Command [`Parameter`]: A list of [`Flag`]s passed to the command
198///
199/// This `Parameter` will capture all following arguments, until it
200/// finds one that can't be parsed as a `Flag` (i.e. not starting with
201/// `--` or `-`, or quoted arguments).
202///
203/// Unlike [`Vec`], this `Parameter` _can_ be followed up by other
204/// ones, and if there are no `Flag`s, then this will have an empty
205/// list. As such, this function never actually fails.
206pub struct Flags(pub Vec<Flag>);
207
208impl Parameter for Flags {
209    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
210        let mut list = Vec::new();
211
212        while let Ok(flag) = args.next_as(pa) {
213            list.push(flag);
214        }
215
216        Ok((Self(list), Some(form::id_of!("param.flag"))))
217    }
218
219    fn arg_name() -> Text {
220        txt!("[param.flag]flags...")
221    }
222}
223
224impl Flags {
225    /// Returns `true` if the `Flags` contains a [`Flag::Word`] with
226    /// the given word
227    pub fn has_word(&self, word: &str) -> bool {
228        self.0
229            .iter()
230            .any(|flag| flag.as_str().as_word() == Ok(word))
231    }
232
233    /// Returns `true` if the `Flags` contains [`Flag::Blob`]s with
234    /// all `char`s in the given blob
235    pub fn has_blob(&self, blob: &str) -> bool {
236        blob.chars().all(|char| {
237            self.0
238                .iter()
239                .filter_map(|flag| flag.as_str().as_blob().ok())
240                .any(|blob| blob.chars().any(|c| c == char))
241        })
242    }
243}
244
245impl std::ops::Deref for Flags {
246    type Target = Vec<Flag>;
247
248    fn deref(&self) -> &Self::Target {
249        &self.0
250    }
251}
252
253impl std::ops::DerefMut for Flags {
254    fn deref_mut(&mut self) -> &mut Self::Target {
255        &mut self.0
256    }
257}
258
259/// Command [`Parameter`]: Global or local scope for commands
260///
261/// This struct captures a [`Flag`] if it exists, if it is `--global`,
262/// then the scope is global. If the flag is something else, it
263/// returns an [`Err`]. If there is no `Flag` then the scope is
264/// [`Scope::Local`].
265#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
266pub enum Scope {
267    /// The scope of this command is meant to be local
268    ///
269    /// This is a loose definition. Usually, it means it should affect
270    /// stuff related only to the current [`Buffer`], but you can
271    /// decide that.
272    ///
273    /// [`Buffer`]: crate::buffer::Buffer
274    Local,
275    /// The scope of this command is meant to be global
276    ///
277    /// This is a loose definition. Usually, it means it should affect
278    /// stuff related to _all_ [`Buffer`]s, but you decide what it
279    /// really means.
280    ///
281    /// [`Buffer`]: crate::buffer::Buffer
282    Global,
283}
284
285impl Parameter for Scope {
286    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
287        if let Ok((flag, form)) = args.next_as_with_form::<Flag>(pa) {
288            if flag.is_word("global") {
289                Ok((Scope::Global, form))
290            } else {
291                Err(txt!("Invalid flag, it can only be [param.info]--global"))
292            }
293        } else {
294            Ok((Scope::Local, None))
295        }
296    }
297
298    fn arg_name() -> Text {
299        txt!("[param.flag]--global[param.punctuation]?")
300    }
301}
302
303impl<P: Parameter> Parameter for Option<P> {
304    /// Will match either [`Parameter`] given, or nothing
305    ///
306    /// This, like other lists, _has_ to be the final argument in the
307    /// [`Parameter`] list, as it will either match correcly, finish
308    /// matching, or match incorrectly in order to give accurate
309    /// feedback.
310    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
311        match args.next_as::<P>(pa) {
312            Ok(arg) => Ok((Some(arg), None)),
313            Err(err) if args.is_forming_param => Err(err),
314            Err(_) => Ok((None, None)),
315        }
316    }
317
318    fn arg_name() -> Text {
319        txt!("{}[param.punctuation]?", P::arg_name())
320    }
321}
322
323impl<P: Parameter> Parameter for Vec<P> {
324    /// Will match a list of [`Parameter`]s
325    ///
326    /// This, like other lists, _has_ to be the final argument in the
327    /// [`Parameter`] list, as it will either match correcly, finish
328    /// matching, or match incorrectly in order to give accurate
329    /// feedback.
330    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
331        let mut returns = Vec::new();
332
333        loop {
334            match args.next_as::<P>(pa) {
335                Ok(ret) => returns.push(ret),
336                Err(err) if args.is_forming_param => return Err(err),
337                Err(_) => break Ok((returns, None)),
338            }
339        }
340    }
341
342    fn arg_name() -> Text {
343        txt!("{}[param.punctuation]...", P::arg_name())
344    }
345}
346
347impl<const N: usize, P: Parameter> Parameter for [P; N] {
348    /// Will match either the argument given, or nothing
349    ///
350    /// This, like other lists, _has_ to be the final argument in the
351    /// [`Parameter`] list, as it will either match correcly, finish
352    /// matching, or match incorrectly in order to give accurate
353    /// feedback.
354    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
355        use std::mem::MaybeUninit;
356        let mut returns = [const { MaybeUninit::uninit() }; N];
357
358        for r in returns.iter_mut() {
359            match args.next_as::<P>(pa) {
360                Ok(ret) => *r = MaybeUninit::new(ret),
361                Err(err) => return Err(err),
362            }
363        }
364
365        Ok((returns.map(|ret| unsafe { ret.assume_init() }), None))
366    }
367
368    fn arg_name() -> Text {
369        let punct = form::id_of!("param.punctuation");
370        txt!(
371            "{punct}[[{}{punct}; [param.count]{N}{punct}]]",
372            P::arg_name()
373        )
374    }
375}
376
377/// Command [`Parameter`]: A list of between `MIN` and `MAX` items
378///
379/// This, like other lists, _has_ to be the final argument in the
380/// [`Parameter`] list, as it will either match correcly, finish
381/// matching, or match incorrectly in order to give accurate
382/// feedback.
383pub struct Between<const MIN: usize, const MAX: usize, P>(pub Vec<P>);
384
385impl<const MIN: usize, const MAX: usize, P: Parameter> Parameter for Between<MIN, MAX, P> {
386    /// Will match between `MIN` and `MAX` [`Parameter`]s
387    ///
388    /// This, like other lists, _has_ to be the final argument in the
389    /// [`Parameter`] list, as it will either match correcly, finish
390    /// matching, or match incorrectly in order to give accurate
391    /// feedback.
392    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
393        let mut returns = Vec::new();
394
395        for _ in 0..MAX {
396            match args.next_as::<P>(pa) {
397                Ok(ret) => returns.push(ret),
398                Err(err) if args.is_forming_param => return Err(err),
399                Err(_) if returns.len() >= MIN => return Ok((Self(returns), None)),
400                Err(err) => return Err(err),
401            }
402        }
403
404        if returns.len() >= MIN {
405            Ok((Self(returns), None))
406        } else {
407            Err(txt!(
408                "List needed at least [param.info]{MIN}[] elements, got only [a]{}",
409                returns.len()
410            ))
411        }
412    }
413
414    fn arg_name() -> Text {
415        let punct = form::id_of!("param.punctuation");
416        let count = form::id_of!("param.count");
417        txt!(
418            "{punct}[[{}{punct}; {count}{MIN}{punct}..{count}{MAX}{punct}]]",
419            P::arg_name()
420        )
421    }
422}
423
424impl<const MIN: usize, const MAX: usize, P> std::ops::Deref for Between<MIN, MAX, P> {
425    type Target = Vec<P>;
426
427    fn deref(&self) -> &Self::Target {
428        &self.0
429    }
430}
431
432impl<const MIN: usize, const MAX: usize, P> std::ops::DerefMut for Between<MIN, MAX, P> {
433    fn deref_mut(&mut self) -> &mut Self::Target {
434        &mut self.0
435    }
436}
437
438impl Parameter for String {
439    fn new(_: &Pass, args: &mut Args) -> Result<(String, Option<FormId>), Text> {
440        Ok((args.next()?.to_string(), None))
441    }
442
443    fn arg_name() -> Text {
444        txt!("[param]arg")
445    }
446}
447
448/// Command [`Parameter`]: The remaining arguments, divided by a space
449///
450/// Fails if the [`String`] would be empty.
451pub struct Remainder(pub String);
452
453impl Parameter for Remainder {
454    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
455        let remainder: String = std::iter::from_fn(|| args.next().ok().map(|arg| arg.value))
456            .collect::<Vec<&str>>()
457            .join(" ");
458        if remainder.is_empty() {
459            Err(txt!("There are no more arguments"))
460        } else {
461            Ok((Self(remainder), None))
462        }
463    }
464
465    fn arg_name() -> Text {
466        txt!("[param]args")
467    }
468}
469implDeref!(Remainder, String);
470
471impl Parameter for Handle {
472    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
473        let buffer_name = args.next()?.value;
474        if let Some(handle) = crate::context::windows()
475            .buffers(pa)
476            .into_iter()
477            .find(|handle| handle.read(pa).name() == buffer_name)
478        {
479            Ok((handle, Some(form::id_of!("param.path.open"))))
480        } else {
481            Err(txt!("No buffer called [a]{buffer_name}[] open"))
482        }
483    }
484
485    fn arg_name() -> Text {
486        txt!("[param]buffer")
487    }
488}
489
490/// Command [`Parameter`]: An open [`Buffer`]'s name, except the
491/// current
492///
493/// [`Buffer`]: crate::buffer::Buffer
494pub struct OtherBuffer(pub Handle);
495
496impl Parameter for OtherBuffer {
497    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
498        let handle = args.next_as::<Handle>(pa)?;
499        let cur_handle = crate::context::current_buffer(pa);
500        if cur_handle == handle {
501            Err(txt!("Argument can't be the current buffer"))
502        } else {
503            Ok((Self(handle), Some(form::id_of!("param.path.open"))))
504        }
505    }
506
507    fn arg_name() -> Text {
508        txt!("[param]buffer")
509    }
510}
511implDeref!(OtherBuffer, Handle);
512
513/// Command [`Parameter`]: A file that _could_ exist
514///
515/// This is the case if the file's path has a parent that exists,
516/// or if the file itself exists.
517///
518/// [`Buffer`]: crate::buffer::Buffer
519pub struct ValidFilePath(pub PathBuf, bool);
520
521impl Parameter for ValidFilePath {
522    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
523        let path = PathBuf::from(
524            shellexpand::full(args.next()?.value)
525                .map_err(|err| txt!("{err}"))?
526                .into_owned(),
527        );
528
529        let canon_path = path.canonicalize();
530        let path = if let Ok(path) = &canon_path {
531            if !path.is_file() {
532                return Err(txt!("Path is not a buffer"));
533            }
534            path.clone()
535        } else if canon_path.is_err()
536            && let Ok(canon_path) = path.with_file_name(".").canonicalize()
537        {
538            canon_path.join(
539                path.file_name()
540                    .ok_or_else(|| txt!("Path has no buffer name"))?,
541            )
542        } else {
543            return Err(txt!("Path was not found"));
544        };
545
546        if let Some(parent) = path.parent()
547            && let Ok(false) | Err(_) = parent.try_exists()
548        {
549            return Err(txt!("Path's parent doesn't exist"));
550        }
551
552        let (form, exists_or_is_open) = if crate::context::windows()
553            .buffers(pa)
554            .into_iter()
555            .map(|handle| handle.read(pa).path())
556            .any(|p| std::path::Path::new(&p) == path)
557        {
558            (form::id_of!("param.path.open"), true)
559        } else if let Ok(true) = path.try_exists() {
560            (form::id_of!("param.path.exists"), true)
561        } else {
562            (form::id_of!("param.path"), false)
563        };
564
565        Ok((Self(path, exists_or_is_open), Some(form)))
566    }
567
568    fn arg_name() -> Text {
569        txt!("[param]path")
570    }
571}
572implDeref!(ValidFilePath, PathBuf);
573
574/////////// Command specific parameters
575
576/// Comand [`Parameter`]: A [`ValidFilePath`], [`Handle`], `--cfg` or
577/// `--cfg-manifest`
578///
579/// This is a generalized way of switching to a [`Handle<Buffer`] on
580/// the `edit` and `open` commands.
581#[doc(hidden)]
582pub enum PathOrBufferOrCfg {
583    Path(PathBuf),
584    Buffer(Handle),
585    Cfg,
586    CfgManifest,
587}
588
589impl Parameter for PathOrBufferOrCfg {
590    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
591        struct DropGuard;
592        impl Drop for DropGuard {
593            fn drop(&mut self) {
594                ONLY_EXISTING.store(false, Ordering::Relaxed);
595            }
596        }
597
598        let _guard = DropGuard;
599
600        args.use_completions_for::<CfgOrManifest>();
601        args.use_completions_for::<Handle>();
602        args.use_completions_for::<ValidFilePath>();
603
604        if let Ok((cfg_or_manifest, form)) = args.next_as_with_form::<CfgOrManifest>(pa) {
605            match cfg_or_manifest {
606                CfgOrManifest::Cfg => Ok((Self::Cfg, form)),
607                CfgOrManifest::Manifest => Ok((Self::CfgManifest, form)),
608            }
609        } else if let Ok((handle, form)) = args.next_as_with_form::<Handle>(pa) {
610            Ok((Self::Buffer(handle), form))
611        } else {
612            let (path, form) = args.next_as_with_form::<ValidFilePath>(pa)?;
613            if !path.1 && ONLY_EXISTING.load(Ordering::Relaxed) {
614                Err(txt!("[a]{path}[]: No such file"))
615            } else {
616                Ok((Self::Path(path.0), form))
617            }
618        }
619    }
620
621    fn arg_name() -> Text {
622        let flag = form::id_of!("param.flag");
623        let punct = form::id_of!("param.punctuation");
624        txt!("[param]path{punct}/[param]buffer{punct}/{flag}--cfg{punct}/{flag}--cfg-manifest")
625    }
626}
627
628static ONLY_EXISTING: AtomicBool = AtomicBool::new(false);
629
630/// Command [`Parameter`]: The `--existing` flag
631pub struct Existing;
632
633impl Parameter for Existing {
634    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
635        let initial = args.clone();
636        let Ok((flag, form)) = args.next_as_with_form::<Flag>(pa) else {
637            return Ok((Self, None));
638        };
639
640        if flag.is_word("existing") {
641            ONLY_EXISTING.store(true, Ordering::Relaxed);
642        } else {
643            *args = initial;
644        }
645
646        Ok((Self, form))
647    }
648
649    fn arg_name() -> Text {
650        txt!("[param.flag]--existing[param.punctuation]?")
651    }
652}
653
654/// Command [`Parameter`]: `--cfg` or `--cfg-manifest`
655///
656/// This is a quick shorthand to get `{duat_config}/src/lib.rs` or
657/// `{duat_config}/Cargo.toml`, respectively
658pub enum CfgOrManifest {
659    /// Represents `{duat_config}/src/lib.rs`
660    Cfg,
661    /// Represents `{duat_config}/Cargo.toml`
662    Manifest,
663}
664
665impl Parameter for CfgOrManifest {
666    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
667        if let Ok((flag, form)) = args.next_as_with_form::<Flag>(pa)
668            && let Some(ret) = match flag.as_word()?.as_str() {
669                "cfg" => Some((Self::Cfg, form)),
670                "cfg-manifest" => Some((Self::Manifest, form)),
671                _ => None,
672            }
673        {
674            Ok(ret)
675        } else {
676            Err(txt!(
677                "Invalid flag, pick [param.flag]cfg[] or [param.flag]cfg-manifest"
678            ))
679        }
680    }
681
682    fn arg_name() -> Text {
683        txt!("[param.flag]--cfg[param.punctuation]/[param.flag]--cfg-manifest")
684    }
685}
686
687/// Command [`Parameter`]: An [`f32`] from a [`u8`] or a percentage
688///
689/// The percentage is of whole divisions of 100, 100 being equivalent
690/// to 255 in [`u8`].
691pub struct F32PercentOfU8(pub f32);
692
693impl Parameter for F32PercentOfU8 {
694    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
695        let arg = args.next()?;
696        if let Some(percentage) = arg.strip_suffix("%") {
697            let percentage: u8 = percentage
698                .parse()
699                .map_err(|_| txt!("[a]{arg}[] is not a valid percentage"))?;
700            if percentage <= 100 {
701                Ok((Self(percentage as f32 / 100.0), None))
702            } else {
703                Err(txt!("[a]{arg}[] is more than [a]100%"))
704            }
705        } else {
706            let byte: u8 = arg
707                .parse()
708                .map_err(|_| txt!("[a]{arg}[] couldn't be parsed"))?;
709            Ok((Self(byte as f32 / 255.0), None))
710        }
711    }
712
713    fn arg_name() -> Text {
714        let punct = form::id_of!("param.punctuation");
715        txt!("[param]u8{punct}/[param]0..=100%")
716    }
717}
718implDeref!(F32PercentOfU8, f32);
719
720impl Parameter for Color {
721    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
722        const fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
723            t = if t < 0.0 { t + 1.0 } else { t };
724            t = if t > 1.0 { t - 1.0 } else { t };
725            if t < 1.0 / 6.0 {
726                p + (q - p) * 6.0 * t
727            } else if t < 1.0 / 2.0 {
728                q
729            } else if t < 2.0 / 3.0 {
730                p + (q - p) * (2.0 / 3.0 - t) * 6.0
731            } else {
732                p
733            }
734        }
735
736        let arg = args.next()?.value;
737        // Expects "#{red:x}{green:x}{blue:x}"
738        if let Some(hex) = arg.strip_prefix("#") {
739            let total = match u32::from_str_radix(hex, 16) {
740                Ok(total) if hex.len() == 6 => total,
741                _ => return Err(txt!("Hexcode does not contain 6 hex values")),
742            };
743            let r = (total >> 16) as u8;
744            let g = (total >> 8) as u8;
745            let b = total as u8;
746            Ok((Color::Rgb { r, g, b }, None))
747            // Expects "hsl {hue%?} {saturation%?} {lightness%?}"
748        } else if arg == "hsl" {
749            let hue = args.next_as::<F32PercentOfU8>(pa)?.0;
750            let sat = args.next_as::<F32PercentOfU8>(pa)?.0;
751            let lit = args.next_as::<F32PercentOfU8>(pa)?.0;
752            let [r, g, b] = if sat == 0.0 {
753                [lit.round() as u8; 3]
754            } else {
755                let q = if lit < 0.5 {
756                    lit * (1.0 + sat)
757                } else {
758                    lit + sat - lit * sat
759                };
760                let p = 2.0 * lit - q;
761                let r = hue_to_rgb(p, q, hue + 1.0 / 3.0);
762                let g = hue_to_rgb(p, q, hue);
763                let b = hue_to_rgb(p, q, hue - 1.0 / 3.0);
764                [r.round() as u8, g.round() as u8, b.round() as u8]
765            };
766            Ok((Color::Rgb { r, g, b }, None))
767        } else {
768            Err(txt!("Color format was not recognized"))
769        }
770    }
771
772    fn arg_name() -> Text {
773        txt!("[param]#{{rgb hex}}[param.punctuation]/[param]hsl {{h}} {{s}} {{l}}")
774    }
775}
776
777/// Command [`Parameter`]: The name of a [`Form`] that has been [set]
778///
779/// [set]: crate::form::set
780/// [`Form`]: crate::form::Form
781pub struct FormName(pub String);
782
783impl Parameter for FormName {
784    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
785        let arg = args.next()?.value;
786        if !arg.chars().all(|c| c.is_ascii_alphanumeric() || c == '.') {
787            return Err(txt!(
788                "Expected identifiers separated by '.'s, found [a]{arg}"
789            ));
790        }
791        if crate::form::exists(arg) {
792            Ok((Self(arg.to_string()), Some(form::id_of_non_static(arg))))
793        } else {
794            Err(txt!("The form [a]{arg}[] has not been set"))
795        }
796    }
797
798    fn arg_name() -> Text {
799        txt!("[param]form")
800    }
801}
802
803implDeref!(FormName, String);
804
805/// Command [`Parameter`]: An existing colorscheme's name
806pub struct ColorSchemeArg(pub String);
807
808impl Parameter for ColorSchemeArg {
809    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
810        let scheme = args.next()?.value;
811        if crate::form::colorscheme_exists(scheme) {
812            Ok((ColorSchemeArg(scheme.to_string()), None))
813        } else {
814            Err(txt!("The colorscheme [a]{scheme}[] was not found"))
815        }
816    }
817
818    fn arg_name() -> Text {
819        txt!("[param]colorscheme")
820    }
821}
822implDeref!(ColorSchemeArg, String);
823
824/// Command [`Parameter`]: Options for reloading
825#[doc(hidden)]
826pub struct ReloadOptions {
827    /// Wether to clean
828    pub clean: bool,
829    /// Wether to update
830    pub update: bool,
831}
832
833impl Parameter for ReloadOptions {
834    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
835        let flags = args.next_as::<Flags>(pa)?;
836
837        if flags
838            .iter()
839            .any(|flag| !flag.is_word("update") && !flag.is_word("clean"))
840        {
841            Err(txt!("Invalid [a]Flag"))
842        } else {
843            Ok((
844                Self {
845                    clean: flags.has_word("clean"),
846                    update: flags.has_word("update"),
847                },
848                Some(form::id_of!("param.flag")),
849            ))
850        }
851    }
852
853    fn arg_name() -> Text {
854        let punct = form::id_of!("param.punctuation");
855        txt!("[param.flag]--clean{punct}? [param.flag]--update{punct}?")
856    }
857}
858
859/// The list of arguments passed to a command
860#[derive(Clone)]
861pub struct Args<'a> {
862    iter: Peekable<ArgsIter<'a>>,
863    param_range: Range<usize>,
864    has_to_start_param: bool,
865    is_forming_param: bool,
866    // For figuring out what's being parsed atm
867    /// The argument index is used to keep parameters in the
868    /// last_parsed list, even if they failed parsing and another
869    /// parameter was parsed with the same argument instead.
870    arg_n: usize,
871    /// This list will have the TypeId of `T` within the
872    /// `self.next_as::<T>()` call. It serves the purpose of keeping
873    /// track of when a type is done being parsed.
874    currently_parsing: Vec<TypeId>,
875    /// This list will have the TypeId of `T` while `T` is within
876    /// currently_parsing or we haven't succesfully moved on to the
877    /// next argument.
878    /// This is used to aid completion, by keeping track of when we
879    /// have moved on to parsing a new argument.
880    last_parsed: Vec<(usize, TypeId)>,
881}
882
883impl<'arg> Args<'arg> {
884    /// Returns a new instance of `Args`
885    pub(super) fn new(command: &'arg str) -> Self {
886        Self {
887            iter: ArgsIter::new(command).peekable(),
888            param_range: 0..0,
889            has_to_start_param: false,
890            is_forming_param: false,
891            arg_n: 0,
892            currently_parsing: Vec::new(),
893            last_parsed: Vec::new(),
894        }
895    }
896
897    /// Returns the next word or quoted argument
898    #[allow(clippy::should_implement_trait)]
899    pub fn next(&mut self) -> Result<Arg<'arg>, Text> {
900        match self.iter.next() {
901            Some((value, range, is_quoted)) => {
902                self.param_range = range.clone();
903                if self.has_to_start_param {
904                    self.has_to_start_param = false;
905                    self.is_forming_param = true;
906                }
907
908                self.last_parsed.retain(|(start_n, ty)| {
909                    self.currently_parsing.contains(ty) || self.arg_n == *start_n
910                });
911
912                self.arg_n += 1;
913
914                Ok(Arg { value, is_quoted })
915            }
916            None => Err(txt!("Wrong argument count")),
917        }
918    }
919
920    /// Tries to parse the next argument as `P`
921    ///
922    /// If parsing fails, [`Args`] will be reset as if this function
923    /// wasn't called.
924    pub fn next_as<P: Parameter>(&mut self, pa: &Pass) -> Result<P, Text> {
925        self.next_as_with_form(pa).map(|(param, _)| param)
926    }
927
928    /// Tries to parse the next argument as `P`, also returning the
929    /// [`Option<Form>`]
930    ///
931    /// If parsing fails, [`Args`] will be reset as if this function
932    /// wasn't called.
933    pub fn next_as_with_form<P: Parameter>(
934        &mut self,
935        pa: &Pass,
936    ) -> Result<(P, Option<FormId>), Text> {
937        self.currently_parsing.push(TypeId::of::<P>());
938
939        let initial = (
940            self.iter.clone(),
941            self.param_range.clone(),
942            self.has_to_start_param,
943            self.is_forming_param,
944            self.arg_n,
945        );
946
947        self.has_to_start_param = true;
948        let ret = P::new(pa, self);
949        if ret.is_ok() {
950            self.is_forming_param = false;
951        } else {
952            self.iter = initial.0;
953            self.param_range = initial.1;
954            self.has_to_start_param = initial.2;
955            self.is_forming_param = initial.3;
956            self.arg_n = initial.4;
957        }
958
959        self.currently_parsing.retain(|&ty| ty != TypeId::of::<P>());
960
961        ret
962    }
963
964    /// Tries to get the next argument, otherwise returns a [`Text`]
965    pub fn next_else<T: Into<Text>>(&mut self, to_text: T) -> Result<&'arg str, Text> {
966        match self.next() {
967            Ok(arg) => Ok(arg.value),
968            Err(_) => Err(to_text.into()),
969        }
970    }
971
972    /// Returns the char position of the next argument
973    ///
974    /// Mostly used for error feedback by the [`PromptLine`]
975    ///
976    /// [`PromptLine`]: docs.rs/duat/latest/duat/widgets/struct.PromptLine.html
977    pub fn next_start(&mut self) -> Option<usize> {
978        self.iter.peek().map(|(_, r, _)| r.start)
979    }
980
981    /// The range of the previous [`Parameter`]
982    ///
983    /// Mostly used for error feedback by the [`PromptLine`]
984    ///
985    /// [`PromptLine`]: docs.rs/duat/latest/duat/widgets/struct.PromptLine.html
986    pub fn param_range(&self) -> Range<usize> {
987        self.param_range.clone()
988    }
989
990    /// The last [`Parameter`] type that was parsed
991    ///
992    /// This can/should be used for argument completion.
993    pub fn last_parsed(&self) -> Vec<TypeId> {
994        self.last_parsed.iter().map(|(_, ty)| *ty).collect()
995    }
996
997    /// Declare that you are trying to parse the given [`Parameter`]
998    /// type
999    ///
1000    /// This is used on completions, where multiple pools of choices
1001    /// may be selected from in order to get the completions list to
1002    /// show.
1003    pub fn use_completions_for<P: Parameter>(&mut self) {
1004        self.last_parsed.push((self.arg_n, TypeId::of::<P>()));
1005    }
1006}
1007
1008/// An arguemnt that was passed to a command
1009#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1010pub struct Arg<'arg> {
1011    /// The `&str` of that argument
1012    pub value: &'arg str,
1013    /// Wether said argument was quoted
1014    ///
1015    /// This is useful for the [`Flag`] parameter, since it lets you
1016    /// distinguish between `--flag` and `"--flag"`, treating the
1017    /// latter as not a `Flag`
1018    pub is_quoted: bool,
1019}
1020
1021impl<'arg> std::ops::Deref for Arg<'arg> {
1022    type Target = &'arg str;
1023
1024    fn deref(&self) -> &Self::Target {
1025        &self.value
1026    }
1027}
1028
1029impl<'arg, 'other> PartialEq<&'other str> for Arg<'arg> {
1030    fn eq(&self, other: &&'other str) -> bool {
1031        &self.value == other
1032    }
1033}
1034
1035/// A iterator over arguments in a `&str`, useful for the [`cmd`]
1036/// module
1037///
1038/// [`cmd`]: super
1039#[derive(Clone)]
1040pub struct ArgsIter<'a> {
1041    command: &'a str,
1042    chars: std::str::CharIndices<'a>,
1043    start: Option<usize>,
1044    end: Option<usize>,
1045    is_quoting: bool,
1046    last_char: char,
1047}
1048
1049impl<'a> ArgsIter<'a> {
1050    /// Returns a new iterator over arguments in a `&str`
1051    pub fn new(command: &'a str) -> Self {
1052        let mut args_iter = Self {
1053            command,
1054            chars: command.char_indices(),
1055            start: None,
1056            end: None,
1057            is_quoting: false,
1058            // Initial value doesn't matter, as long as it's not '\' or '"'
1059            last_char: 'a',
1060        };
1061
1062        args_iter.next();
1063        args_iter
1064    }
1065}
1066
1067impl<'a> Iterator for ArgsIter<'a> {
1068    type Item = (&'a str, Range<usize>, bool);
1069
1070    fn next(&mut self) -> Option<Self::Item> {
1071        let mut is_quoted = false;
1072        while let Some((b, char)) = self.chars.next() {
1073            let lc = self.last_char;
1074            self.last_char = char;
1075            if self.start.is_some() && char.is_whitespace() && !self.is_quoting {
1076                self.end = Some(b);
1077                break;
1078            } else if char == '\'' && lc != '\\' {
1079                self.is_quoting = !self.is_quoting;
1080                if !self.is_quoting {
1081                    is_quoted = true;
1082                    self.end = Some(b);
1083                    break;
1084                } else {
1085                    self.start = Some(b + 1);
1086                }
1087            } else if !char.is_whitespace() && self.start.is_none() {
1088                self.start = Some(b);
1089            }
1090        }
1091
1092        let e = self.end.take().unwrap_or(self.command.len());
1093        self.start
1094            .take()
1095            .map(|s| (&self.command[s..e], s..e, is_quoted))
1096    }
1097}
1098
1099macro_rules! parse_impl {
1100    ($t:ty, $static_list:expr) => {
1101        impl Parameter for $t {
1102            fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
1103                let arg = args.next()?;
1104                let arg = arg.parse().map_err(|_| {
1105                    txt!(
1106                        "[a]{arg}[] couldn't be parsed as [param.info]{}[]",
1107                        stringify!($t)
1108                    )
1109                });
1110                arg.map(|arg| (arg, None))
1111            }
1112
1113            fn arg_name() -> Text {
1114                txt!("[param]{}", stringify!($t))
1115            }
1116        }
1117    };
1118}
1119
1120parse_impl!(bool, Some(&["true", "false"]));
1121parse_impl!(u8, None);
1122parse_impl!(u16, None);
1123parse_impl!(u32, None);
1124parse_impl!(u64, None);
1125parse_impl!(u128, None);
1126parse_impl!(usize, None);
1127parse_impl!(i8, None);
1128parse_impl!(i16, None);
1129parse_impl!(i32, None);
1130parse_impl!(i64, None);
1131parse_impl!(i128, None);
1132parse_impl!(isize, None);
1133parse_impl!(f32, None);
1134parse_impl!(f64, None);
1135parse_impl!(path, Some(&[]));
1136
1137#[allow(non_camel_case_types)]
1138type path = std::path::PathBuf;