Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/picker.rs')
-rw-r--r--helix-term/src/ui/picker.rs226
1 files changed, 67 insertions, 159 deletions
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index 4f77f8b9..df8d52eb 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -85,7 +85,6 @@ pub type FileLocation<'a> = (PathOrId<'a>, Option<(usize, usize)>);
pub enum CachedPreview {
Document(Box<Document>),
- Directory(Vec<(String, bool)>),
Binary,
LargeFile,
NotFound,
@@ -107,20 +106,12 @@ impl Preview<'_, '_> {
}
}
- fn dir_content(&self) -> Option<&Vec<(String, bool)>> {
- match self {
- Preview::Cached(CachedPreview::Directory(dir_content)) => Some(dir_content),
- _ => None,
- }
- }
-
/// Alternate text to show for the preview.
fn placeholder(&self) -> &str {
match *self {
Self::EditorDocument(_) => "<Invalid file location>",
Self::Cached(preview) => match preview {
CachedPreview::Document(_) => "<Invalid file location>",
- CachedPreview::Directory(_) => "<Invalid directory location>",
CachedPreview::Binary => "<Binary file>",
CachedPreview::LargeFile => "<File too large to preview>",
CachedPreview::NotFound => "<File not found>",
@@ -258,7 +249,6 @@ pub struct Picker<T: 'static + Send + Sync, D: 'static> {
widths: Vec<Constraint>,
callback_fn: PickerCallback<T>,
- default_action: Action,
pub truncate_start: bool,
/// Caches paths to documents
@@ -309,10 +299,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
F: Fn(&mut Context, &T, Action) + 'static,
{
let columns: Arc<[_]> = columns.into_iter().collect();
- let matcher_columns = columns
- .iter()
- .filter(|col: &&Column<T, D>| col.filter)
- .count() as u32;
+ let matcher_columns = columns.iter().filter(|col| col.filter).count() as u32;
assert!(matcher_columns > 0);
let matcher = Nucleo::new(
Config::DEFAULT,
@@ -386,7 +373,6 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
truncate_start: true,
show_preview: true,
callback_fn: Box::new(callback_fn),
- default_action: Action::Replace,
completion_height: 0,
widths,
preview_cache: HashMap::new(),
@@ -429,11 +415,6 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
self
}
- pub fn with_initial_cursor(mut self, cursor: u32) -> Self {
- self.cursor = cursor;
- self
- }
-
pub fn with_dynamic_query(
mut self,
callback: DynQueryCallback<T, D>,
@@ -450,11 +431,6 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
self
}
- pub fn with_default_action(mut self, action: Action) -> Self {
- self.default_action = action;
- self
- }
-
/// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
pub fn move_by(&mut self, amount: u32, direction: Direction) {
let len = self.matcher.snapshot().matched_item_count();
@@ -600,76 +576,41 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
// retrieve the `Arc<Path>` key. The `path` in scope here is a `&Path` and
// we can cheaply clone the key for the preview highlight handler.
let (path, preview) = self.preview_cache.get_key_value(path).unwrap();
- if matches!(preview, CachedPreview::Document(doc) if doc.syntax().is_none()) {
+ if matches!(preview, CachedPreview::Document(doc) if doc.language_config().is_none())
+ {
helix_event::send_blocking(&self.preview_highlight_handler, path.clone());
}
return Some((Preview::Cached(preview), range));
}
let path: Arc<Path> = path.into();
- let preview = std::fs::metadata(&path)
- .and_then(|metadata| {
- if metadata.is_dir() {
- let files = super::directory_content(&path, editor)?;
- let file_names: Vec<_> = files
- .iter()
- .filter_map(|(file_path, is_dir)| {
- let name = file_path
- .strip_prefix(&path)
- .map(|p| Some(p.as_os_str()))
- .unwrap_or_else(|_| file_path.file_name())?
- .to_string_lossy();
- if *is_dir {
- Some((format!("{}/", name), true))
- } else {
- Some((name.into_owned(), false))
- }
- })
- .collect();
- Ok(CachedPreview::Directory(file_names))
- } else if metadata.is_file() {
- if metadata.len() > MAX_FILE_SIZE_FOR_PREVIEW {
- return Ok(CachedPreview::LargeFile);
- }
- let content_type = std::fs::File::open(&path).and_then(|file| {
- // Read up to 1kb to detect the content type
- let n = file.take(1024).read_to_end(&mut self.read_buffer)?;
- let content_type =
- content_inspector::inspect(&self.read_buffer[..n]);
- self.read_buffer.clear();
- Ok(content_type)
- })?;
- if content_type.is_binary() {
- return Ok(CachedPreview::Binary);
- }
- let mut doc = Document::open(
- &path,
- None,
- false,
- editor.config.clone(),
- editor.syn_loader.clone(),
- )
- .or(Err(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- "Cannot open document",
- )))?;
- let loader = editor.syn_loader.load();
- if let Some(language_config) = doc.detect_language_config(&loader) {
- doc.language = Some(language_config);
- // Asynchronously highlight the new document
- helix_event::send_blocking(
- &self.preview_highlight_handler,
- path.clone(),
- );
+ let data = std::fs::File::open(&path).and_then(|file| {
+ let metadata = file.metadata()?;
+ // Read up to 1kb to detect the content type
+ let n = file.take(1024).read_to_end(&mut self.read_buffer)?;
+ let content_type = content_inspector::inspect(&self.read_buffer[..n]);
+ self.read_buffer.clear();
+ Ok((metadata, content_type))
+ });
+ let preview = data
+ .map(
+ |(metadata, content_type)| match (metadata.len(), content_type) {
+ (_, content_inspector::ContentType::BINARY) => CachedPreview::Binary,
+ (size, _) if size > MAX_FILE_SIZE_FOR_PREVIEW => {
+ CachedPreview::LargeFile
}
- Ok(CachedPreview::Document(Box::new(doc)))
- } else {
- Err(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- "Neither a dir, nor a file",
- ))
- }
- })
+ _ => Document::open(&path, None, None, editor.config.clone())
+ .map(|doc| {
+ // Asynchronously highlight the new document
+ helix_event::send_blocking(
+ &self.preview_highlight_handler,
+ path.clone(),
+ );
+ CachedPreview::Document(Box::new(doc))
+ })
+ .unwrap_or(CachedPreview::NotFound),
+ },
+ )
.unwrap_or(CachedPreview::NotFound);
self.preview_cache.insert(path.clone(), preview);
Some((Preview::Cached(&self.preview_cache[&path]), range))
@@ -708,6 +649,10 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
// -- Render the input bar:
+ let area = inner.clip_left(1).with_height(1);
+ // render the prompt first since it will clear its background
+ self.prompt.render(area, surface, cx);
+
let count = format!(
"{}{}/{}",
if status.running || self.matcher.active_injectors() > 0 {
@@ -718,13 +663,6 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
snapshot.matched_item_count(),
snapshot.item_count(),
);
-
- let area = inner.clip_left(1).with_height(1);
- let line_area = area.clip_right(count.len() as u16 + 1);
-
- // render the prompt first since it will clear its background
- self.prompt.render(line_area, surface, cx);
-
surface.set_stringn(
(area.x + area.width).saturating_sub(count.len() as u16 + 1),
area.y,
@@ -885,7 +823,6 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
// clear area
let background = cx.editor.theme.get("ui.background");
let text = cx.editor.theme.get("ui.text");
- let directory = cx.editor.theme.get("ui.text.directory");
surface.clear_with(area, background);
const BLOCK: Block<'_> = Block::bordered();
@@ -900,29 +837,13 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
if let Some((preview, range)) = self.get_preview(cx.editor) {
let doc = match preview.document() {
Some(doc)
- if range.is_none_or(|(start, end)| {
+ if range.map_or(true, |(start, end)| {
start <= end && end <= doc.text().len_lines()
}) =>
{
doc
}
_ => {
- if let Some(dir_content) = preview.dir_content() {
- for (i, (path, is_dir)) in
- dir_content.iter().take(inner.height as usize).enumerate()
- {
- let style = if *is_dir { directory } else { text };
- surface.set_stringn(
- inner.x,
- inner.y + i as u16,
- path,
- inner.width as usize,
- style,
- );
- }
- return;
- }
-
let alt_text = preview.placeholder();
let x = inner.x + inner.width.saturating_sub(alt_text.len() as u16) / 2;
let y = inner.y + inner.height / 2;
@@ -958,18 +879,21 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
}
}
- let loader = cx.editor.syn_loader.load();
-
- let syntax_highlighter =
- EditorView::doc_syntax_highlighter(doc, offset.anchor, area.height, &loader);
- let mut overlay_highlights = Vec::new();
-
- EditorView::doc_diagnostics_highlights_into(
+ let syntax_highlights = EditorView::doc_syntax_highlights(
doc,
+ offset.anchor,
+ area.height,
&cx.editor.theme,
- &mut overlay_highlights,
);
+ let mut overlay_highlights =
+ EditorView::empty_highlight_iter(doc, offset.anchor, area.height);
+ for spans in EditorView::doc_diagnostics_highlights(doc, &cx.editor.theme) {
+ if spans.is_empty() {
+ continue;
+ }
+ overlay_highlights = Box::new(helix_core::syntax::merge(overlay_highlights, spans));
+ }
let mut decorations = DecorationManager::default();
if let Some((start, end)) = range {
@@ -999,7 +923,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
offset,
// TODO: compute text annotations asynchronously here (like inlay hints)
&TextAnnotations::default(),
- syntax_highlighter,
+ syntax_highlights,
overlay_highlights,
&cx.editor.theme,
decorations,
@@ -1048,23 +972,23 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
let close_fn = |picker: &mut Self| {
// if the picker is very large don't store it as last_picker to avoid
// excessive memory consumption
- let callback: compositor::Callback =
- if picker.matcher.snapshot().item_count() > 1_000_000 {
- Box::new(|compositor: &mut Compositor, _ctx| {
- // remove the layer
- compositor.pop();
- })
- } else {
- // stop streaming in new items in the background, really we should
- // be restarting the stream somehow once the picker gets
- // reopened instead (like for an FS crawl) that would also remove the
- // need for the special case above but that is pretty tricky
- picker.version.fetch_add(1, atomic::Ordering::Relaxed);
- Box::new(|compositor: &mut Compositor, _ctx| {
- // remove the layer
- compositor.last_picker = compositor.pop();
- })
- };
+ let callback: compositor::Callback = if picker.matcher.snapshot().item_count() > 100_000
+ {
+ Box::new(|compositor: &mut Compositor, _ctx| {
+ // remove the layer
+ compositor.pop();
+ })
+ } else {
+ // stop streaming in new items in the background, really we should
+ // be restarting the stream somehow once the picker gets
+ // reopened instead (like for an FS crawl) that would also remove the
+ // need for the special case above but that is pretty tricky
+ picker.version.fetch_add(1, atomic::Ordering::Relaxed);
+ Box::new(|compositor: &mut Compositor, _ctx| {
+ // remove the layer
+ compositor.last_picker = compositor.pop();
+ })
+ };
EventResult::Consumed(Some(callback))
};
@@ -1090,7 +1014,7 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
key!(Esc) | ctrl!('c') => return close_fn(self),
alt!(Enter) => {
if let Some(option) = self.selection() {
- (self.callback_fn)(ctx, option, self.default_action);
+ (self.callback_fn)(ctx, option, Action::Load);
}
}
key!(Enter) => {
@@ -1101,20 +1025,12 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
.first_history_completion(ctx.editor)
.filter(|_| self.prompt.line().is_empty())
{
- // The percent character is used by the query language and needs to be
- // escaped with a backslash.
- let completion = if completion.contains('%') {
- completion.replace('%', "\\%")
- } else {
- completion.into_owned()
- };
- self.prompt.set_line(completion, ctx.editor);
-
+ self.prompt.set_line(completion.to_string(), ctx.editor);
// Inserting from the history register is a paste.
self.handle_prompt_change(true);
} else {
if let Some(option) = self.selection() {
- (self.callback_fn)(ctx, option, self.default_action);
+ (self.callback_fn)(ctx, option, Action::Replace);
}
if let Some(history_register) = self.prompt.history_register() {
if let Err(err) = ctx
@@ -1157,15 +1073,7 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
let inner = block.inner(area);
// prompt area
- let render_preview =
- self.show_preview && self.file_fn.is_some() && area.width > MIN_AREA_WIDTH_FOR_PREVIEW;
-
- let picker_width = if render_preview {
- area.width / 2
- } else {
- area.width
- };
- let area = inner.clip_left(1).with_height(1).with_width(picker_width);
+ let area = inner.clip_left(1).with_height(1);
self.prompt.cursor(area, editor)
}