diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f84bb16e9..54fea453e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -723,20 +723,26 @@ pub fn command_mode(cx: &mut Context) { // simple heuristic: if there's no space, complete command. // if there's a space, file completion kicks in. We should specialize by command later. if parts.len() <= 1 { + use std::{borrow::Cow, ops::Range}; + let end = 0..; COMMAND_LIST .iter() .filter(|command| command.contains(input)) - .map(|command| std::borrow::Cow::Borrowed(*command)) + .map(|command| (end.clone(), Cow::Borrowed(*command))) .collect() } else { let part = parts.last().unwrap(); ui::completers::filename(part) + .into_iter() + .map(|(range, file)| { + // offset ranges to input + let offset = input.len() - part.len(); + let range = (range.start + offset)..; + (range, file) + }) + .collect() // TODO - // completion needs to be more advanced: need to return starting index for replace - // for example, "src/" completion application.rs needs to insert after /, but "hx" - // completion helix-core needs to replace the text. - // // additionally, completion items could have a info section that would get // displayed in a popup above the prompt when items are tabbed over } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index f7ba59cd4..b35cba60a 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -137,9 +137,9 @@ pub fn buffer_picker(views: &[View], current: usize) -> Picker<(Option, } pub mod completers { - use std::borrow::Cow; + use std::{borrow::Cow, ops::RangeFrom}; // TODO: we could return an iter/lazy thing so it can fetch as many as it needs. - pub fn filename(input: &str) -> Vec> { + pub fn filename(input: &str) -> Vec<(RangeFrom, Cow<'static, str>)> { // Rust's filename handling is really annoying. use ignore::WalkBuilder; @@ -163,6 +163,8 @@ pub mod completers { (path, file_name) }; + let end = (input.len()..); + let mut files: Vec<_> = WalkBuilder::new(dir.clone()) .max_depth(Some(1)) .build() @@ -178,10 +180,11 @@ pub mod completers { if is_dir { path.push(""); } - Cow::from(path.to_str().unwrap().to_string()) + let path = path.to_str().unwrap().to_string(); + (end.clone(), Cow::from(path)) }) }) // TODO: unwrap or skip - .filter(|path| !path.is_empty()) // TODO + .filter(|(_, path)| !path.is_empty()) // TODO .collect(); // if empty, return a list of dirs and files in current dir @@ -195,15 +198,20 @@ pub mod completers { // inefficient, but we need to calculate the scores, filter out None, then sort. let mut matches: Vec<_> = files .into_iter() - .filter_map(|file| { + .filter_map(|(range, file)| { matcher .fuzzy_match(&file, &file_name) .map(|score| (file, score)) }) .collect(); + let range = ((input.len() - file_name.len())..); + matches.sort_unstable_by_key(|(_file, score)| Reverse(*score)); - files = matches.into_iter().map(|(file, _)| file).collect(); + files = matches + .into_iter() + .map(|(file, _)| (range.clone(), file)) + .collect(); // TODO: complete to longest common match } diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 19885c024..35890b4c1 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -3,16 +3,15 @@ use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use helix_core::Position; use helix_view::Editor; use helix_view::Theme; -use std::borrow::Cow; -use std::string::String; +use std::{borrow::Cow, ops::RangeFrom}; pub struct Prompt { prompt: String, pub line: String, cursor: usize, - completion: Vec>, + completion: Vec<(RangeFrom, Cow<'static, str>)>, completion_selection_index: Option, - completion_fn: Box Vec>>, + completion_fn: Box Vec<(RangeFrom, Cow<'static, str>)>>, callback_fn: Box, } @@ -29,7 +28,7 @@ pub enum PromptEvent { impl Prompt { pub fn new( prompt: String, - mut completion_fn: impl FnMut(&str) -> Vec> + 'static, + mut completion_fn: impl FnMut(&str) -> Vec<(RangeFrom, Cow<'static, str>)> + 'static, callback_fn: impl FnMut(&mut Editor, &str, PromptEvent) + 'static, ) -> Prompt { Prompt { @@ -85,15 +84,9 @@ impl Prompt { self.completion_selection_index.map(|i| i + 1).unwrap_or(0) % self.completion.len(); self.completion_selection_index = Some(index); - let item = &self.completion[index]; + let (range, item) = &self.completion[index]; - // replace the last arg - if let Some(pos) = self.line.rfind(' ') { - self.line.replace_range(pos + 1.., item); - } else { - // need toowned_clone_into nightly feature to reuse allocation - self.line = item.to_string(); - } + self.line.replace_range(range.clone(), item); self.move_end(); // TODO: recalculate completion when completion item is accepted, (Enter) @@ -135,7 +128,7 @@ impl Prompt { Rect::new(0, area.height - col_height - 2, area.width, col_height), theme.get("ui.statusline"), ); - for (i, command) in self.completion.iter().enumerate() { + for (i, (_range, completion)) in self.completion.iter().enumerate() { let color = if self.completion_selection_index.is_some() && i == self.completion_selection_index.unwrap() { @@ -146,7 +139,7 @@ impl Prompt { surface.set_stringn( 1 + col * BASE_WIDTH, area.height - col_height - 2 + row, - &command, + &completion, BASE_WIDTH as usize - 1, color, );