Improve completion: src/<tab> will now correctly complete to src/main.rs

This commit is contained in:
Blaž Hrastnik 2021-03-21 14:13:49 +09:00
parent f29f01858d
commit a32806b490
3 changed files with 33 additions and 26 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
);