-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add cwd aware hinter towards nushell/nushell#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
- Loading branch information
Showing
6 changed files
with
230 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters