92 lines
3.1 KiB
Rust
92 lines
3.1 KiB
Rust
use nu_ansi_term::{Color, Style};
|
|||
|
|
use std::borrow::BorrowMut;
|
||
|
|
|
||
|
|
use crate::{completion, refs};
|
||
|
|
|
||
|
|
pub(crate) struct ReedlineCompleter<SE: brush_core::ShellExtensions> {
|
||
|
|
pub shell: refs::ShellRef<SE>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<SE: brush_core::ShellExtensions> reedline::Completer for ReedlineCompleter<SE> {
|
||
|
|
fn complete(&mut self, line: &str, pos: usize) -> Vec<reedline::Suggestion> {
|
||
|
|
tokio::task::block_in_place(|| {
|
||
|
|
tokio::runtime::Handle::current().block_on(self.complete_async(line, pos))
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<SE: brush_core::ShellExtensions> ReedlineCompleter<SE> {
|
||
|
|
async fn complete_async(&self, line: &str, pos: usize) -> Vec<reedline::Suggestion> {
|
||
|
|
let mut shell_guard = self.shell.lock().await;
|
||
|
|
let shell = shell_guard.borrow_mut().as_mut();
|
||
|
|
let completions = completion::complete_async(shell, line, pos).await;
|
||
|
|
|
||
|
|
// We're done with the shell, so drop it eagerly.
|
||
|
|
drop(shell_guard);
|
||
|
|
|
||
|
|
let insertion_index = completions.insertion_index;
|
||
|
|
let delete_count = completions.delete_count;
|
||
|
|
let options = completions.options;
|
||
|
|
|
||
|
|
completions
|
||
|
|
.candidates
|
||
|
|
.into_iter()
|
||
|
|
.map(|candidate| {
|
||
|
|
Self::to_suggestion(line, candidate, insertion_index, delete_count, &options)
|
||
|
|
})
|
||
|
|
.collect()
|
||
|
|
}
|
||
|
|
|
||
|
|
#[allow(
|
||
|
|
clippy::string_slice,
|
||
|
|
reason = "all indices + counts are expected to be at char boundaries"
|
||
|
|
)]
|
||
|
|
fn to_suggestion(
|
||
|
|
line: &str,
|
||
|
|
mut candidate: String,
|
||
|
|
mut insertion_index: usize,
|
||
|
|
mut delete_count: usize,
|
||
|
|
options: &brush_core::completion::ProcessingOptions,
|
||
|
|
) -> reedline::Suggestion {
|
||
|
|
let mut style = Style::new();
|
||
|
|
|
||
|
|
// Special handling for filename completions.
|
||
|
|
if options.treat_as_filenames {
|
||
|
|
if brush_core::sys::fs::ends_with_path_separator(&candidate) {
|
||
|
|
style = style.fg(Color::Green);
|
||
|
|
}
|
||
|
|
|
||
|
|
if insertion_index + delete_count <= line.len() {
|
||
|
|
let removed = &line[insertion_index..insertion_index + delete_count];
|
||
|
|
if let Some(last_sep_index) = brush_core::sys::fs::rfind_path_separator(removed) {
|
||
|
|
if candidate.starts_with(removed) {
|
||
|
|
candidate = candidate.split_off(last_sep_index + 1);
|
||
|
|
insertion_index += last_sep_index + 1;
|
||
|
|
delete_count -= last_sep_index + 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// See if there's whitespace at the end.
|
||
|
|
let append_whitespace = candidate.ends_with(' ');
|
||
|
|
if append_whitespace {
|
||
|
|
candidate.pop();
|
||
|
|
}
|
||
|
|
|
||
|
|
reedline::Suggestion {
|
||
|
|
value: candidate,
|
||
|
|
description: None,
|
||
|
|
style: Some(style),
|
||
|
|
extra: None,
|
||
|
|
span: reedline::Span {
|
||
|
|
start: insertion_index,
|
||
|
|
end: insertion_index + delete_count,
|
||
|
|
},
|
||
|
|
match_indices: None,
|
||
|
|
display_override: None,
|
||
|
|
append_whitespace,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|