aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src')
-rw-r--r--helix-term/src/commands.rs72
-rw-r--r--helix-term/src/commands/dap.rs7
-rw-r--r--helix-term/src/ui/mod.rs47
-rw-r--r--helix-term/src/ui/picker.rs8
-rw-r--r--helix-term/src/ui/prompt.rs55
5 files changed, 127 insertions, 62 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index e2c4a9d9..bf87f446 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1435,7 +1435,7 @@ fn select_regex(cx: &mut Context) {
cx,
"select:".into(),
Some(reg),
- |_input: &str| Vec::new(),
+ |_ctx: &compositor::Context, _input: &str| Vec::new(),
move |view, doc, regex, event| {
if event != PromptEvent::Update {
return;
@@ -1458,7 +1458,7 @@ fn split_selection(cx: &mut Context) {
cx,
"split:".into(),
Some(reg),
- |_input: &str| Vec::new(),
+ |_ctx: &compositor::Context, _input: &str| Vec::new(),
move |view, doc, regex, event| {
if event != PromptEvent::Update {
return;
@@ -1600,7 +1600,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
cx,
"search:".into(),
Some(reg),
- move |input: &str| {
+ move |_ctx: &compositor::Context, input: &str| {
completions
.iter()
.filter(|comp| comp.starts_with(input))
@@ -1701,7 +1701,7 @@ fn global_search(cx: &mut Context) {
cx,
"global-search:".into(),
None,
- move |input: &str| {
+ move |_ctx: &compositor::Context, input: &str| {
completions
.iter()
.filter(|comp| comp.starts_with(input))
@@ -2079,26 +2079,54 @@ pub mod cmd {
Ok(())
}
+ fn buffer_close_impl(
+ editor: &mut Editor,
+ args: &[Cow<str>],
+ force: bool,
+ ) -> anyhow::Result<()> {
+ if args.is_empty() {
+ let doc_id = view!(editor).doc;
+ editor.close_document(doc_id, force)?;
+ return Ok(());
+ }
+
+ for arg in args {
+ let doc_id = editor.documents().find_map(|doc| {
+ let arg_path = Some(Path::new(arg.as_ref()));
+ if doc.path().map(|p| p.as_path()) == arg_path
+ || doc.relative_path().as_deref() == arg_path
+ {
+ Some(doc.id())
+ } else {
+ None
+ }
+ });
+
+ match doc_id {
+ Some(doc_id) => editor.close_document(doc_id, force)?,
+ None => {
+ editor.set_error(format!("couldn't close buffer '{}': does not exist", arg));
+ }
+ }
+ }
+
+ Ok(())
+ }
+
fn buffer_close(
cx: &mut compositor::Context,
- _args: &[Cow<str>],
+ args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
- let view = view!(cx.editor);
- let doc_id = view.doc;
- cx.editor.close_document(doc_id, false)?;
- Ok(())
+ buffer_close_impl(cx.editor, args, false)
}
fn force_buffer_close(
cx: &mut compositor::Context,
- _args: &[Cow<str>],
+ args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
- let view = view!(cx.editor);
- let doc_id = view.doc;
- cx.editor.close_document(doc_id, true)?;
- Ok(())
+ buffer_close_impl(cx.editor, args, true)
}
fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
@@ -2927,14 +2955,14 @@ pub mod cmd {
aliases: &["bc", "bclose"],
doc: "Close the current buffer.",
fun: buffer_close,
- completer: None, // FIXME: buffer completer
+ completer: Some(completers::buffer),
},
TypableCommand {
name: "buffer-close!",
aliases: &["bc!", "bclose!"],
doc: "Close the current buffer forcefully (ignoring unsaved changes).",
fun: force_buffer_close,
- completer: None, // FIXME: buffer completer
+ completer: Some(completers::buffer),
},
TypableCommand {
name: "write",
@@ -3262,7 +3290,7 @@ fn command_mode(cx: &mut Context) {
let mut prompt = Prompt::new(
":".into(),
Some(':'),
- |input: &str| {
+ |ctx: &compositor::Context, input: &str| {
static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
@@ -3294,7 +3322,7 @@ fn command_mode(cx: &mut Context) {
..
}) = cmd::TYPABLE_COMMAND_MAP.get(parts[0])
{
- completer(part)
+ completer(ctx, part)
.into_iter()
.map(|(range, file)| {
// offset ranges to input
@@ -5358,7 +5386,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(),
+ |_ctx: &compositor::Context, _input: &str| Vec::new(),
move |view, doc, regex, event| {
if event != PromptEvent::Update {
return;
@@ -6122,7 +6150,7 @@ fn shell_keep_pipe(cx: &mut Context) {
let prompt = Prompt::new(
"keep-pipe:".into(),
Some('|'),
- |_input: &str| Vec::new(),
+ |_ctx: &compositor::Context, _input: &str| Vec::new(),
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.config.shell;
if event != PromptEvent::Validate {
@@ -6218,7 +6246,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
let prompt = Prompt::new(
prompt,
Some('|'),
- |_input: &str| Vec::new(),
+ |_ctx: &compositor::Context, _input: &str| Vec::new(),
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
let shell = &cx.editor.config.shell;
if event != PromptEvent::Validate {
@@ -6314,7 +6342,7 @@ fn rename_symbol(cx: &mut Context) {
let prompt = Prompt::new(
"rename-to:".into(),
None,
- |_input: &str| Vec::new(),
+ |_ctx: &compositor::Context, _input: &str| Vec::new(),
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
if event != PromptEvent::Validate {
return;
diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs
index 9da2715f..925c65c1 100644
--- a/helix-term/src/commands/dap.rs
+++ b/helix-term/src/commands/dap.rs
@@ -361,8 +361,9 @@ fn debug_parameter_prompt(
let completer = match field_type {
"filename" => ui::completers::filename,
"directory" => ui::completers::directory,
- _ => |_input: &str| Vec::new(),
+ _ => ui::completers::none,
};
+
Prompt::new(
format!("{}: ", name).into(),
None,
@@ -696,7 +697,7 @@ pub fn dap_edit_condition(cx: &mut Context) {
let mut prompt = Prompt::new(
"condition:".into(),
None,
- |_input: &str| Vec::new(),
+ ui::completers::none,
move |cx, input: &str, event: PromptEvent| {
if event != PromptEvent::Validate {
return;
@@ -740,7 +741,7 @@ pub fn dap_edit_log(cx: &mut Context) {
let mut prompt = Prompt::new(
"log-message:".into(),
None,
- |_input: &str| Vec::new(),
+ ui::completers::none,
move |cx, input: &str, event: PromptEvent| {
if event != PromptEvent::Validate {
return;
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 7f6d9f7c..263342b7 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -30,7 +30,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(&crate::compositor::Context, &str) -> Vec<prompt::Completion> + 'static,
fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static,
) -> Prompt {
let (view, doc) = current!(cx.editor);
@@ -168,18 +168,53 @@ 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::editor::Config;
use helix_view::theme;
use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::cmp::Reverse;
- pub type Completer = fn(&str) -> Vec<Completion>;
+ pub type Completer = fn(&Context, &str) -> Vec<Completion>;
- pub fn theme(input: &str) -> Vec<Completion> {
+ pub fn none(_cx: &Context, _input: &str) -> Vec<Completion> {
+ Vec::new()
+ }
+
+ pub fn buffer(cx: &Context, input: &str) -> 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(_cx: &Context, input: &str) -> 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"),
@@ -207,7 +242,7 @@ pub mod completers {
names
}
- pub fn setting(input: &str) -> Vec<Completion> {
+ pub fn setting(_cx: &Context, input: &str) -> Vec<Completion> {
static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
serde_json::to_value(Config::default())
.unwrap()
@@ -232,7 +267,7 @@ pub mod completers {
.collect()
}
- pub fn filename(input: &str) -> Vec<Completion> {
+ pub fn filename(_cx: &Context, input: &str) -> Vec<Completion> {
filename_impl(input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
@@ -244,7 +279,7 @@ pub mod completers {
})
}
- pub fn directory(input: &str) -> Vec<Completion> {
+ pub fn directory(_cx: &Context, input: &str) -> Vec<Completion> {
filename_impl(input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index 9cddbc60..dcc64002 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -302,7 +302,7 @@ impl<T> Picker<T> {
let prompt = Prompt::new(
"".into(),
None,
- |_pattern: &str| Vec::new(),
+ |_ctx: &Context, _pattern: &str| Vec::new(),
|_editor: &mut Context, _pattern: &str, _event: PromptEvent| {
//
},
@@ -395,12 +395,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);
}
}
@@ -468,7 +468,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) {
diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs
index 4c4fef26..ff6b8c76 100644
--- a/helix-term/src/ui/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -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(&Context, &str) -> Vec<Completion>>,
callback_fn: Box<dyn FnMut(&mut Context, &str, PromptEvent)>,
pub doc_fn: Box<dyn Fn(&str) -> Option<&'static str>>,
}
@@ -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(&Context, &str) -> 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,
@@ -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)(cx, &self.line);
self.exit_selection();
}
@@ -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)(cx, &self.line);
}
- 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)(cx, &self.line);
}
- 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)(cx, &self.line);
}
- 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)(cx, &self.line);
}
- 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)(cx, &self.line);
}
- 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)(cx, &self.line);
}
- 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)(cx, &self.line);
self.exit_selection();
}
@@ -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') => {
@@ -474,7 +474,7 @@ impl Component for Prompt {
}
key!(Enter) => {
if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) {
- self.completion = (self.completion_fn)(&self.line);
+ self.completion = (self.completion_fn)(cx, &self.line);
self.exit_selection();
} else {
(self.callback_fn)(cx, &self.line, PromptEvent::Validate);
@@ -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);
}
_ => (),
@@ -525,6 +525,7 @@ impl Component for Prompt {
}
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
+ self.completion = (self.completion_fn)(cx, &self.line);
self.render_prompt(area, surface, cx)
}