rustyline/
validate.rs

1//! Input validation API (Multi-line editing)
2
3use crate::keymap::Invoke;
4use crate::Result;
5
6/// Input validation result
7#[non_exhaustive]
8pub enum ValidationResult {
9    /// Incomplete input
10    Incomplete,
11    /// Validation fails with an optional error message. User must fix the
12    /// input.
13    Invalid(Option<String>),
14    /// Validation succeeds with an optional message
15    Valid(Option<String>),
16}
17
18impl ValidationResult {
19    pub(crate) fn is_valid(&self) -> bool {
20        matches!(self, ValidationResult::Valid(_))
21    }
22
23    pub(crate) fn has_message(&self) -> bool {
24        matches!(
25            self,
26            ValidationResult::Valid(Some(_)) | ValidationResult::Invalid(Some(_))
27        )
28    }
29}
30
31/// Give access to user input.
32pub struct ValidationContext<'i> {
33    i: &'i mut dyn Invoke,
34}
35
36impl<'i> ValidationContext<'i> {
37    pub(crate) fn new(i: &'i mut dyn Invoke) -> Self {
38        ValidationContext { i }
39    }
40
41    /// Returns user input.
42    #[must_use]
43    pub fn input(&self) -> &str {
44        self.i.input()
45    }
46
47    // TODO
48    //fn invoke(&mut self, cmd: Cmd) -> Result<?> {
49    //    self.i.invoke(cmd)
50    //}
51}
52
53/// This trait provides an extension interface for determining whether
54/// the current input buffer is valid. Rustyline uses the method
55/// provided by this trait to decide whether hitting the enter key
56/// will end the current editing session and return the current line
57/// buffer to the caller of `Editor::readline` or variants.
58pub trait Validator {
59    /// Takes the currently edited `input` and returns a
60    /// `ValidationResult` indicating whether it is valid or not along
61    /// with an option message to display about the result. The most
62    /// common validity check to implement is probably whether the
63    /// input is complete or not, for instance ensuring that all
64    /// delimiters are fully balanced.
65    ///
66    /// If you implement more complex validation checks it's probably
67    /// a good idea to also implement a `Hinter` to provide feedback
68    /// about what is invalid.
69    ///
70    /// For auto-correction like a missing closing quote or to reject invalid
71    /// char while typing, the input will be mutable (TODO).
72    fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
73        let _ = ctx;
74        Ok(ValidationResult::Valid(None))
75    }
76
77    /// Configure whether validation is performed while typing or only
78    /// when user presses the Enter key.
79    ///
80    /// Default is `false`.
81    ///
82    /// This feature is not yet implemented, so this function is currently a
83    /// no-op
84    fn validate_while_typing(&self) -> bool {
85        false
86    }
87}
88
89impl Validator for () {}
90
91impl<'v, V: ?Sized + Validator> Validator for &'v V {
92    fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
93        (**self).validate(ctx)
94    }
95
96    fn validate_while_typing(&self) -> bool {
97        (**self).validate_while_typing()
98    }
99}
100
101/// Simple matching bracket validator.
102#[derive(Default)]
103pub struct MatchingBracketValidator {
104    _priv: (),
105}
106
107impl MatchingBracketValidator {
108    /// Constructor
109    #[must_use]
110    pub fn new() -> Self {
111        Self { _priv: () }
112    }
113}
114
115impl Validator for MatchingBracketValidator {
116    fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
117        Ok(validate_brackets(ctx.input()))
118    }
119}
120
121fn validate_brackets(input: &str) -> ValidationResult {
122    let mut stack = vec![];
123    for c in input.chars() {
124        match c {
125            '(' | '[' | '{' => stack.push(c),
126            ')' | ']' | '}' => match (stack.pop(), c) {
127                (Some('('), ')') | (Some('['), ']') | (Some('{'), '}') => {}
128                (Some(wanted), _) => {
129                    return ValidationResult::Invalid(Some(format!(
130                        "Mismatched brackets: {:?} is not properly closed",
131                        wanted
132                    )))
133                }
134                (None, c) => {
135                    return ValidationResult::Invalid(Some(format!(
136                        "Mismatched brackets: {:?} is unpaired",
137                        c
138                    )))
139                }
140            },
141            _ => {}
142        }
143    }
144    if stack.is_empty() {
145        ValidationResult::Valid(None)
146    } else {
147        ValidationResult::Incomplete
148    }
149}