diff --git a/src/engine.rs b/src/engine.rs index bdb04373..8265145e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -143,6 +143,9 @@ pub struct Reedline { hinter: Option>, hide_hints: bool, + // Auto-show completion menu as you type (IDE/fish style) + auto_complete_menu: bool, + // Use ansi coloring or not use_ansi_coloring: bool, @@ -233,6 +236,7 @@ impl Reedline { visual_selection_style, hinter, hide_hints: false, + auto_complete_menu: false, validator, use_ansi_coloring: true, cwd: None, @@ -326,6 +330,16 @@ impl Reedline { self } + /// Enable auto-showing completion menu as you type (IDE/fish style) + /// + /// When enabled, the completion menu named "completion_menu" will automatically + /// appear as you type, without needing to press Tab. + #[must_use] + pub fn with_auto_complete_menu(mut self, enable: bool) -> Self { + self.auto_complete_menu = enable; + self + } + /// A builder to configure the tab completion /// # Example /// ```rust @@ -1209,11 +1223,65 @@ impl Reedline { } } } - if self.editor.line_buffer().get_buffer().is_empty() { + // Check if we should keep the menu active + let buffer = self.editor.line_buffer().get_buffer(); + let should_stay_active = if self.auto_complete_menu { + // Use same logic as auto-activate: need 1+ char after last space + if let Some(last_word_start) = buffer.rfind(' ') { + !buffer[last_word_start + 1..].is_empty() + } else { + // First word - keep menu if user activated it manually + !buffer.is_empty() + } + } else { + // No auto-complete mode, keep active unless empty + !buffer.is_empty() + }; + + if !should_stay_active { menu.menu_event(MenuEvent::Deactivate); } else { menu.menu_event(MenuEvent::Edit(self.quick_completions)); } + } else if self.auto_complete_menu { + // Auto-activate completion menu (IDE style) + // - Flags: show immediately on "-" or "--" + // - Files/args: show after 1+ chars typed (not just space) + // - Commands (first word): don't auto-complete + let buffer = self.editor.line_buffer().get_buffer(); + let should_show = if let Some(last_word_start) = buffer.rfind(' ') { + let last_word = &buffer[last_word_start + 1..]; + // Show if: flags (starts with -) OR has 1+ char typed + !last_word.is_empty() + } else { + // First word - don't auto-complete commands + false + }; + + if let Some(menu) = self.menus.iter_mut().find(|m| m.name() == "completion_menu") { + if should_show { + if !menu.is_active() { + // Only activate if not already active + menu.menu_event(MenuEvent::Activate(self.quick_completions)); + menu.update_values( + &mut self.editor, + self.completer.as_mut(), + self.history.as_ref(), + ); + } else { + // Menu already active, just send edit event to update + menu.menu_event(MenuEvent::Edit(self.quick_completions)); + menu.update_values( + &mut self.editor, + self.completer.as_mut(), + self.history.as_ref(), + ); + } + } else if menu.is_active() { + // Deactivate when no longer typing a flag + menu.menu_event(MenuEvent::Deactivate); + } + } } Ok(EventStatus::Handled) } diff --git a/src/hinter/completion.rs b/src/hinter/completion.rs new file mode 100644 index 00000000..ca6a4cdd --- /dev/null +++ b/src/hinter/completion.rs @@ -0,0 +1,98 @@ +use crate::{hinter::get_first_token, Completer, Hinter, History}; +use nu_ansi_term::{Color, Style}; +use std::sync::{Arc, Mutex}; + +/// A hinter that uses completions (not history) to show inline suggestions +/// +/// This provides fish-style autosuggestions based on what the completer returns, +/// showing the first completion result as gray text that can be accepted with → +pub struct CompletionHinter { + completer: Arc>, + style: Style, + current_hint: String, + min_chars: usize, +} + +impl Hinter for CompletionHinter { + fn handle( + &mut self, + line: &str, + pos: usize, + _history: &dyn History, + use_ansi_coloring: bool, + _cwd: &str, + ) -> String { + self.current_hint = if line.chars().count() >= self.min_chars && pos == line.len() { + // Only show hints when cursor is at end of line + if let Ok(mut completer) = self.completer.lock() { + let suggestions = completer.complete(line, pos); + if let Some(first) = suggestions.first() { + // The suggestion replaces line[span.start..span.end] with `value` + // We want to show what extends beyond what the user typed + let span_end = first.span.end.min(line.len()); + let span_start = first.span.start.min(span_end); + let typed_portion = &line[span_start..span_end]; + + // If the completion value starts with what's being replaced, + // show the suffix (the new part) + if let Some(suffix) = first.value.strip_prefix(typed_portion) { + suffix.to_string() + } else { + // Fuzzy match - just show if value is longer than typed + if first.value.len() > typed_portion.len() { + first.value.clone() + } else { + String::new() + } + } + } else { + String::new() + } + } else { + String::new() + } + } else { + String::new() + }; + + if use_ansi_coloring && !self.current_hint.is_empty() { + self.style.paint(&self.current_hint).to_string() + } else { + self.current_hint.clone() + } + } + + fn complete_hint(&self) -> String { + self.current_hint.clone() + } + + fn next_hint_token(&self) -> String { + get_first_token(&self.current_hint) + } +} + +impl CompletionHinter { + /// Create a new CompletionHinter with the given completer + pub fn new(completer: Arc>) -> Self { + CompletionHinter { + completer, + style: Style::new().fg(Color::DarkGray), + current_hint: String::new(), + min_chars: 1, + } + } + + /// A builder that sets the style applied to the hint + #[must_use] + pub fn with_style(mut self, style: Style) -> Self { + self.style = style; + self + } + + /// A builder that sets minimum characters before showing hints + #[must_use] + pub fn with_min_chars(mut self, min_chars: usize) -> Self { + self.min_chars = min_chars; + self + } +} diff --git a/src/hinter/mod.rs b/src/hinter/mod.rs index fcd29b67..a5fe7f18 100644 --- a/src/hinter/mod.rs +++ b/src/hinter/mod.rs @@ -1,5 +1,7 @@ +mod completion; mod cwd_aware; mod default; +pub use completion::CompletionHinter; pub use cwd_aware::CwdAwareHinter; pub use default::DefaultHinter; diff --git a/src/lib.rs b/src/lib.rs index d202a220..174da365 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -272,6 +272,7 @@ mod completion; pub use completion::{Completer, DefaultCompleter, Span, Suggestion}; mod hinter; +pub use hinter::CompletionHinter; pub use hinter::CwdAwareHinter; pub use hinter::{DefaultHinter, Hinter};