diff options
author | Blaž Hrastnik | 2021-03-21 05:13:49 +0000 |
---|---|---|
committer | Blaž Hrastnik | 2021-03-21 05:13:49 +0000 |
commit | a32806b4901dfbb85fdc6c043515033afcbdb9a7 (patch) | |
tree | 0888cb1be221964a5ac0486953fc9f35d53e4c7a | |
parent | f29f01858d1b8c9e54b3293879796a4650823f60 (diff) |
Improve completion: src/<tab> will now correctly complete to src/main.rs
-rw-r--r-- | helix-term/src/commands.rs | 16 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 20 | ||||
-rw-r--r-- | helix-term/src/ui/prompt.rs | 23 |
3 files changed, 33 insertions, 26 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f84bb16e..54fea453 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 f7ba59cd..b35cba60 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<PathBuf>, } 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<Cow<'static, str>> { + pub fn filename(input: &str) -> Vec<(RangeFrom<usize>, 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 19885c02..35890b4c 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<Cow<'static, str>>, + completion: Vec<(RangeFrom<usize>, Cow<'static, str>)>, completion_selection_index: Option<usize>, - completion_fn: Box<dyn FnMut(&str) -> Vec<Cow<'static, str>>>, + completion_fn: Box<dyn FnMut(&str) -> Vec<(RangeFrom<usize>, Cow<'static, str>)>>, callback_fn: Box<dyn FnMut(&mut Editor, &str, PromptEvent)>, } @@ -29,7 +28,7 @@ pub enum PromptEvent { impl Prompt { pub fn new( prompt: String, - mut completion_fn: impl FnMut(&str) -> Vec<Cow<'static, str>> + 'static, + mut completion_fn: impl FnMut(&str) -> Vec<(RangeFrom<usize>, 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, ); |