mirror of
https://github.com/nushell/reedline.git
synced 2024-09-11 15:55:50 +03:00
add cwd aware hinter (#647)
* add cwd aware hinter towards https://github.com/nushell/nushell/issues/8883 * handle the case where get_current_dir returns Err * WIP cwd aware hinter - guard CwdAwareHinter with feature flag - remove references to fish from DefaultHinter as fish is cwd aware - add example * document that CwdAwareHinter is only compatible with sqlite history * handle non-sqlite history * handle no sqlite feature in example * fix warnings
This commit is contained in:
parent
adc20cb371
commit
973dbb5f5f
86
examples/cwd_aware_hinter.rs
Normal file
86
examples/cwd_aware_hinter.rs
Normal file
@ -0,0 +1,86 @@
|
||||
// Create a reedline object with in-line hint support.
|
||||
// cargo run --example cwd_aware_hinter
|
||||
//
|
||||
// Fish-style cwd history based hinting
|
||||
// assuming history ["abc", "ade"]
|
||||
// pressing "a" hints to abc.
|
||||
// Up/Down or Ctrl p/n, to select next/previous match
|
||||
|
||||
use std::io;
|
||||
|
||||
fn create_item(cwd: &str, cmd: &str, exit_status: i64) -> reedline::HistoryItem {
|
||||
use std::time::Duration;
|
||||
|
||||
use reedline::HistoryItem;
|
||||
HistoryItem {
|
||||
id: None,
|
||||
start_timestamp: None,
|
||||
command_line: cmd.to_string(),
|
||||
session_id: None,
|
||||
hostname: Some("foohost".to_string()),
|
||||
cwd: Some(cwd.to_string()),
|
||||
duration: Some(Duration::from_millis(1000)),
|
||||
exit_status: Some(exit_status),
|
||||
more_info: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_filled_example_history(home_dir: &str, orig_dir: &str) -> Box<dyn reedline::History> {
|
||||
use reedline::History;
|
||||
#[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))]
|
||||
let mut history = Box::new(reedline::FileBackedHistory::new(100));
|
||||
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
|
||||
let mut history = Box::new(reedline::SqliteBackedHistory::in_memory().unwrap());
|
||||
|
||||
history.save(create_item(orig_dir, "dummy", 0)).unwrap(); // add dummy item so ids start with 1
|
||||
history.save(create_item(orig_dir, "ls /usr", 0)).unwrap();
|
||||
history.save(create_item(orig_dir, "pwd", 0)).unwrap();
|
||||
|
||||
history.save(create_item(home_dir, "cat foo", 0)).unwrap();
|
||||
history.save(create_item(home_dir, "ls bar", 0)).unwrap();
|
||||
history.save(create_item(home_dir, "rm baz", 0)).unwrap();
|
||||
|
||||
history
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use reedline::{CwdAwareHinter, DefaultPrompt, Reedline, Signal};
|
||||
|
||||
let orig_dir = std::env::current_dir().unwrap();
|
||||
#[allow(deprecated)]
|
||||
let home_dir = std::env::home_dir().unwrap();
|
||||
|
||||
let history = create_filled_example_history(
|
||||
&home_dir.to_string_lossy().to_string(),
|
||||
&orig_dir.to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
let mut line_editor = Reedline::create()
|
||||
.with_hinter(Box::new(
|
||||
CwdAwareHinter::default().with_style(Style::new().italic().fg(Color::Yellow)),
|
||||
))
|
||||
.with_history(history);
|
||||
|
||||
let prompt = DefaultPrompt::default();
|
||||
|
||||
let mut iterations = 0;
|
||||
loop {
|
||||
if iterations % 2 == 0 {
|
||||
std::env::set_current_dir(&orig_dir).unwrap();
|
||||
} else {
|
||||
std::env::set_current_dir(&home_dir).unwrap();
|
||||
}
|
||||
let sig = line_editor.read_line(&prompt)?;
|
||||
match sig {
|
||||
Signal::Success(buffer) => {
|
||||
println!("We processed: {buffer}");
|
||||
}
|
||||
Signal::CtrlD | Signal::CtrlC => {
|
||||
println!("\nAborted!");
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
iterations += 1;
|
||||
}
|
||||
}
|
108
src/hinter/cwd_aware.rs
Normal file
108
src/hinter/cwd_aware.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use crate::{
|
||||
history::SearchQuery,
|
||||
result::{ReedlineError, ReedlineErrorVariants::HistoryFeatureUnsupported},
|
||||
Hinter, History,
|
||||
};
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
/// A hinter that uses the completions or the history to show a hint to the user
|
||||
///
|
||||
/// Similar to `fish` autosuggestions
|
||||
pub struct CwdAwareHinter {
|
||||
style: Style,
|
||||
current_hint: String,
|
||||
min_chars: usize,
|
||||
}
|
||||
|
||||
impl Hinter for CwdAwareHinter {
|
||||
fn handle(
|
||||
&mut self,
|
||||
line: &str,
|
||||
#[allow(unused_variables)] pos: usize,
|
||||
history: &dyn History,
|
||||
use_ansi_coloring: bool,
|
||||
) -> String {
|
||||
self.current_hint = if line.chars().count() >= self.min_chars {
|
||||
history
|
||||
.search(SearchQuery::last_with_prefix_and_cwd(
|
||||
line.to_string(),
|
||||
history.session(),
|
||||
))
|
||||
.or_else(|err| {
|
||||
if let ReedlineError(HistoryFeatureUnsupported { .. }) = err {
|
||||
history.search(SearchQuery::last_with_prefix(
|
||||
line.to_string(),
|
||||
history.session(),
|
||||
))
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
})
|
||||
.expect("todo: error handling")
|
||||
.get(0)
|
||||
.map_or_else(String::new, |entry| {
|
||||
entry
|
||||
.command_line
|
||||
.get(line.len()..)
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
})
|
||||
} 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 {
|
||||
let mut reached_content = false;
|
||||
let result: String = self
|
||||
.current_hint
|
||||
.chars()
|
||||
.take_while(|c| match (c.is_whitespace(), reached_content) {
|
||||
(true, true) => false,
|
||||
(true, false) => true,
|
||||
(false, true) => true,
|
||||
(false, false) => {
|
||||
reached_content = true;
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CwdAwareHinter {
|
||||
fn default() -> Self {
|
||||
CwdAwareHinter {
|
||||
style: Style::new().fg(Color::LightGray),
|
||||
current_hint: String::new(),
|
||||
min_chars: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CwdAwareHinter {
|
||||
/// A builder that sets the style applied to the hint as part of the buffer
|
||||
#[must_use]
|
||||
pub fn with_style(mut self, style: Style) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// A builder that sets the number of characters that have to be present to enable history hints
|
||||
#[must_use]
|
||||
pub fn with_min_chars(mut self, min_chars: usize) -> Self {
|
||||
self.min_chars = min_chars;
|
||||
self
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
use crate::{history::SearchQuery, Hinter, History};
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
/// A hinter that use the completions or the history to show a hint to the user
|
||||
///
|
||||
/// Similar to `fish` autosuggestins
|
||||
/// A hinter that uses the completions or the history to show a hint to the user
|
||||
pub struct DefaultHinter {
|
||||
style: Style,
|
||||
current_hint: String,
|
||||
|
@ -1,4 +1,6 @@
|
||||
mod cwd_aware;
|
||||
mod default;
|
||||
pub use cwd_aware::CwdAwareHinter;
|
||||
pub use default::DefaultHinter;
|
||||
|
||||
use crate::History;
|
||||
|
@ -66,6 +66,18 @@ impl SearchFilter {
|
||||
s
|
||||
}
|
||||
|
||||
/// Create a search filter with a [`CommandLineSearch`] and `cwd`
|
||||
pub fn from_text_search_cwd(
|
||||
cwd: String,
|
||||
cmd: CommandLineSearch,
|
||||
session: Option<HistorySessionId>,
|
||||
) -> SearchFilter {
|
||||
let mut s = SearchFilter::anything(session);
|
||||
s.command_line = Some(cmd);
|
||||
s.cwd_exact = Some(cwd);
|
||||
s
|
||||
}
|
||||
|
||||
/// anything within this session
|
||||
pub fn anything(session: Option<HistorySessionId>) -> SearchFilter {
|
||||
SearchFilter {
|
||||
@ -134,6 +146,26 @@ impl SearchQuery {
|
||||
))
|
||||
}
|
||||
|
||||
/// Get the most recent entry starting with the `prefix` and `cwd` same as the current cwd
|
||||
pub fn last_with_prefix_and_cwd(
|
||||
prefix: String,
|
||||
session: Option<HistorySessionId>,
|
||||
) -> SearchQuery {
|
||||
let cwd = std::env::current_dir();
|
||||
if let Ok(cwd) = cwd {
|
||||
SearchQuery::last_with_search(SearchFilter::from_text_search_cwd(
|
||||
cwd.to_string_lossy().to_string(),
|
||||
CommandLineSearch::Prefix(prefix),
|
||||
session,
|
||||
))
|
||||
} else {
|
||||
SearchQuery::last_with_search(SearchFilter::from_text_search(
|
||||
CommandLineSearch::Prefix(prefix),
|
||||
session,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Query to get all entries in the given [`SearchDirection`]
|
||||
pub fn everything(
|
||||
direction: SearchDirection,
|
||||
|
@ -269,6 +269,7 @@ mod completion;
|
||||
pub use completion::{Completer, DefaultCompleter, Span, Suggestion};
|
||||
|
||||
mod hinter;
|
||||
pub use hinter::CwdAwareHinter;
|
||||
pub use hinter::{DefaultHinter, Hinter};
|
||||
|
||||
mod validator;
|
||||
|
Loading…
Reference in New Issue
Block a user