Allow theming directory prompt completions (#12205)
This commit is contained in:
parent
6eb186eb7b
commit
91a5d407da
16 changed files with 63 additions and 20 deletions
|
@ -305,6 +305,7 @@ These scopes are used for theming the editor interface:
|
||||||
| `ui.text.focus` | The currently selected line in the picker |
|
| `ui.text.focus` | The currently selected line in the picker |
|
||||||
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
|
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
|
||||||
| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
|
| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
|
||||||
|
| `ui.text.directory` | Directory names in prompt completion |
|
||||||
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
|
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
|
||||||
| `ui.virtual.whitespace` | Visible whitespace characters |
|
| `ui.virtual.whitespace` | Visible whitespace characters |
|
||||||
| `ui.virtual.indent-guide` | Vertical indent width guides |
|
| `ui.virtual.indent-guide` | Vertical indent width guides |
|
||||||
|
|
|
@ -2183,7 +2183,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
|
||||||
completions
|
completions
|
||||||
.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.., comp.clone().into()))
|
||||||
.collect()
|
.collect()
|
||||||
},
|
},
|
||||||
move |cx, regex, event| {
|
move |cx, regex, event| {
|
||||||
|
|
|
@ -3198,8 +3198,8 @@ pub(super) fn command_mode(cx: &mut Context) {
|
||||||
{
|
{
|
||||||
completer(editor, word)
|
completer(editor, word)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(range, file)| {
|
.map(|(range, mut file)| {
|
||||||
let file = shellwords::escape(file);
|
file.content = shellwords::escape(file.content);
|
||||||
|
|
||||||
// offset ranges to input
|
// offset ranges to input
|
||||||
let offset = input.len() - word_len;
|
let offset = input.len() - word_len;
|
||||||
|
|
|
@ -32,6 +32,17 @@ use helix_view::Editor;
|
||||||
|
|
||||||
use std::{error::Error, path::PathBuf};
|
use std::{error::Error, path::PathBuf};
|
||||||
|
|
||||||
|
struct Utf8PathBuf {
|
||||||
|
path: String,
|
||||||
|
is_dir: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for Utf8PathBuf {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn prompt(
|
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>,
|
||||||
|
@ -266,6 +277,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod completers {
|
pub mod completers {
|
||||||
|
use super::Utf8PathBuf;
|
||||||
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;
|
||||||
|
@ -274,6 +286,7 @@ pub mod completers {
|
||||||
use helix_view::{editor::Config, Editor};
|
use helix_view::{editor::Config, Editor};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use tui::text::Span;
|
||||||
|
|
||||||
pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
|
pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
|
||||||
|
|
||||||
|
@ -290,7 +303,7 @@ 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.into()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +349,7 @@ 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..), Span::raw(name)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,7 +437,7 @@ pub mod completers {
|
||||||
|
|
||||||
// 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.
|
||||||
fn filename_impl<F>(
|
fn filename_impl<F>(
|
||||||
_editor: &Editor,
|
editor: &Editor,
|
||||||
input: &str,
|
input: &str,
|
||||||
git_ignore: bool,
|
git_ignore: bool,
|
||||||
filter_fn: F,
|
filter_fn: F,
|
||||||
|
@ -482,7 +495,7 @@ pub mod completers {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
//let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
|
let is_dir = entry.file_type().is_some_and(|entry| entry.is_dir());
|
||||||
|
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let mut path = if is_tilde {
|
let mut path = if is_tilde {
|
||||||
|
@ -501,23 +514,35 @@ pub mod completers {
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = path.into_os_string().into_string().ok()?;
|
let path = path.into_os_string().into_string().ok()?;
|
||||||
Some(Cow::from(path))
|
Some(Utf8PathBuf { path, is_dir })
|
||||||
})
|
})
|
||||||
}) // TODO: unwrap or skip
|
}) // TODO: unwrap or skip
|
||||||
.filter(|path| !path.is_empty());
|
.filter(|path| !path.path.is_empty());
|
||||||
|
|
||||||
|
let directory_color = editor.theme.get("ui.text.directory");
|
||||||
|
|
||||||
|
let style_from_file = |file: Utf8PathBuf| {
|
||||||
|
if file.is_dir {
|
||||||
|
Span::styled(file.path, directory_color)
|
||||||
|
} else {
|
||||||
|
Span::raw(file.path)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// if empty, return a list of dirs and files in current dir
|
// if empty, return a list of dirs and files in current dir
|
||||||
if let Some(file_name) = file_name {
|
if let Some(file_name) = file_name {
|
||||||
let range = (input.len().saturating_sub(file_name.len()))..;
|
let range = (input.len().saturating_sub(file_name.len()))..;
|
||||||
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(), style_from_file(name)))
|
||||||
.collect()
|
.collect()
|
||||||
|
|
||||||
// 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
|
||||||
files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
|
.map(|file| (end.clone(), style_from_file(file)))
|
||||||
|
.collect();
|
||||||
|
files.sort_unstable_by(|(_, path1), (_, path2)| path1.content.cmp(&path2.content));
|
||||||
files
|
files
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use helix_view::keyboard::KeyCode;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{borrow::Cow, ops::RangeFrom};
|
use std::{borrow::Cow, ops::RangeFrom};
|
||||||
use tui::buffer::Buffer as Surface;
|
use tui::buffer::Buffer as Surface;
|
||||||
|
use tui::text::Span;
|
||||||
use tui::widgets::{Block, Widget};
|
use tui::widgets::{Block, Widget};
|
||||||
|
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
|
@ -19,7 +20,8 @@ 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>, Span<'static>);
|
||||||
type CompletionFn = Box<dyn FnMut(&Editor, &str) -> Vec<Completion>>;
|
type CompletionFn = Box<dyn FnMut(&Editor, &str) -> Vec<Completion>>;
|
||||||
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>>>;
|
||||||
|
@ -374,7 +376,7 @@ impl Prompt {
|
||||||
|
|
||||||
let (range, item) = &self.completion[index];
|
let (range, item) = &self.completion[index];
|
||||||
|
|
||||||
self.line.replace_range(range.clone(), item);
|
self.line.replace_range(range.clone(), &item.content);
|
||||||
|
|
||||||
self.move_end();
|
self.move_end();
|
||||||
}
|
}
|
||||||
|
@ -399,7 +401,7 @@ impl Prompt {
|
||||||
let max_len = self
|
let max_len = self
|
||||||
.completion
|
.completion
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, completion)| completion.len() as u16)
|
.map(|(_, completion)| completion.content.len() as u16)
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(BASE_WIDTH)
|
.unwrap_or(BASE_WIDTH)
|
||||||
.max(BASE_WIDTH);
|
.max(BASE_WIDTH);
|
||||||
|
@ -438,18 +440,22 @@ impl Prompt {
|
||||||
for (i, (_range, completion)) in
|
for (i, (_range, completion)) in
|
||||||
self.completion.iter().enumerate().skip(offset).take(items)
|
self.completion.iter().enumerate().skip(offset).take(items)
|
||||||
{
|
{
|
||||||
let color = if Some(i) == self.selection {
|
let is_selected = Some(i) == self.selection;
|
||||||
selected_color // TODO: just invert bg
|
|
||||||
|
let completion_item_style = if is_selected {
|
||||||
|
selected_color
|
||||||
} else {
|
} else {
|
||||||
completion_color
|
completion_color.patch(completion.style)
|
||||||
};
|
};
|
||||||
|
|
||||||
surface.set_stringn(
|
surface.set_stringn(
|
||||||
area.x + col * (1 + col_width),
|
area.x + col * (1 + col_width),
|
||||||
area.y + row,
|
area.y + row,
|
||||||
completion,
|
&completion.content,
|
||||||
col_width.saturating_sub(1) as usize,
|
col_width.saturating_sub(1) as usize,
|
||||||
color,
|
completion_item_style,
|
||||||
);
|
);
|
||||||
|
|
||||||
row += 1;
|
row += 1;
|
||||||
if row > area.height - 1 {
|
if row > area.height - 1 {
|
||||||
row = 0;
|
row = 0;
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
"ui.text" = "text"
|
"ui.text" = "text"
|
||||||
"ui.text.focus" = { fg = "text", bg = "surface0", modifiers = ["bold"] }
|
"ui.text.focus" = { fg = "text", bg = "surface0", modifiers = ["bold"] }
|
||||||
"ui.text.inactive" = { fg = "overlay1" }
|
"ui.text.inactive" = { fg = "overlay1" }
|
||||||
|
"ui.text.directory" = { fg = "blue" }
|
||||||
|
|
||||||
"ui.virtual" = "overlay0"
|
"ui.virtual" = "overlay0"
|
||||||
"ui.virtual.ruler" = { bg = "surface0" }
|
"ui.virtual.ruler" = { bg = "surface0" }
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
"ui.bufferline.background" = { bg = "background" }
|
"ui.bufferline.background" = { bg = "background" }
|
||||||
"ui.text" = { fg = "text" }
|
"ui.text" = { fg = "text" }
|
||||||
"ui.text.focus" = { fg = "white" }
|
"ui.text.focus" = { fg = "white" }
|
||||||
|
"ui.text.directory" = { fg = "blue3" }
|
||||||
"ui.virtual.whitespace" = { fg = "#3e3e3d" }
|
"ui.virtual.whitespace" = { fg = "#3e3e3d" }
|
||||||
"ui.virtual.ruler" = { bg = "borders" }
|
"ui.virtual.ruler" = { bg = "borders" }
|
||||||
"ui.virtual.indent-guide" = { fg = "dark_gray4" }
|
"ui.virtual.indent-guide" = { fg = "dark_gray4" }
|
||||||
|
|
|
@ -118,6 +118,7 @@
|
||||||
"ui.statusline.select" = { fg = "black", bg = "cyan", modifiers = ["bold"] }
|
"ui.statusline.select" = { fg = "black", bg = "cyan", modifiers = ["bold"] }
|
||||||
"ui.text" = { fg = "foreground" }
|
"ui.text" = { fg = "foreground" }
|
||||||
"ui.text.focus" = { fg = "cyan" }
|
"ui.text.focus" = { fg = "cyan" }
|
||||||
|
"ui.text.directory" = { fg = "cyan" }
|
||||||
"ui.virtual.indent-guide" = { fg = "indent" }
|
"ui.virtual.indent-guide" = { fg = "indent" }
|
||||||
"ui.virtual.inlay-hint" = { fg = "cyan" }
|
"ui.virtual.inlay-hint" = { fg = "cyan" }
|
||||||
"ui.virtual.inlay-hint.parameter" = { fg = "cyan", modifiers = ["italic", "dim"] }
|
"ui.virtual.inlay-hint.parameter" = { fg = "cyan", modifiers = ["italic", "dim"] }
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"ui.statusline.select" = { fg = "background_dark", bg = "purple" }
|
"ui.statusline.select" = { fg = "background_dark", bg = "purple" }
|
||||||
"ui.text" = { fg = "foreground" }
|
"ui.text" = { fg = "foreground" }
|
||||||
"ui.text.focus" = { fg = "cyan" }
|
"ui.text.focus" = { fg = "cyan" }
|
||||||
|
"ui.text.directory" = { fg = "cyan" }
|
||||||
"ui.window" = { fg = "foreground" }
|
"ui.window" = { fg = "foreground" }
|
||||||
"ui.virtual.jump-label" = { fg = "pink", modifiers = ["bold"] }
|
"ui.virtual.jump-label" = { fg = "pink", modifiers = ["bold"] }
|
||||||
"ui.virtual.ruler" = { bg = "background_dark" }
|
"ui.virtual.ruler" = { bg = "background_dark" }
|
||||||
|
|
|
@ -59,6 +59,7 @@ label = "scale.red.3"
|
||||||
"ui.text" = { fg = "fg.muted" }
|
"ui.text" = { fg = "fg.muted" }
|
||||||
"ui.text.focus" = { fg = "fg.default" }
|
"ui.text.focus" = { fg = "fg.default" }
|
||||||
"ui.text.inactive" = "fg.subtle"
|
"ui.text.inactive" = "fg.subtle"
|
||||||
|
"ui.text.directory" = { fg = "scale.blue.2" }
|
||||||
"ui.virtual" = { fg = "scale.gray.6" }
|
"ui.virtual" = { fg = "scale.gray.6" }
|
||||||
"ui.virtual.ruler" = { bg = "canvas.subtle" }
|
"ui.virtual.ruler" = { bg = "canvas.subtle" }
|
||||||
"ui.virtual.jump-label" = { fg = "scale.red.2", modifiers = ["bold"] }
|
"ui.virtual.jump-label" = { fg = "scale.red.2", modifiers = ["bold"] }
|
||||||
|
|
|
@ -59,6 +59,7 @@ label = "scale.red.5"
|
||||||
"ui.text" = { fg = "fg.muted" }
|
"ui.text" = { fg = "fg.muted" }
|
||||||
"ui.text.focus" = { fg = "fg.default" }
|
"ui.text.focus" = { fg = "fg.default" }
|
||||||
"ui.text.inactive" = "fg.subtle"
|
"ui.text.inactive" = "fg.subtle"
|
||||||
|
"ui.text.directory" = { fg = "scale.blue.4" }
|
||||||
"ui.virtual" = { fg = "scale.gray.2" }
|
"ui.virtual" = { fg = "scale.gray.2" }
|
||||||
"ui.virtual.ruler" = { bg = "canvas.subtle" }
|
"ui.virtual.ruler" = { bg = "canvas.subtle" }
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@
|
||||||
"ui.statusline.select" = { fg = "bg1", bg = "orange1", modifiers = ["bold"] }
|
"ui.statusline.select" = { fg = "bg1", bg = "orange1", modifiers = ["bold"] }
|
||||||
|
|
||||||
"ui.text" = { fg = "fg1" }
|
"ui.text" = { fg = "fg1" }
|
||||||
|
"ui.text.directory" = { fg = "blue1" }
|
||||||
"ui.virtual.inlay-hint" = { fg = "gray" }
|
"ui.virtual.inlay-hint" = { fg = "gray" }
|
||||||
"ui.virtual.jump-label" = { fg = "purple0", modifiers = ["bold"] }
|
"ui.virtual.jump-label" = { fg = "purple0", modifiers = ["bold"] }
|
||||||
"ui.virtual.ruler" = { bg = "bg1" }
|
"ui.virtual.ruler" = { bg = "bg1" }
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
|
|
||||||
"ui.background" = { bg = "bg", fg = "text" }
|
"ui.background" = { bg = "bg", fg = "text" }
|
||||||
"ui.text" = { fg = "text" }
|
"ui.text" = { fg = "text" }
|
||||||
|
"ui.text.directory" = { fg = "blue" }
|
||||||
|
|
||||||
"ui.statusline" = { bg = "bg", fg = "text" }
|
"ui.statusline" = { bg = "bg", fg = "text" }
|
||||||
"ui.statusline.inactive" = { bg = "bg", fg = "disabled" }
|
"ui.statusline.inactive" = { bg = "bg", fg = "disabled" }
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"ui.text" = { fg = "text" }
|
"ui.text" = { fg = "text" }
|
||||||
"ui.text.focus" = { bg = "overlay" }
|
"ui.text.focus" = { bg = "overlay" }
|
||||||
"ui.text.info" = { fg = "subtle" }
|
"ui.text.info" = { fg = "subtle" }
|
||||||
|
"ui.text.directory" = { fg = "iris" }
|
||||||
|
|
||||||
"ui.virtual.jump-label" = { fg = "love", modifiers = ["bold"] }
|
"ui.virtual.jump-label" = { fg = "love", modifiers = ["bold"] }
|
||||||
"ui.virtual.ruler" = { bg = "overlay" }
|
"ui.virtual.ruler" = { bg = "overlay" }
|
||||||
|
|
|
@ -89,6 +89,7 @@ hint = { fg = "hint" }
|
||||||
"ui.text.focus" = { bg = "bg-focus" }
|
"ui.text.focus" = { bg = "bg-focus" }
|
||||||
"ui.text.inactive" = { fg = "comment", modifiers = ["italic"] }
|
"ui.text.inactive" = { fg = "comment", modifiers = ["italic"] }
|
||||||
"ui.text.info" = { bg = "bg-menu", fg = "fg" }
|
"ui.text.info" = { bg = "bg-menu", fg = "fg" }
|
||||||
|
"ui.text.directory" = { fg = "cyan" }
|
||||||
"ui.virtual.ruler" = { bg = "fg-gutter" }
|
"ui.virtual.ruler" = { bg = "fg-gutter" }
|
||||||
"ui.virtual.whitespace" = { fg = "fg-gutter" }
|
"ui.virtual.whitespace" = { fg = "fg-gutter" }
|
||||||
"ui.virtual.inlay-hint" = { bg = "bg-inlay", fg = "teal" }
|
"ui.virtual.inlay-hint" = { bg = "bg-inlay", fg = "teal" }
|
||||||
|
|
|
@ -56,6 +56,7 @@ tabstop = { modifiers = ["italic"], bg = "bossanova" }
|
||||||
"ui.text" = { fg = "lavender" }
|
"ui.text" = { fg = "lavender" }
|
||||||
"ui.text.focus" = { fg = "white" }
|
"ui.text.focus" = { fg = "white" }
|
||||||
"ui.text.inactive" = "sirocco"
|
"ui.text.inactive" = "sirocco"
|
||||||
|
"ui.text.directory" = { fg = "lilac" }
|
||||||
"ui.virtual" = { fg = "comet" }
|
"ui.virtual" = { fg = "comet" }
|
||||||
"ui.virtual.ruler" = { bg = "bossanova" }
|
"ui.virtual.ruler" = { bg = "bossanova" }
|
||||||
"ui.virtual.jump-label" = { fg = "apricot", modifiers = ["bold"] }
|
"ui.virtual.jump-label" = { fg = "apricot", modifiers = ["bold"] }
|
||||||
|
|
Loading…
Reference in a new issue