From 63ccb4b7b368b845dce9c354118566d4d53fcc7e Mon Sep 17 00:00:00 2001 From: cactus-man <64769435+Cactus-man@users.noreply.github.com> Date: Thu, 1 Feb 2024 22:08:45 +0100 Subject: [PATCH 01/23] Configure gitignore for vsc --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d909355c..a41bf631 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ tarpaulin-report.html *.rsproj *.rsproj.user *.sln + +# Visual Studio Code +.vscode \ No newline at end of file From 03faf572f1c296e06a6ee96d35bb0b98553d8061 Mon Sep 17 00:00:00 2001 From: cactus-man <64769435+Cactus-man@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:43:38 +0100 Subject: [PATCH 02/23] Use actual builder to construct `Reedline`, deprecate builder methods on type and fix examples --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/basic.rs | 2 +- examples/completions.rs | 13 ++- examples/custom_prompt.rs | 2 +- examples/demo.rs | 50 ++++---- examples/highlighter.rs | 5 +- examples/hinter.rs | 6 +- examples/history.rs | 8 +- examples/ide_completions.rs | 13 ++- examples/transient_prompt.rs | 23 ++-- examples/validator.rs | 6 +- src/engine.rs | 89 +++++++------- src/engine/builder.rs | 219 +++++++++++++++++++++++++++++++++++ 14 files changed, 328 insertions(+), 112 deletions(-) create mode 100644 src/engine/builder.rs diff --git a/Cargo.lock b/Cargo.lock index 8e21b60c..4eea05da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,7 +717,7 @@ dependencies = [ [[package]] name = "reedline" -version = "0.28.0" +version = "0.29.0" dependencies = [ "arboard", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 1b392cad..3da13cdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" name = "reedline" repository = "https://github.com/nushell/reedline" rust-version = "1.62.1" -version = "0.28.0" +version = "0.29.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] diff --git a/examples/basic.rs b/examples/basic.rs index 2c04b75b..2a3a7ee0 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -8,7 +8,7 @@ use std::io; fn main() -> io::Result<()> { // Create a new Reedline engine with a local History that is not synchronized to a file. - let mut line_editor = Reedline::create(); + let mut line_editor = Reedline::new(); let prompt = DefaultPrompt::default(); loop { diff --git a/examples/completions.rs b/examples/completions.rs index 164b5c30..4e372c95 100644 --- a/examples/completions.rs +++ b/examples/completions.rs @@ -57,7 +57,7 @@ fn main() -> io::Result<()> { "abadarabc".into(), ]; - let completer = Box::new(DefaultCompleter::new_with_wordlen(commands, 2)); + let completer = DefaultCompleter::new_with_wordlen(commands, 2); // Use the interactive menu to select options from the completer let columnar_menu = ColumnarMenu::default() @@ -71,12 +71,13 @@ fn main() -> io::Result<()> { let mut keybindings = default_emacs_keybindings(); add_menu_keybindings(&mut keybindings); - let edit_mode = Box::new(Emacs::new(keybindings)); + let edit_mode = Emacs::new(keybindings); - let mut line_editor = Reedline::create() - .with_completer(completer) - .with_menu(ReedlineMenu::EngineCompleter(completion_menu)) - .with_edit_mode(edit_mode); + let mut line_editor = Reedline::builder() + .with_completion(completer) + .add_menu(ReedlineMenu::EngineCompleter(completion_menu)) + .with_edit_mode(edit_mode) + .build(); let prompt = DefaultPrompt::default(); diff --git a/examples/custom_prompt.rs b/examples/custom_prompt.rs index fc9b1034..c4949598 100644 --- a/examples/custom_prompt.rs +++ b/examples/custom_prompt.rs @@ -56,7 +56,7 @@ impl Prompt for CustomPrompt { fn main() -> io::Result<()> { println!("Custom prompt demo:\nAbort with Ctrl-C or Ctrl-D"); - let mut line_editor = Reedline::create(); + let mut line_editor = Reedline::new(); let prompt = CustomPrompt(Cell::new(0), "Custom Prompt"); diff --git a/examples/demo.rs b/examples/demo.rs index ad1f808a..a5d1c713 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -9,8 +9,8 @@ use { reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, ColumnarMenu, DefaultCompleter, DefaultHinter, DefaultPrompt, DefaultValidator, - EditCommand, EditMode, Emacs, ExampleHighlighter, Keybindings, ListMenu, Reedline, - ReedlineEvent, ReedlineMenu, Signal, Vi, + EditCommand, Emacs, ExampleHighlighter, Keybindings, ListMenu, Reedline, ReedlineEvent, + ReedlineMenu, Signal, Vi, }, }; @@ -42,7 +42,7 @@ fn main() -> reedline::Result<()> { .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?, ); #[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))] - let history = Box::new(FileBackedHistory::with_file(50, "history.txt".into())?); + let history = FileBackedHistory::with_file(50, "history.txt".into())?; let commands = vec![ "test".into(), "clear".into(), @@ -71,7 +71,7 @@ fn main() -> reedline::Result<()> { "こんにちは世界".into(), "こんばんは世界".into(), ]; - let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2)); + let completer = DefaultCompleter::new_with_wordlen(commands.clone(), 2); let cursor_config = CursorConfig { vi_insert: Some(SetCursorStyle::BlinkingBar), @@ -79,33 +79,30 @@ fn main() -> reedline::Result<()> { emacs: None, }; - let mut line_editor = Reedline::create() - .with_history_session_id(history_session_id) + let mut builder = Reedline::builder() + .with_history_session_id(history_session_id.unwrap()) .with_history(history) - .with_history_exclusion_prefix(Some(" ".to_string())) - .with_completer(completer) + .with_history_exclusion_prefix(" ".to_string()) + .with_completion(completer) .with_quick_completions(true) .with_partial_completions(true) .with_cursor_config(cursor_config) - .use_bracketed_paste(true) - .use_kitty_keyboard_enhancement(true) - .with_highlighter(Box::new(ExampleHighlighter::new(commands))) - .with_hinter(Box::new( - DefaultHinter::default().with_style(Style::new().fg(Color::DarkGray)), - )) - .with_validator(Box::new(DefaultValidator)) + .with_bracketed_paste(true) + .with_kitty_keyboard_enhancement(true) + .with_highlighter(ExampleHighlighter::new(commands)) + .with_hints(DefaultHinter::default().with_style(Style::new().fg(Color::DarkGray))) + .with_validator(DefaultValidator) .with_ansi_colors(true); // Adding default menus for the compiled reedline - line_editor = line_editor - .with_menu(ReedlineMenu::EngineCompleter(Box::new( + builder = builder.add_menus(vec![ + ReedlineMenu::EngineCompleter(Box::new( ColumnarMenu::default().with_name("completion_menu"), - ))) - .with_menu(ReedlineMenu::HistoryMenu(Box::new( - ListMenu::default().with_name("history_menu"), - ))); + )), + ReedlineMenu::HistoryMenu(Box::new(ListMenu::default().with_name("history_menu"))), + ]); - let edit_mode: Box = if vi_mode { + if vi_mode { let mut normal_keybindings = default_vi_normal_keybindings(); let mut insert_keybindings = default_vi_insert_keybindings(); @@ -114,22 +111,19 @@ fn main() -> reedline::Result<()> { add_newline_keybinding(&mut insert_keybindings); - Box::new(Vi::new(insert_keybindings, normal_keybindings)) + builder = builder.with_edit_mode(Vi::new(insert_keybindings, normal_keybindings)); } else { let mut keybindings = default_emacs_keybindings(); add_menu_keybindings(&mut keybindings); add_newline_keybinding(&mut keybindings); - - Box::new(Emacs::new(keybindings)) + builder = builder.with_edit_mode(Emacs::new(keybindings)); }; - line_editor = line_editor.with_edit_mode(edit_mode); - // Adding vi as text editor let temp_file = temp_dir().join("temp_file.nu"); let mut command = Command::new("vi"); command.arg(&temp_file); - line_editor = line_editor.with_buffer_editor(command, temp_file); + let mut line_editor = builder.with_buffer_editor(command, temp_file).build(); let prompt = DefaultPrompt::default(); diff --git a/examples/highlighter.rs b/examples/highlighter.rs index 3edeb974..6f79b3b3 100644 --- a/examples/highlighter.rs +++ b/examples/highlighter.rs @@ -12,8 +12,9 @@ fn main() -> io::Result<()> { "hello world reedline".into(), "this is the reedline crate".into(), ]; - let mut line_editor = - Reedline::create().with_highlighter(Box::new(ExampleHighlighter::new(commands))); + let mut line_editor = Reedline::builder() + .with_highlighter(ExampleHighlighter::new(commands)) + .build(); let prompt = DefaultPrompt::default(); loop { diff --git a/examples/hinter.rs b/examples/hinter.rs index 9ed4ba2d..b0ec58ec 100644 --- a/examples/hinter.rs +++ b/examples/hinter.rs @@ -11,9 +11,9 @@ use reedline::{DefaultHinter, DefaultPrompt, Reedline, Signal}; use std::io; fn main() -> io::Result<()> { - let mut line_editor = Reedline::create().with_hinter(Box::new( - DefaultHinter::default().with_style(Style::new().italic().fg(Color::LightGray)), - )); + let mut line_editor = Reedline::builder() + .with_hints(DefaultHinter::default().with_style(Style::new().italic().fg(Color::LightGray))) + .build(); let prompt = DefaultPrompt::default(); loop { diff --git a/examples/history.rs b/examples/history.rs index c709bce9..d20f1fe7 100644 --- a/examples/history.rs +++ b/examples/history.rs @@ -10,12 +10,10 @@ use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; use std::io; fn main() -> io::Result<()> { - let history = Box::new( - FileBackedHistory::with_file(5, "history.txt".into()) - .expect("Error configuring history with file"), - ); + let history = FileBackedHistory::with_file(5, "history.txt".into()) + .expect("Error configuring history with file"); - let mut line_editor = Reedline::create().with_history(history); + let mut line_editor = Reedline::builder().with_history(history).build(); let prompt = DefaultPrompt::default(); loop { diff --git a/examples/ide_completions.rs b/examples/ide_completions.rs index f6e69b7b..3bfd445e 100644 --- a/examples/ide_completions.rs +++ b/examples/ide_completions.rs @@ -82,7 +82,7 @@ fn main() -> io::Result<()> { "abadarabc".into(), ]; - let completer = Box::new(DefaultCompleter::new_with_wordlen(commands, 2)); + let completer = DefaultCompleter::new_with_wordlen(commands, 2); // Use the interactive menu to select options from the completer let mut ide_menu = IdeMenu::default() @@ -107,12 +107,13 @@ fn main() -> io::Result<()> { let mut keybindings = default_emacs_keybindings(); add_menu_keybindings(&mut keybindings); - let edit_mode = Box::new(Emacs::new(keybindings)); + let edit_mode = Emacs::new(keybindings); - let mut line_editor = Reedline::create() - .with_completer(completer) - .with_menu(ReedlineMenu::EngineCompleter(completion_menu)) - .with_edit_mode(edit_mode); + let mut line_editor = Reedline::builder() + .with_completion(completer) + .add_menu(ReedlineMenu::EngineCompleter(completion_menu)) + .with_edit_mode(edit_mode) + .build(); let prompt = DefaultPrompt::default(); diff --git a/examples/transient_prompt.rs b/examples/transient_prompt.rs index d3aaa390..6032496b 100644 --- a/examples/transient_prompt.rs +++ b/examples/transient_prompt.rs @@ -89,27 +89,26 @@ fn main() -> io::Result<()> { "hello world reedline".into(), "this is the reedline crate".into(), ]; - let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2)); + let completer = DefaultCompleter::new_with_wordlen(commands.clone(), 2); // Use the interactive menu to select options from the completer let completion_menu = Box::new(ColumnarMenu::default().with_name("completion_menu")); let mut keybindings = default_emacs_keybindings(); add_menu_keybindings(&mut keybindings); - let edit_mode = Box::new(Emacs::new(keybindings)); + let edit_mode = Emacs::new(keybindings); - let mut line_editor = Reedline::create() - .with_hinter(Box::new( - DefaultHinter::default().with_style(Style::new().fg(Color::LightGray)), - )) - .with_completer(completer) - .with_menu(ReedlineMenu::EngineCompleter(completion_menu)) + let mut line_editor = Reedline::builder() + .with_hints(DefaultHinter::default().with_style(Style::new().fg(Color::LightGray))) + .with_completion(completer) + .add_menu(ReedlineMenu::EngineCompleter(completion_menu)) .with_edit_mode(edit_mode) - .with_highlighter(Box::new(ExampleHighlighter::new(commands))) - .with_validator(Box::new(CustomValidator {})) + .with_highlighter(ExampleHighlighter::new(commands)) + .with_validator(CustomValidator {}) .with_ansi_colors(true) - .with_history_exclusion_prefix(Some(String::from(" "))) - .with_transient_prompt(Box::new(TransientPrompt {})); + .with_history_exclusion_prefix(String::from(" ")) + .with_transient_prompt(TransientPrompt {}) + .build(); #[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))] { line_editor = line_editor.with_history(Box::new(SqliteBackedHistory::in_memory().unwrap())); diff --git a/examples/validator.rs b/examples/validator.rs index 2e7ca4f9..65374111 100644 --- a/examples/validator.rs +++ b/examples/validator.rs @@ -21,8 +21,10 @@ impl Validator for CustomValidator { } fn main() -> io::Result<()> { - println!("Input \"complete\" followed by [Enter], will accept the input line (Signal::Succeed will be called)\nPressing [Enter] will in other cases give you a multi-line prompt.\nAbort with Ctrl-C or Ctrl-D"); - let mut line_editor = Reedline::create().with_validator(Box::new(CustomValidator)); + println!("Input \"complete\" followed by [Enter], will accept the input line (Signal::Succeed will be called)"); + println!("Pressing [Enter] will in other cases give you a multi-line prompt."); + println!("Abort with Ctrl-C or Ctrl-D"); + let mut line_editor = Reedline::builder().with_validator(CustomValidator).build(); let prompt = DefaultPrompt::default(); diff --git a/src/engine.rs b/src/engine.rs index e7bcceb7..9744420a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,6 +3,9 @@ use std::path::PathBuf; use itertools::Itertools; use nu_ansi_term::{Color, Style}; +mod builder; +pub use builder::ReedlineBuilder; + use crate::{enums::ReedlineRawEvent, CursorConfig}; #[cfg(feature = "bashisms")] use crate::{ @@ -178,55 +181,32 @@ impl Drop for Reedline { } } +impl Default for Reedline { + fn default() -> Self { + Self::new() + } +} + impl Reedline { const FILTERED_ITEM_ID: HistoryItemId = HistoryItemId(i64::MAX); /// Create a new [`Reedline`] engine with a local [`History`] that is not synchronized to a file. #[must_use] + #[deprecated] pub fn create() -> Self { - let history = Box::::default(); - let painter = Painter::new(std::io::BufWriter::new(std::io::stderr())); - let buffer_highlighter = Box::::default(); - let visual_selection_style = Style::new().on(Color::LightGray); - let completer = Box::::default(); - let hinter = None; - let validator = None; - let edit_mode = Box::::default(); - let hist_session_id = None; - - Reedline { - editor: Editor::default(), - history, - history_cursor: HistoryCursor::new( - HistoryNavigationQuery::Normal(LineBuffer::default()), - hist_session_id, - ), - history_session_id: hist_session_id, - history_last_run_id: None, - history_exclusion_prefix: None, - history_excluded_item: None, - history_cursor_on_excluded: false, - input_mode: InputMode::Regular, - painter, - transient_prompt: None, - edit_mode, - completer, - quick_completions: false, - partial_completions: false, - highlighter: buffer_highlighter, - visual_selection_style, - hinter, - hide_hints: false, - validator, - use_ansi_coloring: true, - menus: Vec::new(), - buffer_editor: None, - cursor_shapes: None, - bracketed_paste: BracketedPasteGuard::default(), - kitty_protocol: KittyProtocolGuard::default(), - #[cfg(feature = "external_printer")] - external_printer: None, - } + Self::new() + } + + #[must_use] + /// Constructs an instance of `Reedline` with sensible defaults for most use cases + pub fn new() -> Self { + Self::builder().build() + } + + #[must_use] + /// Construct a builder for creating a `Reedline` + pub const fn builder() -> ReedlineBuilder { + ReedlineBuilder::new() } /// Get a new history session id based on the current time and the first commit datetime of reedline @@ -248,6 +228,7 @@ impl Reedline { /// /// At this point most terminals should support it or ignore the setting of the necessary /// flags. For full compatibility, keep it disabled. + #[deprecated] pub fn use_bracketed_paste(mut self, enable: bool) -> Self { self.bracketed_paste.set(enable); self @@ -261,6 +242,7 @@ impl Reedline { /// `Reedline` will perform this check internally /// /// Read more: + #[deprecated] pub fn use_kitty_keyboard_enhancement(mut self, enable: bool) -> Self { self.kitty_protocol.set(enable); self @@ -296,6 +278,7 @@ impl Reedline { /// )); /// ``` #[must_use] + #[deprecated] pub fn with_hinter(mut self, hinter: Box) -> Self { self.hinter = Some(hinter); self @@ -303,6 +286,7 @@ impl Reedline { /// Remove current [`Hinter`] #[must_use] + #[deprecated] pub fn disable_hints(mut self) -> Self { self.hinter = None; self @@ -326,6 +310,7 @@ impl Reedline { /// let mut line_editor = Reedline::create().with_completer(completer); /// ``` #[must_use] + #[deprecated] pub fn with_completer(mut self, completer: Box) -> Self { self.completer = completer; self @@ -334,6 +319,7 @@ impl Reedline { /// Turn on quick completions. These completions will auto-select if the completer /// ever narrows down to a single entry. #[must_use] + #[deprecated] pub fn with_quick_completions(mut self, quick_completions: bool) -> Self { self.quick_completions = quick_completions; self @@ -342,6 +328,7 @@ impl Reedline { /// Turn on partial completions. These completions will fill the buffer with the /// smallest common string from all the options #[must_use] + #[deprecated] pub fn with_partial_completions(mut self, partial_completions: bool) -> Self { self.partial_completions = partial_completions; self @@ -350,6 +337,7 @@ impl Reedline { /// A builder which enables or disables the use of ansi coloring in the prompt /// and in the command line syntax highlighting. #[must_use] + #[deprecated] pub fn with_ansi_colors(mut self, use_ansi_coloring: bool) -> Self { self.use_ansi_coloring = use_ansi_coloring; self @@ -372,6 +360,7 @@ impl Reedline { /// Reedline::create().with_highlighter(Box::new(ExampleHighlighter::new(commands))); /// ``` #[must_use] + #[deprecated] pub fn with_highlighter(mut self, highlighter: Box) -> Self { self.highlighter = highlighter; self @@ -379,6 +368,7 @@ impl Reedline { /// A builder that configures the style used for visual selection #[must_use] + #[deprecated] pub fn with_visual_selection_style(mut self, style: Style) -> Self { self.visual_selection_style = style; self @@ -399,6 +389,7 @@ impl Reedline { /// .with_history(history); /// ``` #[must_use] + #[deprecated] pub fn with_history(mut self, history: Box) -> Self { self.history = history; self @@ -420,6 +411,7 @@ impl Reedline { /// .with_history_exclusion_prefix(Some(" ".into())); /// ``` #[must_use] + #[deprecated] pub fn with_history_exclusion_prefix(mut self, ignore_prefix: Option) -> Self { self.history_exclusion_prefix = ignore_prefix; self @@ -436,6 +428,7 @@ impl Reedline { /// Reedline::create().with_validator(Box::new(DefaultValidator)); /// ``` #[must_use] + #[deprecated] pub fn with_validator(mut self, validator: Box) -> Self { self.validator = Some(validator); self @@ -462,6 +455,7 @@ impl Reedline { /// Reedline::create().with_buffer_editor(command, temp_file); /// ``` #[must_use] + #[deprecated] pub fn with_buffer_editor(mut self, editor: Command, temp_file: PathBuf) -> Self { let mut editor = editor; if !editor.get_args().contains(&temp_file.as_os_str()) { @@ -476,6 +470,7 @@ impl Reedline { /// Remove the current [`Validator`] #[must_use] + #[deprecated] pub fn disable_validator(mut self) -> Self { self.validator = None; self @@ -483,6 +478,7 @@ impl Reedline { /// Set a different prompt to be used after submitting each line #[must_use] + #[deprecated] pub fn with_transient_prompt(mut self, transient_prompt: Box) -> Self { self.transient_prompt = Some(transient_prompt); self @@ -490,6 +486,7 @@ impl Reedline { /// A builder which configures the edit mode for your instance of the Reedline engine #[must_use] + #[deprecated] pub fn with_edit_mode(mut self, edit_mode: Box) -> Self { self.edit_mode = edit_mode; self @@ -497,6 +494,7 @@ impl Reedline { /// A builder that appends a menu to the engine #[must_use] + #[deprecated] pub fn with_menu(mut self, menu: ReedlineMenu) -> Self { self.menus.push(menu); self @@ -504,6 +502,7 @@ impl Reedline { /// A builder that clears the list of menus added to the engine #[must_use] + #[deprecated] pub fn clear_menus(mut self) -> Self { self.menus = Vec::new(); self @@ -511,6 +510,7 @@ impl Reedline { /// A builder that adds the history item id #[must_use] + #[deprecated] pub fn with_history_session_id(mut self, session: Option) -> Self { self.history_session_id = session; self @@ -519,6 +519,7 @@ impl Reedline { /// A builder that enables reedline changing the cursor shape based on the current edit mode. /// The current implementation sets the cursor shape when drawing the prompt. /// Do not use this if the cursor shape is set elsewhere, e.g. in the terminal settings or by ansi escape sequences. + #[deprecated] pub fn with_cursor_config(mut self, cursor_shapes: CursorConfig) -> Self { self.cursor_shapes = Some(cursor_shapes); self @@ -1803,5 +1804,5 @@ impl Reedline { #[test] fn thread_safe() { fn f(_: S) {} - f(Reedline::create()); + f(Reedline::new()); } diff --git a/src/engine/builder.rs b/src/engine/builder.rs new file mode 100644 index 00000000..3ba1e6a1 --- /dev/null +++ b/src/engine/builder.rs @@ -0,0 +1,219 @@ +use nu_ansi_term::Style; + +use super::*; + +pub struct ReedlineBuilder { + history: Option>, + edit_mode: Option>, + history_exclusion_prefix: Option, + validator: Option>, + completer: Option>, + quick_completions: bool, + partial_completions: bool, + highlighter: Option>, + buffer_editor: Option, + visual_selection_style: Option