mirror of https://github.com/helix-editor/helix
Improve completion: src/<tab> will now correctly complete to src/main.rs
parent
f29f01858d
commit
a32806b490
|
@ -723,20 +723,26 @@ pub fn command_mode(cx: &mut Context) {
|
||||||
// simple heuristic: if there's no space, complete command.
|
// 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 there's a space, file completion kicks in. We should specialize by command later.
|
||||||
if parts.len() <= 1 {
|
if parts.len() <= 1 {
|
||||||
|
use std::{borrow::Cow, ops::Range};
|
||||||
|
let end = 0..;
|
||||||
COMMAND_LIST
|
COMMAND_LIST
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|command| command.contains(input))
|
.filter(|command| command.contains(input))
|
||||||
.map(|command| std::borrow::Cow::Borrowed(*command))
|
.map(|command| (end.clone(), Cow::Borrowed(*command)))
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
let part = parts.last().unwrap();
|
let part = parts.last().unwrap();
|
||||||
ui::completers::filename(part)
|
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
|
// 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
|
// additionally, completion items could have a info section that would get
|
||||||
// displayed in a popup above the prompt when items are tabbed over
|
// displayed in a popup above the prompt when items are tabbed over
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,9 +137,9 @@ pub fn buffer_picker(views: &[View], current: usize) -> Picker<(Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod completers {
|
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.
|
// 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.
|
// Rust's filename handling is really annoying.
|
||||||
|
|
||||||
use ignore::WalkBuilder;
|
use ignore::WalkBuilder;
|
||||||
|
@ -163,6 +163,8 @@ pub mod completers {
|
||||||
(path, file_name)
|
(path, file_name)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let end = (input.len()..);
|
||||||
|
|
||||||
let mut files: Vec<_> = WalkBuilder::new(dir.clone())
|
let mut files: Vec<_> = WalkBuilder::new(dir.clone())
|
||||||
.max_depth(Some(1))
|
.max_depth(Some(1))
|
||||||
.build()
|
.build()
|
||||||
|
@ -178,10 +180,11 @@ pub mod completers {
|
||||||
if is_dir {
|
if is_dir {
|
||||||
path.push("");
|
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
|
}) // TODO: unwrap or skip
|
||||||
.filter(|path| !path.is_empty()) // TODO
|
.filter(|(_, path)| !path.is_empty()) // TODO
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// if empty, return a list of dirs and files in current dir
|
// 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.
|
// inefficient, but we need to calculate the scores, filter out None, then sort.
|
||||||
let mut matches: Vec<_> = files
|
let mut matches: Vec<_> = files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|file| {
|
.filter_map(|(range, file)| {
|
||||||
matcher
|
matcher
|
||||||
.fuzzy_match(&file, &file_name)
|
.fuzzy_match(&file, &file_name)
|
||||||
.map(|score| (file, score))
|
.map(|score| (file, score))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let range = ((input.len() - file_name.len())..);
|
||||||
|
|
||||||
matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
|
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
|
// TODO: complete to longest common match
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,15 @@ use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
||||||
use helix_core::Position;
|
use helix_core::Position;
|
||||||
use helix_view::Editor;
|
use helix_view::Editor;
|
||||||
use helix_view::Theme;
|
use helix_view::Theme;
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, ops::RangeFrom};
|
||||||
use std::string::String;
|
|
||||||
|
|
||||||
pub struct Prompt {
|
pub struct Prompt {
|
||||||
prompt: String,
|
prompt: String,
|
||||||
pub line: String,
|
pub line: String,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
completion: Vec<Cow<'static, str>>,
|
completion: Vec<(RangeFrom<usize>, Cow<'static, str>)>,
|
||||||
completion_selection_index: Option<usize>,
|
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)>,
|
callback_fn: Box<dyn FnMut(&mut Editor, &str, PromptEvent)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ pub enum PromptEvent {
|
||||||
impl Prompt {
|
impl Prompt {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
prompt: String,
|
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,
|
callback_fn: impl FnMut(&mut Editor, &str, PromptEvent) + 'static,
|
||||||
) -> Prompt {
|
) -> Prompt {
|
||||||
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.map(|i| i + 1).unwrap_or(0) % self.completion.len();
|
||||||
self.completion_selection_index = Some(index);
|
self.completion_selection_index = Some(index);
|
||||||
|
|
||||||
let item = &self.completion[index];
|
let (range, item) = &self.completion[index];
|
||||||
|
|
||||||
// replace the last arg
|
self.line.replace_range(range.clone(), item);
|
||||||
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.move_end();
|
self.move_end();
|
||||||
// TODO: recalculate completion when completion item is accepted, (Enter)
|
// 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),
|
Rect::new(0, area.height - col_height - 2, area.width, col_height),
|
||||||
theme.get("ui.statusline"),
|
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()
|
let color = if self.completion_selection_index.is_some()
|
||||||
&& i == self.completion_selection_index.unwrap()
|
&& i == self.completion_selection_index.unwrap()
|
||||||
{
|
{
|
||||||
|
@ -146,7 +139,7 @@ impl Prompt {
|
||||||
surface.set_stringn(
|
surface.set_stringn(
|
||||||
1 + col * BASE_WIDTH,
|
1 + col * BASE_WIDTH,
|
||||||
area.height - col_height - 2 + row,
|
area.height - col_height - 2 + row,
|
||||||
&command,
|
&completion,
|
||||||
BASE_WIDTH as usize - 1,
|
BASE_WIDTH as usize - 1,
|
||||||
color,
|
color,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue