mirror of
https://github.com/helix-editor/helix.git
synced 2025-01-18 04:57:07 +04:00
Allow theming directory prompt completions (#12205)
This commit is contained in:
parent
6eb186eb7b
commit
91a5d407da
@ -305,6 +305,7 @@ #### Interface
|
||||
| `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.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.whitespace` | Visible whitespace characters |
|
||||
| `ui.virtual.indent-guide` | Vertical indent width guides |
|
||||
|
@ -2183,7 +2183,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
|
||||
completions
|
||||
.iter()
|
||||
.filter(|comp| comp.starts_with(input))
|
||||
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
|
||||
.map(|comp| (0.., comp.clone().into()))
|
||||
.collect()
|
||||
},
|
||||
move |cx, regex, event| {
|
||||
|
@ -3198,8 +3198,8 @@ pub(super) fn command_mode(cx: &mut Context) {
|
||||
{
|
||||
completer(editor, word)
|
||||
.into_iter()
|
||||
.map(|(range, file)| {
|
||||
let file = shellwords::escape(file);
|
||||
.map(|(range, mut file)| {
|
||||
file.content = shellwords::escape(file.content);
|
||||
|
||||
// offset ranges to input
|
||||
let offset = input.len() - word_len;
|
||||
|
@ -32,6 +32,17 @@
|
||||
|
||||
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(
|
||||
cx: &mut crate::commands::Context,
|
||||
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 {
|
||||
use super::Utf8PathBuf;
|
||||
use crate::ui::prompt::Completion;
|
||||
use helix_core::fuzzy::fuzzy_match;
|
||||
use helix_core::syntax::LanguageServerFeature;
|
||||
@ -274,6 +286,7 @@ pub mod completers {
|
||||
use helix_view::{editor::Config, Editor};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::borrow::Cow;
|
||||
use tui::text::Span;
|
||||
|
||||
pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
|
||||
|
||||
@ -290,7 +303,7 @@ pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
|
||||
|
||||
fuzzy_match(input, names, true)
|
||||
.into_iter()
|
||||
.map(|(name, _)| ((0..), name))
|
||||
.map(|(name, _)| ((0..), name.into()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -336,7 +349,7 @@ pub fn setting(_editor: &Editor, input: &str) -> Vec<Completion> {
|
||||
|
||||
fuzzy_match(input, &*KEYS, false)
|
||||
.into_iter()
|
||||
.map(|(name, _)| ((0..), name.into()))
|
||||
.map(|(name, _)| ((0..), Span::raw(name)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -424,7 +437,7 @@ enum FileMatch {
|
||||
|
||||
// TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
|
||||
fn filename_impl<F>(
|
||||
_editor: &Editor,
|
||||
editor: &Editor,
|
||||
input: &str,
|
||||
git_ignore: bool,
|
||||
filter_fn: F,
|
||||
@ -482,7 +495,7 @@ fn filename_impl<F>(
|
||||
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 mut path = if is_tilde {
|
||||
@ -501,23 +514,35 @@ fn filename_impl<F>(
|
||||
}
|
||||
|
||||
let path = path.into_os_string().into_string().ok()?;
|
||||
Some(Cow::from(path))
|
||||
Some(Utf8PathBuf { path, is_dir })
|
||||
})
|
||||
}) // 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 let Some(file_name) = file_name {
|
||||
let range = (input.len().saturating_sub(file_name.len()))..;
|
||||
fuzzy_match(&file_name, files, true)
|
||||
.into_iter()
|
||||
.map(|(name, _)| (range.clone(), name))
|
||||
.map(|(name, _)| (range.clone(), style_from_file(name)))
|
||||
.collect()
|
||||
|
||||
// TODO: complete to longest common match
|
||||
} else {
|
||||
let mut files: Vec<_> = files.map(|file| (end.clone(), file)).collect();
|
||||
files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
|
||||
let mut files: Vec<_> = files
|
||||
.map(|file| (end.clone(), style_from_file(file)))
|
||||
.collect();
|
||||
files.sort_unstable_by(|(_, path1), (_, path2)| path1.content.cmp(&path2.content));
|
||||
files
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, ops::RangeFrom};
|
||||
use tui::buffer::Buffer as Surface;
|
||||
use tui::text::Span;
|
||||
use tui::widgets::{Block, Widget};
|
||||
|
||||
use helix_core::{
|
||||
@ -19,7 +20,8 @@
|
||||
};
|
||||
|
||||
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 CallbackFn = Box<dyn FnMut(&mut Context, &str, PromptEvent)>;
|
||||
pub type DocFn = Box<dyn Fn(&str) -> Option<Cow<str>>>;
|
||||
@ -374,7 +376,7 @@ pub fn change_completion_selection(&mut self, direction: CompletionDirection) {
|
||||
|
||||
let (range, item) = &self.completion[index];
|
||||
|
||||
self.line.replace_range(range.clone(), item);
|
||||
self.line.replace_range(range.clone(), &item.content);
|
||||
|
||||
self.move_end();
|
||||
}
|
||||
@ -399,7 +401,7 @@ pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context)
|
||||
let max_len = self
|
||||
.completion
|
||||
.iter()
|
||||
.map(|(_, completion)| completion.len() as u16)
|
||||
.map(|(_, completion)| completion.content.len() as u16)
|
||||
.max()
|
||||
.unwrap_or(BASE_WIDTH)
|
||||
.max(BASE_WIDTH);
|
||||
@ -438,18 +440,22 @@ pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context)
|
||||
for (i, (_range, completion)) in
|
||||
self.completion.iter().enumerate().skip(offset).take(items)
|
||||
{
|
||||
let color = if Some(i) == self.selection {
|
||||
selected_color // TODO: just invert bg
|
||||
let is_selected = Some(i) == self.selection;
|
||||
|
||||
let completion_item_style = if is_selected {
|
||||
selected_color
|
||||
} else {
|
||||
completion_color
|
||||
completion_color.patch(completion.style)
|
||||
};
|
||||
|
||||
surface.set_stringn(
|
||||
area.x + col * (1 + col_width),
|
||||
area.y + row,
|
||||
completion,
|
||||
&completion.content,
|
||||
col_width.saturating_sub(1) as usize,
|
||||
color,
|
||||
completion_item_style,
|
||||
);
|
||||
|
||||
row += 1;
|
||||
if row > area.height - 1 {
|
||||
row = 0;
|
||||
|
@ -86,6 +86,7 @@
|
||||
"ui.text" = "text"
|
||||
"ui.text.focus" = { fg = "text", bg = "surface0", modifiers = ["bold"] }
|
||||
"ui.text.inactive" = { fg = "overlay1" }
|
||||
"ui.text.directory" = { fg = "blue" }
|
||||
|
||||
"ui.virtual" = "overlay0"
|
||||
"ui.virtual.ruler" = { bg = "surface0" }
|
||||
|
@ -72,6 +72,7 @@
|
||||
"ui.bufferline.background" = { bg = "background" }
|
||||
"ui.text" = { fg = "text" }
|
||||
"ui.text.focus" = { fg = "white" }
|
||||
"ui.text.directory" = { fg = "blue3" }
|
||||
"ui.virtual.whitespace" = { fg = "#3e3e3d" }
|
||||
"ui.virtual.ruler" = { bg = "borders" }
|
||||
"ui.virtual.indent-guide" = { fg = "dark_gray4" }
|
||||
|
@ -118,6 +118,7 @@
|
||||
"ui.statusline.select" = { fg = "black", bg = "cyan", modifiers = ["bold"] }
|
||||
"ui.text" = { fg = "foreground" }
|
||||
"ui.text.focus" = { fg = "cyan" }
|
||||
"ui.text.directory" = { fg = "cyan" }
|
||||
"ui.virtual.indent-guide" = { fg = "indent" }
|
||||
"ui.virtual.inlay-hint" = { fg = "cyan" }
|
||||
"ui.virtual.inlay-hint.parameter" = { fg = "cyan", modifiers = ["italic", "dim"] }
|
||||
|
@ -42,6 +42,7 @@
|
||||
"ui.statusline.select" = { fg = "background_dark", bg = "purple" }
|
||||
"ui.text" = { fg = "foreground" }
|
||||
"ui.text.focus" = { fg = "cyan" }
|
||||
"ui.text.directory" = { fg = "cyan" }
|
||||
"ui.window" = { fg = "foreground" }
|
||||
"ui.virtual.jump-label" = { fg = "pink", modifiers = ["bold"] }
|
||||
"ui.virtual.ruler" = { bg = "background_dark" }
|
||||
|
@ -59,6 +59,7 @@ label = "scale.red.3"
|
||||
"ui.text" = { fg = "fg.muted" }
|
||||
"ui.text.focus" = { fg = "fg.default" }
|
||||
"ui.text.inactive" = "fg.subtle"
|
||||
"ui.text.directory" = { fg = "scale.blue.2" }
|
||||
"ui.virtual" = { fg = "scale.gray.6" }
|
||||
"ui.virtual.ruler" = { bg = "canvas.subtle" }
|
||||
"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.focus" = { fg = "fg.default" }
|
||||
"ui.text.inactive" = "fg.subtle"
|
||||
"ui.text.directory" = { fg = "scale.blue.4" }
|
||||
"ui.virtual" = { fg = "scale.gray.2" }
|
||||
"ui.virtual.ruler" = { bg = "canvas.subtle" }
|
||||
|
||||
|
@ -106,6 +106,7 @@
|
||||
"ui.statusline.select" = { fg = "bg1", bg = "orange1", modifiers = ["bold"] }
|
||||
|
||||
"ui.text" = { fg = "fg1" }
|
||||
"ui.text.directory" = { fg = "blue1" }
|
||||
"ui.virtual.inlay-hint" = { fg = "gray" }
|
||||
"ui.virtual.jump-label" = { fg = "purple0", modifiers = ["bold"] }
|
||||
"ui.virtual.ruler" = { bg = "bg1" }
|
||||
|
@ -60,6 +60,7 @@
|
||||
|
||||
"ui.background" = { bg = "bg", fg = "text" }
|
||||
"ui.text" = { fg = "text" }
|
||||
"ui.text.directory" = { fg = "blue" }
|
||||
|
||||
"ui.statusline" = { bg = "bg", fg = "text" }
|
||||
"ui.statusline.inactive" = { bg = "bg", fg = "disabled" }
|
||||
|
@ -36,6 +36,7 @@
|
||||
"ui.text" = { fg = "text" }
|
||||
"ui.text.focus" = { bg = "overlay" }
|
||||
"ui.text.info" = { fg = "subtle" }
|
||||
"ui.text.directory" = { fg = "iris" }
|
||||
|
||||
"ui.virtual.jump-label" = { fg = "love", modifiers = ["bold"] }
|
||||
"ui.virtual.ruler" = { bg = "overlay" }
|
||||
|
@ -89,6 +89,7 @@ hint = { fg = "hint" }
|
||||
"ui.text.focus" = { bg = "bg-focus" }
|
||||
"ui.text.inactive" = { fg = "comment", modifiers = ["italic"] }
|
||||
"ui.text.info" = { bg = "bg-menu", fg = "fg" }
|
||||
"ui.text.directory" = { fg = "cyan" }
|
||||
"ui.virtual.ruler" = { bg = "fg-gutter" }
|
||||
"ui.virtual.whitespace" = { fg = "fg-gutter" }
|
||||
"ui.virtual.inlay-hint" = { bg = "bg-inlay", fg = "teal" }
|
||||
|
@ -56,6 +56,7 @@ tabstop = { modifiers = ["italic"], bg = "bossanova" }
|
||||
"ui.text" = { fg = "lavender" }
|
||||
"ui.text.focus" = { fg = "white" }
|
||||
"ui.text.inactive" = "sirocco"
|
||||
"ui.text.directory" = { fg = "lilac" }
|
||||
"ui.virtual" = { fg = "comet" }
|
||||
"ui.virtual.ruler" = { bg = "bossanova" }
|
||||
"ui.virtual.jump-label" = { fg = "apricot", modifiers = ["bold"] }
|
||||
|
Loading…
Reference in New Issue
Block a user