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