on master help sign in
Improve completion: src/<tab> will now correctly complete to src/main.rs
commita32806b4901dfbb85fdc6c043515033afcbdb9a7 authorBlaž Hrastnik <blaz@mxxn.io> dateSun 21 Mar 2021, 14:13 (6 years ago) parentf29f018
3 files changed+33−26
helix-term/src/commands.rs+11−5 helix-term/src/ui/mod.rs+14−6 helix-term/src/ui/prompt.rs+8−15
helix-term/src/commands.rs+11−5
@@ -723,20 +723,26 @@ pub fn command_mode(cx: &mut Context) {
723723 // simple heuristic: if there's no space, complete command.
724724 // if there's a space, file completion kicks in. We should specialize by command later.
725725 if parts.len() <= 1 {
726+ use std::{borrow::Cow, ops::Range};
727+ let end = 0..;
726728 COMMAND_LIST
727729 .iter()
728730 .filter(|command| command.contains(input))
729− .map(|command| std::borrow::Cow::Borrowed(*command))
731+ .map(|command| (end.clone(), Cow::Borrowed(*command)))
730732 .collect()
731733 } else {
732734 let part = parts.last().unwrap();
733735 ui::completers::filename(part)
736+ .into_iter()
737+ .map(|(range, file)| {
738+ // offset ranges to input
739+ let offset = input.len() - part.len();
740+ let range = (range.start + offset)..;
741+ (range, file)
742+ })
743+ .collect()
734744
735745 // TODO
736− // completion needs to be more advanced: need to return starting index for replace
737− // for example, "src/" completion application.rs needs to insert after /, but "hx"
738− // completion helix-core needs to replace the text.
739− //
740746 // additionally, completion items could have a info section that would get
741747 // displayed in a popup above the prompt when items are tabbed over
742748 }
helix-term/src/ui/mod.rs+14−6
@@ -137,9 +137,9 @@ pub fn buffer_picker(views: &[View], current: usize) -> Picker<(Option<PathBuf>,
137137}
138138
139139pub mod completers {
140− use std::borrow::Cow;
140+ use std::{borrow::Cow, ops::RangeFrom};
141141 // TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
142− pub fn filename(input: &str) -> Vec<Cow<'static, str>> {
142+ pub fn filename(input: &str) -> Vec<(RangeFrom<usize>, Cow<'static, str>)> {
143143 // Rust's filename handling is really annoying.
144144
145145 use ignore::WalkBuilder;
@@ -163,6 +163,8 @@ pub mod completers {
163163 (path, file_name)
164164 };
165165
166+ let end = (input.len()..);
167+
166168 let mut files: Vec<_> = WalkBuilder::new(dir.clone())
167169 .max_depth(Some(1))
168170 .build()
@@ -178,10 +180,11 @@ pub mod completers {
178180 if is_dir {
179181 path.push("");
180182 }
181− Cow::from(path.to_str().unwrap().to_string())
183+ let path = path.to_str().unwrap().to_string();
184+ (end.clone(), Cow::from(path))
182185 })
183186 }) // TODO: unwrap or skip
184− .filter(|path| !path.is_empty()) // TODO
187+ .filter(|(_, path)| !path.is_empty()) // TODO
185188 .collect();
186189
187190 // if empty, return a list of dirs and files in current dir
@@ -195,15 +198,20 @@ pub mod completers {
195198 // inefficient, but we need to calculate the scores, filter out None, then sort.
196199 let mut matches: Vec<_> = files
197200 .into_iter()
198− .filter_map(|file| {
201+ .filter_map(|(range, file)| {
199202 matcher
200203 .fuzzy_match(&file, &file_name)
201204 .map(|score| (file, score))
202205 })
203206 .collect();
204207
208+ let range = ((input.len() - file_name.len())..);
209+
205210 matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
206− files = matches.into_iter().map(|(file, _)| file).collect();
211+ files = matches
212+ .into_iter()
213+ .map(|(file, _)| (range.clone(), file))
214+ .collect();
207215
208216 // TODO: complete to longest common match
209217 }
helix-term/src/ui/prompt.rs+8−15
@@ -3,16 +3,15 @@ use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
33use helix_core::Position;
44use helix_view::Editor;
55use helix_view::Theme;
6−use std::borrow::Cow;
7−use std::string::String;
6+use std::{borrow::Cow, ops::RangeFrom};
87
98pub struct Prompt {
109 prompt: String,
1110 pub line: String,
1211 cursor: usize,
13− completion: Vec<Cow<'static, str>>,
12+ completion: Vec<(RangeFrom<usize>, Cow<'static, str>)>,
1413 completion_selection_index: Option<usize>,
15− completion_fn: Box<dyn FnMut(&str) -> Vec<Cow<'static, str>>>,
14+ completion_fn: Box<dyn FnMut(&str) -> Vec<(RangeFrom<usize>, Cow<'static, str>)>>,
1615 callback_fn: Box<dyn FnMut(&mut Editor, &str, PromptEvent)>,
1716}
1817
@@ -29,7 +28,7 @@ pub enum PromptEvent {
2928impl Prompt {
3029 pub fn new(
3130 prompt: String,
32− mut completion_fn: impl FnMut(&str) -> Vec<Cow<'static, str>> + 'static,
31+ mut completion_fn: impl FnMut(&str) -> Vec<(RangeFrom<usize>, Cow<'static, str>)> + 'static,
3332 callback_fn: impl FnMut(&mut Editor, &str, PromptEvent) + 'static,
3433 ) -> Prompt {
3534 Prompt {
@@ -85,15 +84,9 @@ impl Prompt {
8584 self.completion_selection_index.map(|i| i + 1).unwrap_or(0) % self.completion.len();
8685 self.completion_selection_index = Some(index);
8786
88− let item = &self.completion[index];
87+ let (range, item) = &self.completion[index];
8988
90− // replace the last arg
91− if let Some(pos) = self.line.rfind(' ') {
92− self.line.replace_range(pos + 1.., item);
93− } else {
94− // need toowned_clone_into nightly feature to reuse allocation
95− self.line = item.to_string();
96− }
89+ self.line.replace_range(range.clone(), item);
9790
9891 self.move_end();
9992 // TODO: recalculate completion when completion item is accepted, (Enter)
@@ -135,7 +128,7 @@ impl Prompt {
135128 Rect::new(0, area.height - col_height - 2, area.width, col_height),
136129 theme.get("ui.statusline"),
137130 );
138− for (i, command) in self.completion.iter().enumerate() {
131+ for (i, (_range, completion)) in self.completion.iter().enumerate() {
139132 let color = if self.completion_selection_index.is_some()
140133 && i == self.completion_selection_index.unwrap()
141134 {
@@ -146,7 +139,7 @@ impl Prompt {
146139 surface.set_stringn(
147140 1 + col * BASE_WIDTH,
148141 area.height - col_height - 2 + row,
149− &command,
142+ &completion,
150143 BASE_WIDTH as usize - 1,
151144 color,
152145 );