Added option to hide Prompt completions

pull/8339/head
ItsEthra 1 year ago
parent 2533876ad7
commit 25f2016cf0

@ -2098,7 +2098,8 @@ fn searcher(cx: &mut Context, direction: Direction) {
.iter() .iter()
.filter(|comp| comp.starts_with(input)) .filter(|comp| comp.starts_with(input))
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone()))) .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
.collect() .collect::<Vec<_>>()
.into()
}, },
move |cx, regex, event| { move |cx, regex, event| {
if event == PromptEvent::Validate { if event == PromptEvent::Validate {
@ -2290,7 +2291,8 @@ fn global_search(cx: &mut Context) {
.iter() .iter()
.filter(|comp| comp.starts_with(input)) .filter(|comp| comp.starts_with(input))
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone()))) .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
.collect() .collect::<Vec<_>>()
.into()
}, },
move |cx, _, input, event| { move |cx, _, input, event| {
if event != PromptEvent::Validate { if event != PromptEvent::Validate {

@ -1,16 +1,18 @@
use helix_view::document::read_to_string;
use std::fmt::Write; use std::fmt::Write;
use std::io::BufReader; use std::io::BufReader;
use std::ops::Deref; use std::ops::Deref;
use crate::job::Job; use crate::job::Job;
use crate::ui::CompletionResult;
use super::*; use super::*;
use helix_core::fuzzy::fuzzy_match; use helix_core::fuzzy::fuzzy_match;
use helix_core::indent::MAX_INDENT; use helix_core::indent::MAX_INDENT;
use helix_core::{line_ending, shellwords::Shellwords}; use helix_core::{encoding, line_ending, shellwords::Shellwords};
use helix_view::document::{read_to_string, DEFAULT_LANGUAGE_NAME}; use helix_view::document::DEFAULT_LANGUAGE_NAME;
use helix_view::editor::{CloseError, ConfigEvent}; use helix_view::editor::{Action, CloseError, CommandHints, ConfigEvent};
use serde_json::Value; use serde_json::Value;
use ui::completers::{self, Completer}; use ui::completers::{self, Completer};
@ -3132,14 +3134,18 @@ pub(super) fn command_mode(cx: &mut Context) {
let words = shellwords.words(); let words = shellwords.words();
if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) { if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
fuzzy_match( let mut result: CompletionResult = fuzzy_match(
input, input,
TYPABLE_COMMAND_LIST.iter().map(|command| command.name), TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
false, false,
) )
.into_iter() .into_iter()
.map(|(name, _)| (0.., name.into())) .map(|(name, _)| (0.., name.into()))
.collect() .collect::<Vec<_>>()
.into();
result.show_popup = matches!(editor.config().command_hints, CommandHints::Always);
result
} else { } else {
// Otherwise, use the command's completer and the last shellword // Otherwise, use the command's completer and the last shellword
// as completion input. // as completion input.
@ -3151,11 +3157,12 @@ pub(super) fn command_mode(cx: &mut Context) {
let argument_number = argument_number_of(&shellwords); let argument_number = argument_number_of(&shellwords);
if let Some(completer) = TYPABLE_COMMAND_MAP let mut result = if let Some(completer) = TYPABLE_COMMAND_MAP
.get(&words[0] as &str) .get(&words[0] as &str)
.map(|tc| tc.completer_for_argument_number(argument_number)) .map(|tc| tc.completer_for_argument_number(argument_number))
{ {
completer(editor, word) completer(editor, word)
.completions
.into_iter() .into_iter()
.map(|(range, file)| { .map(|(range, file)| {
let file = shellwords::escape(file); let file = shellwords::escape(file);
@ -3165,10 +3172,18 @@ pub(super) fn command_mode(cx: &mut Context) {
let range = (range.start + offset)..; let range = (range.start + offset)..;
(range, file) (range, file)
}) })
.collect() .collect::<Vec<_>>()
.into()
} else { } else {
Vec::new() CompletionResult::default()
} };
result.show_popup = matches!(
editor.config().command_hints,
CommandHints::Always | CommandHints::OnlyArguments
);
result
} }
}, // completion }, // completion
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {

@ -23,7 +23,7 @@ pub use markdown::Markdown;
pub use menu::Menu; pub use menu::Menu;
pub use picker::{DynamicPicker, FileLocation, Picker}; pub use picker::{DynamicPicker, FileLocation, Picker};
pub use popup::Popup; pub use popup::Popup;
pub use prompt::{Prompt, PromptEvent}; pub use prompt::{CompletionResult, Prompt, PromptEvent};
pub use spinner::{ProgressSpinners, Spinner}; pub use spinner::{ProgressSpinners, Spinner};
pub use text::Text; pub use text::Text;
@ -35,7 +35,7 @@ pub fn prompt(
cx: &mut crate::commands::Context, cx: &mut crate::commands::Context,
prompt: std::borrow::Cow<'static, str>, prompt: std::borrow::Cow<'static, str>,
history_register: Option<char>, history_register: Option<char>,
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static, completion_fn: impl FnMut(&Editor, &str) -> CompletionResult + 'static,
callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static, callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
) { ) {
let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn); let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn);
@ -49,7 +49,7 @@ pub fn prompt_with_input(
prompt: std::borrow::Cow<'static, str>, prompt: std::borrow::Cow<'static, str>,
input: String, input: String,
history_register: Option<char>, history_register: Option<char>,
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static, completion_fn: impl FnMut(&Editor, &str) -> CompletionResult + 'static,
callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static, callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
) { ) {
let prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn) let prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn)
@ -61,7 +61,7 @@ pub fn regex_prompt(
cx: &mut crate::commands::Context, cx: &mut crate::commands::Context,
prompt: std::borrow::Cow<'static, str>, prompt: std::borrow::Cow<'static, str>,
history_register: Option<char>, history_register: Option<char>,
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static, completion_fn: impl FnMut(&Editor, &str) -> CompletionResult + 'static,
fun: impl Fn(&mut crate::compositor::Context, rope::Regex, PromptEvent) + 'static, fun: impl Fn(&mut crate::compositor::Context, rope::Regex, PromptEvent) + 'static,
) { ) {
raw_regex_prompt( raw_regex_prompt(
@ -72,11 +72,12 @@ pub fn regex_prompt(
move |cx, regex, _, event| fun(cx, regex, event), move |cx, regex, _, event| fun(cx, regex, event),
); );
} }
pub fn raw_regex_prompt( pub fn raw_regex_prompt(
cx: &mut crate::commands::Context, cx: &mut crate::commands::Context,
prompt: std::borrow::Cow<'static, str>, prompt: std::borrow::Cow<'static, str>,
history_register: Option<char>, history_register: Option<char>,
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static, completion_fn: impl FnMut(&Editor, &str) -> CompletionResult + 'static,
fun: impl Fn(&mut crate::compositor::Context, rope::Regex, &str, PromptEvent) + 'static, fun: impl Fn(&mut crate::compositor::Context, rope::Regex, &str, PromptEvent) + 'static,
) { ) {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
@ -254,6 +255,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
} }
pub mod completers { pub mod completers {
use super::CompletionResult;
use crate::ui::prompt::Completion; use crate::ui::prompt::Completion;
use helix_core::fuzzy::fuzzy_match; use helix_core::fuzzy::fuzzy_match;
use helix_core::syntax::LanguageServerFeature; use helix_core::syntax::LanguageServerFeature;
@ -263,13 +265,13 @@ pub mod completers {
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::borrow::Cow; use std::borrow::Cow;
pub type Completer = fn(&Editor, &str) -> Vec<Completion>; pub type Completer = fn(&Editor, &str) -> CompletionResult;
pub fn none(_editor: &Editor, _input: &str) -> Vec<Completion> { pub fn none(_editor: &Editor, _input: &str) -> CompletionResult {
Vec::new() CompletionResult::default()
} }
pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> { pub fn buffer(editor: &Editor, input: &str) -> CompletionResult {
let names = editor.documents.values().map(|doc| { let names = editor.documents.values().map(|doc| {
doc.relative_path() doc.relative_path()
.map(|p| p.display().to_string().into()) .map(|p| p.display().to_string().into())
@ -279,10 +281,11 @@ pub mod completers {
fuzzy_match(input, names, true) fuzzy_match(input, names, true)
.into_iter() .into_iter()
.map(|(name, _)| ((0..), name)) .map(|(name, _)| ((0..), name))
.collect() .collect::<Vec<Completion>>()
.into()
} }
pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> { pub fn theme(_editor: &Editor, input: &str) -> CompletionResult {
let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes")); let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes"));
for rt_dir in helix_loader::runtime_dirs() { for rt_dir in helix_loader::runtime_dirs() {
names.extend(theme::Loader::read_names(&rt_dir.join("themes"))); names.extend(theme::Loader::read_names(&rt_dir.join("themes")));
@ -295,7 +298,8 @@ pub mod completers {
fuzzy_match(input, names, false) fuzzy_match(input, names, false)
.into_iter() .into_iter()
.map(|(name, _)| ((0..), name.into())) .map(|(name, _)| ((0..), name.into()))
.collect() .collect::<Vec<Completion>>()
.into()
} }
/// Recursive function to get all keys from this value and add them to vec /// Recursive function to get all keys from this value and add them to vec
@ -314,7 +318,7 @@ pub mod completers {
} }
} }
pub fn setting(_editor: &Editor, input: &str) -> Vec<Completion> { pub fn setting(_editor: &Editor, input: &str) -> CompletionResult {
static KEYS: Lazy<Vec<String>> = Lazy::new(|| { static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
let mut keys = Vec::new(); let mut keys = Vec::new();
let json = serde_json::json!(Config::default()); let json = serde_json::json!(Config::default());
@ -325,18 +329,19 @@ pub mod completers {
fuzzy_match(input, &*KEYS, false) fuzzy_match(input, &*KEYS, false)
.into_iter() .into_iter()
.map(|(name, _)| ((0..), name.into())) .map(|(name, _)| ((0..), name.into()))
.collect() .collect::<Vec<Completion>>()
.into()
} }
pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> { pub fn filename(editor: &Editor, input: &str) -> CompletionResult {
filename_with_git_ignore(editor, input, true) filename_with_git_ignore(editor, input, true).into()
} }
pub fn filename_with_git_ignore( pub fn filename_with_git_ignore(
editor: &Editor, editor: &Editor,
input: &str, input: &str,
git_ignore: bool, git_ignore: bool,
) -> Vec<Completion> { ) -> CompletionResult {
filename_impl(editor, input, git_ignore, |entry| { filename_impl(editor, input, git_ignore, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
@ -346,9 +351,10 @@ pub mod completers {
FileMatch::Accept FileMatch::Accept
} }
}) })
.into()
} }
pub fn language(editor: &Editor, input: &str) -> Vec<Completion> { pub fn language(editor: &Editor, input: &str) -> CompletionResult {
let text: String = "text".into(); let text: String = "text".into();
let loader = editor.syn_loader.load(); let loader = editor.syn_loader.load();
@ -360,32 +366,34 @@ pub mod completers {
fuzzy_match(input, language_ids, false) fuzzy_match(input, language_ids, false)
.into_iter() .into_iter()
.map(|(name, _)| ((0..), name.to_owned().into())) .map(|(name, _)| ((0..), name.to_owned().into()))
.collect() .collect::<Vec<Completion>>()
.into()
} }
pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> { pub fn lsp_workspace_command(editor: &Editor, input: &str) -> CompletionResult {
let Some(options) = doc!(editor) let Some(options) = doc!(editor)
.language_servers_with_feature(LanguageServerFeature::WorkspaceCommand) .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
.find_map(|ls| ls.capabilities().execute_command_provider.as_ref()) .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
else { else {
return vec![]; return CompletionResult::default();
}; };
fuzzy_match(input, &options.commands, false) fuzzy_match(input, &options.commands, false)
.into_iter() .into_iter()
.map(|(name, _)| ((0..), name.to_owned().into())) .map(|(name, _)| ((0..), name.to_owned().into()))
.collect() .collect::<Vec<Completion>>()
.into()
} }
pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> { pub fn directory(editor: &Editor, input: &str) -> CompletionResult {
directory_with_git_ignore(editor, input, true) directory_with_git_ignore(editor, input, true).into()
} }
pub fn directory_with_git_ignore( pub fn directory_with_git_ignore(
editor: &Editor, editor: &Editor,
input: &str, input: &str,
git_ignore: bool, git_ignore: bool,
) -> Vec<Completion> { ) -> CompletionResult {
filename_impl(editor, input, git_ignore, |entry| { filename_impl(editor, input, git_ignore, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
@ -395,6 +403,7 @@ pub mod completers {
FileMatch::Reject FileMatch::Reject
} }
}) })
.into()
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
@ -414,7 +423,7 @@ pub mod completers {
input: &str, input: &str,
git_ignore: bool, git_ignore: bool,
filter_fn: F, filter_fn: F,
) -> Vec<Completion> ) -> CompletionResult
where where
F: Fn(&ignore::DirEntry) -> FileMatch, F: Fn(&ignore::DirEntry) -> FileMatch,
{ {
@ -498,17 +507,18 @@ pub mod completers {
fuzzy_match(&file_name, files, true) fuzzy_match(&file_name, files, true)
.into_iter() .into_iter()
.map(|(name, _)| (range.clone(), name)) .map(|(name, _)| (range.clone(), name))
.collect() .collect::<Vec<Completion>>()
.into()
// TODO: complete to longest common match // TODO: complete to longest common match
} else { } else {
let mut files: Vec<_> = files.map(|file| (end.clone(), file)).collect(); let mut files: Vec<_> = files.map(|file| (end.clone(), file)).collect();
files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2)); files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
files files.into()
} }
} }
pub fn register(editor: &Editor, input: &str) -> Vec<Completion> { pub fn register(editor: &Editor, input: &str) -> CompletionResult {
let iter = editor let iter = editor
.registers .registers
.iter_preview() .iter_preview()
@ -519,6 +529,7 @@ pub mod completers {
fuzzy_match(input, iter, false) fuzzy_match(input, iter, false)
.into_iter() .into_iter()
.map(|(name, _)| ((0..), name.into())) .map(|(name, _)| ((0..), name.into()))
.collect() .collect::<Vec<Completion>>()
.into()
} }
} }

@ -19,10 +19,25 @@ use helix_view::{
type PromptCharHandler = Box<dyn Fn(&mut Prompt, char, &Context)>; type PromptCharHandler = Box<dyn Fn(&mut Prompt, char, &Context)>;
pub type Completion = (RangeFrom<usize>, Cow<'static, str>); pub type Completion = (RangeFrom<usize>, Cow<'static, str>);
type CompletionFn = Box<dyn FnMut(&Editor, &str) -> Vec<Completion>>; type CompletionFn = Box<dyn FnMut(&Editor, &str) -> CompletionResult>;
type CallbackFn = Box<dyn FnMut(&mut Context, &str, PromptEvent)>; type CallbackFn = Box<dyn FnMut(&mut Context, &str, PromptEvent)>;
pub type DocFn = Box<dyn Fn(&str) -> Option<Cow<str>>>; pub type DocFn = Box<dyn Fn(&str) -> Option<Cow<str>>>;
#[derive(Default)]
pub struct CompletionResult {
pub completions: Vec<Completion>,
pub show_popup: bool,
}
impl From<Vec<Completion>> for CompletionResult {
fn from(completions: Vec<Completion>) -> Self {
Self {
show_popup: true,
completions,
}
}
}
pub struct Prompt { pub struct Prompt {
prompt: Cow<'static, str>, prompt: Cow<'static, str>,
line: String, line: String,
@ -34,6 +49,7 @@ pub struct Prompt {
completion_fn: CompletionFn, completion_fn: CompletionFn,
callback_fn: CallbackFn, callback_fn: CallbackFn,
pub doc_fn: DocFn, pub doc_fn: DocFn,
pub show_popup: bool,
next_char_handler: Option<PromptCharHandler>, next_char_handler: Option<PromptCharHandler>,
language: Option<(&'static str, Arc<ArcSwap<syntax::Loader>>)>, language: Option<(&'static str, Arc<ArcSwap<syntax::Loader>>)>,
} }
@ -72,7 +88,7 @@ impl Prompt {
pub fn new( pub fn new(
prompt: Cow<'static, str>, prompt: Cow<'static, str>,
history_register: Option<char>, history_register: Option<char>,
completion_fn: impl FnMut(&Editor, &str) -> Vec<Completion> + 'static, completion_fn: impl FnMut(&Editor, &str) -> CompletionResult + 'static,
callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static, callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static,
) -> Self { ) -> Self {
Self { Self {
@ -88,6 +104,7 @@ impl Prompt {
doc_fn: Box::new(|_| None), doc_fn: Box::new(|_| None),
next_char_handler: None, next_char_handler: None,
language: None, language: None,
show_popup: true,
} }
} }
@ -114,7 +131,13 @@ impl Prompt {
pub fn recalculate_completion(&mut self, editor: &Editor) { pub fn recalculate_completion(&mut self, editor: &Editor) {
self.exit_selection(); self.exit_selection();
self.completion = (self.completion_fn)(editor, &self.line); let CompletionResult {
completions,
show_popup,
} = (self.completion_fn)(editor, &self.line);
self.completion = completions;
self.show_popup = show_popup;
} }
/// Compute the cursor position after applying movement /// Compute the cursor position after applying movement
@ -367,14 +390,10 @@ impl Prompt {
const BASE_WIDTH: u16 = 30; const BASE_WIDTH: u16 = 30;
impl Prompt { impl Prompt {
pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render_completion_hints(&self, area: Rect, surface: &mut Surface, cx: &mut Context) -> Rect {
let theme = &cx.editor.theme; let theme = &cx.editor.theme;
let prompt_color = theme.get("ui.text");
let completion_color = theme.get("ui.menu"); let completion_color = theme.get("ui.menu");
let selected_color = theme.get("ui.menu.selected"); let selected_color = theme.get("ui.menu.selected");
let suggestion_color = theme.get("ui.text.inactive");
let background = theme.get("ui.background");
// completion
let max_len = self let max_len = self
.completion .completion
@ -437,6 +456,21 @@ impl Prompt {
} }
} }
completion_area
}
pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
let completion_area = if self.show_popup {
self.render_completion_hints(area, surface, cx)
} else {
Rect::new(area.x, area.height.saturating_sub(1), area.width, 0)
};
let theme = &cx.editor.theme;
let prompt_color = theme.get("ui.text");
let suggestion_color = theme.get("ui.text.inactive");
let background = theme.get("ui.background");
if let Some(doc) = (self.doc_fn)(&self.line) { if let Some(doc) = (self.doc_fn)(&self.line) {
let mut text = ui::Text::new(doc.to_string()); let mut text = ui::Text::new(doc.to_string());

@ -351,6 +351,7 @@ pub enum CommandHints {
/// Never show it /// Never show it
Never, Never,
/// Show only for command's arguments /// Show only for command's arguments
#[serde(rename = "only-args")]
OnlyArguments, OnlyArguments,
} }

Loading…
Cancel
Save