Skip to content

Commit

Permalink
helix-term: implement buffer completer
Browse files Browse the repository at this point in the history
In order to implement this completer, the completion function needs to
be able to access the compositor's context (to allow it to get the
list of buffers currently open in the context's editor).
  • Loading branch information
cole-h committed Nov 20, 2021
1 parent 231a18b commit 09e89de
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 46 deletions.
20 changes: 10 additions & 10 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1201,7 +1201,7 @@ fn select_regex(cx: &mut Context) {
cx,
"select:".into(),
Some(reg),
|_input: &str| Vec::new(),
|_input: &str, _ctx: &compositor::Context| Vec::new(),
move |view, doc, regex, event| {
if event != PromptEvent::Update {
return;
Expand All @@ -1224,7 +1224,7 @@ fn split_selection(cx: &mut Context) {
cx,
"split:".into(),
Some(reg),
|_input: &str| Vec::new(),
|_input: &str, _ctx: &compositor::Context| Vec::new(),
move |view, doc, regex, event| {
if event != PromptEvent::Update {
return;
Expand Down Expand Up @@ -1358,7 +1358,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
cx,
"search:".into(),
Some(reg),
move |input: &str| {
move |input: &str, _ctx: &compositor::Context| {
completions
.iter()
.filter(|comp| comp.starts_with(input))
Expand Down Expand Up @@ -1447,7 +1447,7 @@ fn global_search(cx: &mut Context) {
cx,
"global-search:".into(),
None,
move |input: &str| {
move |input: &str, _ctx: &compositor::Context| {
completions
.iter()
.filter(|comp| comp.starts_with(input))
Expand Down Expand Up @@ -2687,7 +2687,7 @@ fn command_mode(cx: &mut Context) {
let mut prompt = Prompt::new(
":".into(),
Some(':'),
|input: &str| {
|input: &str, ctx: &compositor::Context| {
// we use .this over split_whitespace() because we care about empty segments
let parts = input.split(' ').collect::<Vec<&str>>();

Expand All @@ -2708,7 +2708,7 @@ fn command_mode(cx: &mut Context) {
..
}) = cmd::COMMANDS.get(parts[0])
{
completer(part)
completer(part, ctx)
.into_iter()
.map(|(range, file)| {
// offset ranges to input
Expand Down Expand Up @@ -4648,7 +4648,7 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
cx,
if !remove { "keep:" } else { "remove:" }.into(),
Some(reg),
|_input: &str| Vec::new(),
|_input: &str, _ctx: &compositor::Context| Vec::new(),
move |view, doc, regex, event| {
if event != PromptEvent::Update {
return;
Expand Down Expand Up @@ -5301,7 +5301,7 @@ fn shell_keep_pipe(cx: &mut Context) {
let prompt = Prompt::new(
"keep-pipe:".into(),
Some('|'),
|_input: &str| Vec::new(),
|_input: &str, _ctx: &compositor::Context| Vec::new(),
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.config.shell;
if event != PromptEvent::Validate {
Expand Down Expand Up @@ -5398,7 +5398,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
let prompt = Prompt::new(
prompt,
Some('|'),
|_input: &str| Vec::new(),
|_input: &str, _ctx: &compositor::Context| Vec::new(),
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.config.shell;
if event != PromptEvent::Validate {
Expand Down Expand Up @@ -5496,7 +5496,7 @@ fn rename_symbol(cx: &mut Context) {
let prompt = Prompt::new(
"rename-to:".into(),
None,
|_input: &str| Vec::new(),
|_input: &str, _ctx: &compositor::Context| Vec::new(),
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
if event != PromptEvent::Validate {
return;
Expand Down
41 changes: 36 additions & 5 deletions helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub fn regex_prompt(
cx: &mut crate::commands::Context,
prompt: std::borrow::Cow<'static, str>,
history_register: Option<char>,
completion_fn: impl FnMut(&str) -> Vec<prompt::Completion> + 'static,
completion_fn: impl FnMut(&str, &crate::compositor::Context) -> Vec<prompt::Completion> + 'static,
fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static,
) -> Prompt {
let (view, doc) = current!(cx.editor);
Expand Down Expand Up @@ -171,16 +171,47 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
}

pub mod completers {
use crate::compositor::Context;
use crate::ui::prompt::Completion;
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher;
use helix_view::document::SCRATCH_BUFFER_NAME;
use helix_view::theme;
use std::borrow::Cow;
use std::cmp::Reverse;

pub type Completer = fn(&str) -> Vec<Completion>;
pub type Completer = fn(&str, &Context) -> Vec<Completion>;

pub fn buffer(input: &str, cx: &Context) -> Vec<Completion> {
let mut names: Vec<_> = cx
.editor
.documents
.iter()
.map(|(_id, doc)| {
let name = doc
.relative_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| String::from(SCRATCH_BUFFER_NAME));
((0..), Cow::from(name))
})
.collect();

let matcher = Matcher::default();

let mut matches: Vec<_> = names
.into_iter()
.filter_map(|(_range, name)| {
matcher.fuzzy_match(&name, input).map(|score| (name, score))
})
.collect();

matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();

names
}

pub fn theme(input: &str) -> Vec<Completion> {
pub fn theme(input: &str, _cx: &Context) -> Vec<Completion> {
let mut names = theme::Loader::read_names(&helix_core::runtime_dir().join("themes"));
names.extend(theme::Loader::read_names(
&helix_core::config_dir().join("themes"),
Expand All @@ -207,7 +238,7 @@ pub mod completers {
names
}

pub fn filename(input: &str) -> Vec<Completion> {
pub fn filename(input: &str, _cx: &Context) -> Vec<Completion> {
filename_impl(input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());

Expand All @@ -219,7 +250,7 @@ pub mod completers {
})
}

pub fn directory(input: &str) -> Vec<Completion> {
pub fn directory(input: &str, _cx: &Context) -> Vec<Completion> {
filename_impl(input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());

Expand Down
8 changes: 4 additions & 4 deletions helix-term/src/ui/picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ impl<T> Picker<T> {
let prompt = Prompt::new(
"".into(),
None,
|_pattern: &str| Vec::new(),
|_pattern: &str, _ctx: &Context| Vec::new(),
|_editor: &mut Context, _pattern: &str, _event: PromptEvent| {
//
},
Expand Down Expand Up @@ -374,12 +374,12 @@ impl<T> Picker<T> {
.map(|(index, _score)| &self.options[*index])
}

pub fn save_filter(&mut self) {
pub fn save_filter(&mut self, cx: &Context) {
self.filters.clear();
self.filters
.extend(self.matches.iter().map(|(index, _)| *index));
self.filters.sort_unstable(); // used for binary search later
self.prompt.clear();
self.prompt.clear(cx);
}
}

Expand Down Expand Up @@ -438,7 +438,7 @@ impl<T: 'static> Component for Picker<T> {
return close_fn;
}
ctrl!(' ') => {
self.save_filter();
self.save_filter(cx);
}
_ => {
if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
Expand Down
54 changes: 27 additions & 27 deletions helix-term/src/ui/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct Prompt {
selection: Option<usize>,
history_register: Option<char>,
history_pos: Option<usize>,
completion_fn: Box<dyn FnMut(&str) -> Vec<Completion>>,
completion_fn: Box<dyn FnMut(&str, &Context) -> Vec<Completion>>,
callback_fn: Box<dyn FnMut(&mut Context, &str, PromptEvent)>,
pub doc_fn: Box<dyn Fn(&str) -> Option<&'static str>>,
}
Expand Down Expand Up @@ -59,14 +59,14 @@ impl Prompt {
pub fn new(
prompt: Cow<'static, str>,
history_register: Option<char>,
mut completion_fn: impl FnMut(&str) -> Vec<Completion> + 'static,
completion_fn: impl FnMut(&str, &Context) -> Vec<Completion> + 'static,
callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static,
) -> Self {
Self {
prompt,
line: String::new(),
cursor: 0,
completion: completion_fn(""),
completion: Vec::new(),
selection: None,
history_register,
history_pos: None,
Expand Down Expand Up @@ -177,13 +177,13 @@ impl Prompt {
}
}

pub fn insert_char(&mut self, c: char) {
pub fn insert_char(&mut self, c: char, cx: &Context) {
self.line.insert(self.cursor, c);
let mut cursor = GraphemeCursor::new(self.cursor, self.line.len(), false);
if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) {
self.cursor = pos;
}
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
self.exit_selection();
}

Expand All @@ -205,61 +205,61 @@ impl Prompt {
self.cursor = self.line.len();
}

pub fn delete_char_backwards(&mut self) {
pub fn delete_char_backwards(&mut self, cx: &Context) {
let pos = self.eval_movement(Movement::BackwardChar(1));
self.line.replace_range(pos..self.cursor, "");
self.cursor = pos;

self.exit_selection();
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
}

pub fn delete_char_forwards(&mut self) {
pub fn delete_char_forwards(&mut self, cx: &Context) {
let pos = self.eval_movement(Movement::ForwardChar(1));
self.line.replace_range(self.cursor..pos, "");

self.exit_selection();
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
}

pub fn delete_word_backwards(&mut self) {
pub fn delete_word_backwards(&mut self, cx: &Context) {
let pos = self.eval_movement(Movement::BackwardWord(1));
self.line.replace_range(pos..self.cursor, "");
self.cursor = pos;

self.exit_selection();
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
}

pub fn delete_word_forwards(&mut self) {
pub fn delete_word_forwards(&mut self, cx: &Context) {
let pos = self.eval_movement(Movement::ForwardWord(1));
self.line.replace_range(self.cursor..pos, "");

self.exit_selection();
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
}

pub fn kill_to_start_of_line(&mut self) {
pub fn kill_to_start_of_line(&mut self, cx: &Context) {
let pos = self.eval_movement(Movement::StartOfLine);
self.line.replace_range(pos..self.cursor, "");
self.cursor = pos;

self.exit_selection();
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
}

pub fn kill_to_end_of_line(&mut self) {
pub fn kill_to_end_of_line(&mut self, cx: &Context) {
let pos = self.eval_movement(Movement::EndOfLine);
self.line.replace_range(self.cursor..pos, "");

self.exit_selection();
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
}

pub fn clear(&mut self) {
pub fn clear(&mut self, cx: &Context) {
self.line.clear();
self.cursor = 0;
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
self.exit_selection();
}

Expand Down Expand Up @@ -442,16 +442,16 @@ impl Component for Prompt {
ctrl!('f') | key!(Right) => self.move_cursor(Movement::ForwardChar(1)),
ctrl!('e') | key!(End) => self.move_end(),
ctrl!('a') | key!(Home) => self.move_start(),
ctrl!('w') => self.delete_word_backwards(),
alt!('d') => self.delete_word_forwards(),
ctrl!('k') => self.kill_to_end_of_line(),
ctrl!('u') => self.kill_to_start_of_line(),
ctrl!('w') => self.delete_word_backwards(cx),
alt!('d') => self.delete_word_forwards(cx),
ctrl!('k') => self.kill_to_end_of_line(cx),
ctrl!('u') => self.kill_to_start_of_line(cx),
ctrl!('h') | key!(Backspace) => {
self.delete_char_backwards();
self.delete_char_backwards(cx);
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
}
ctrl!('d') | key!(Delete) => {
self.delete_char_forwards();
self.delete_char_forwards(cx);
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
}
ctrl!('s') => {
Expand All @@ -474,7 +474,7 @@ impl Component for Prompt {
}
key!(Enter) => {
if self.selection.is_some() && self.line.ends_with('/') {
self.completion = (self.completion_fn)(&self.line);
self.completion = (self.completion_fn)(&self.line, cx);
self.exit_selection();
} else {
(self.callback_fn)(cx, &self.line, PromptEvent::Validate);
Expand Down Expand Up @@ -515,7 +515,7 @@ impl Component for Prompt {
code: KeyCode::Char(c),
modifiers,
} if !modifiers.contains(KeyModifiers::CONTROL) => {
self.insert_char(c);
self.insert_char(c, cx);
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
}
_ => (),
Expand Down

0 comments on commit 09e89de

Please sign in to comment.