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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
//! Provides a method for presenting a prompt for user input that can be customized with [`actions`] //! and [`completions`]. //! //! The core functionality of this module is [`read_line`]. Its invocation can be cumbersome due //! to required type annotations, therefore this module also provider a [`Builder`] which helps to //! craft the invocation to [`read_line`]. //! //! ### Basic usage: //! //! ```no_run //! use rucline::Outcome::Accepted; //! use rucline::prompt::{Builder, Prompt}; //! //! if let Ok(Accepted(string)) = Prompt::from("What's you favorite website? ") //! // Add some tab completions (Optional) //! .suggester(vec![ //! "https://www.rust-lang.org/", //! "https://docs.rs/", //! "https://crates.io/", //! ]) //! //Block until value is ready //! .read_line() //! { //! println!("'{}' seems to be your favorite website", string); //! } //! ``` //! //! [`actions`]: ../actions/enum.Action.html //! [`completions`]: ../completion/index.html //! [`read_line`]: fn.read_line.html //! [`Builder`]: trait.Builder.html mod builder; mod context; mod writer; use context::Context; use writer::Writer; use crate::actions::{action_for, Action, Direction, Overrider, Range, Scope}; use crate::completion::{Completer, Suggester}; use crate::Buffer; pub use builder::{Builder, Prompt}; /// The outcome of [`read_line`], being either accepted or canceled by the user. /// /// [`read_line`]: fn.read_line.html pub enum Outcome { /// If the user accepts the prompt input, i.e. an [`Accept`] event was emitted. this variant will /// contain the accepted text. /// /// [`Accept`]: ../actions/enum.Action.html#variant.Accept Accepted(String), /// If the user cancels the prompt input, i.e. a [`Cancel`] event was emitted. this variant will /// contain the rejected buffer, with text and cursor position intact from the moment of /// rejection. /// /// [`Cancel`]: ../actions/enum.Action.html#variant.Cancel Canceled(Buffer), } impl Outcome { /// Returns true if the outcome was accepted. #[must_use] pub fn was_acceoted(&self) -> bool { matches!(self, Outcome::Accepted(_)) } /// Returns accepted text. /// /// # Panics /// /// Panics if the [`Outcome`] is [`Canceled`] /// /// [`Outcome`]: enum.Outcome.html /// [`Canceled`]: enum.Outcome.html#variant.Canceled #[must_use] pub fn unwrap(self) -> String { if let Outcome::Accepted(string) = self { string } else { panic!("called `Outcome::unwrap()` on a `Canceled` value") } } /// Converts this [`Outcome`] into an optional containing the accepted text. /// /// # Return /// * `Some(String)` - If the [`Outcome`] is [`accepted`]. /// * `None` - If the [`Outcome`] is [`canceled`]. /// /// [`Outcome`]: enum.Outcome.html /// [`accepted`]: enum.Outcome.html#variant.Accepted /// [`canceled`]: enum.Outcome.html#variant.Canceled #[must_use] pub fn some(self) -> Option<String> { match self { Outcome::Accepted(string) => Some(string), Outcome::Canceled(_) => None, } } /// Converts this [`Outcome`] into a result containing the accepted text or the canceled buffer. /// /// # Return /// * `Ok(String)` - If the [`Outcome`] is [`accepted`]. /// * `Err(Buffer)` - If the [`Outcome`] is [`canceled`]. /// /// # Errors /// * [`Buffer`] - If the user canceled the input. /// /// [`Outcome`]: enum.Outcome.html /// [`Buffer`]: ../buffer/struct.Buffer.html /// [`accepted`]: enum.Outcome.html#variant.Accepted /// [`canceled`]: enum.Outcome.html#variant.Canceled pub fn ok(self) -> Result<String, Buffer> { match self { Outcome::Accepted(string) => Ok(string), Outcome::Canceled(buffer) => Err(buffer), } } } // TODO: Support crossterm async /// Analogous to `std::io::stdin().read_line()`, however providing all the customization /// configured in the passed parameters. /// /// This method will block until an input is committed by the user. /// /// Calling this method directly can be cumbersome, therefore it is recommended to use the helper /// [`Prompt`] and [`Builder`] to craft the call. /// /// # Return /// * [`Outcome`] - Either [`Accepted`] containing the user input, or [`Canceled`] /// containing the rejected [`buffer`]. /// /// # Errors /// * [`Error`] - If an error occurred while reading the user input. /// /// [`Accepted`]: enum.Outcome.html#variant.Accepted /// [`Builder`]: trait.Builder.html /// [`Canceled`]: enum.Outcome.html#variant.Canceled /// [`Error`]: ../enum.Error.html /// [`Outcome`]: enum.Outcome.html /// [`Prompt`]: struct.Prompt.html /// [`buffer`]: ../buffer/struct.Buffer.html pub fn read_line<O, C, S>( prompt: Option<&str>, buffer: Option<Buffer>, erase_after_read: bool, overrider: Option<&O>, completer: Option<&C>, suggester: Option<&S>, ) -> Result<Outcome, crate::Error> where O: Overrider + ?Sized, C: Completer + ?Sized, S: Suggester + ?Sized, { let mut context = Context::new( erase_after_read, prompt.as_deref(), buffer, completer, suggester, )?; context.print()?; loop { if let crossterm::event::Event::Key(e) = crossterm::event::read()? { match action_for(overrider, e, &context) { Action::Write(c) => context.write(c)?, Action::Delete(scope) => context.delete(scope)?, Action::Move(range, direction) => context.move_cursor(range, direction)?, Action::Complete(range) => context.complete(range)?, Action::Suggest(direction) => context.suggest(direction)?, Action::Noop => continue, Action::Cancel => { if context.is_suggesting() { context.cancel_suggestion()?; } else { return Ok(Outcome::Canceled(context.into())); } } Action::Accept => return Ok(Outcome::Accepted(context.buffer_as_string())), } } } }