aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBlaž Hrastnik2021-03-21 05:13:49 +0000
committerBlaž Hrastnik2021-03-21 05:13:49 +0000
commita32806b4901dfbb85fdc6c043515033afcbdb9a7 (patch)
tree0888cb1be221964a5ac0486953fc9f35d53e4c7a
parentf29f01858d1b8c9e54b3293879796a4650823f60 (diff)
Improve completion: src/<tab> will now correctly complete to src/main.rs
-rw-r--r--helix-term/src/commands.rs16
-rw-r--r--helix-term/src/ui/mod.rs20
-rw-r--r--helix-term/src/ui/prompt.rs23
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,
);