From 2f9ca3840a478fc58f411b6a4bc67019829ff72f Mon Sep 17 00:00:00 2001 From: A-Walrus <58790821+A-Walrus@users.noreply.github.com> Date: Mon, 21 Nov 2022 03:58:35 +0200 Subject: [PATCH] Add preview for scratch buffers in buffer picker (#3454) --- helix-term/src/commands.rs | 6 +- helix-term/src/commands/dap.rs | 4 +- helix-term/src/commands/lsp.rs | 2 +- helix-term/src/ui/mod.rs | 2 +- helix-term/src/ui/picker.rs | 129 +++++++++++++++++++++------------ 5 files changed, 88 insertions(+), 55 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1b0c557e6..25f00f987 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2005,7 +2005,7 @@ fn global_search(cx: &mut Context) { align_view(doc, view, Align::Center); }, |_editor, FileResult { path, line_num }| { - Some((path.clone(), Some((*line_num, *line_num)))) + Some((path.clone().into(), Some((*line_num, *line_num)))) }, ); compositor.push(Box::new(overlayed(picker))); @@ -2360,7 +2360,7 @@ fn buffer_picker(cx: &mut Context) { .selection(view_id) .primary() .cursor_line(doc.text().slice(..)); - Some((meta.path.clone()?, Some((line, line)))) + Some((meta.id.into(), Some((line, line)))) }, ); cx.push_layer(Box::new(overlayed(picker))); @@ -2441,7 +2441,7 @@ fn jumplist_picker(cx: &mut Context) { |editor, meta| { let doc = &editor.documents.get(&meta.id)?; let line = meta.selection.primary().cursor_line(doc.text().slice(..)); - Some((meta.path.clone()?, Some((line, line)))) + Some((meta.path.clone()?.into(), Some((line, line)))) }, ); cx.push_layer(Box::new(overlayed(picker))); diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index c27417e39..b182f28c4 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -85,7 +85,7 @@ fn thread_picker( frame.line.saturating_sub(1), frame.end_line.unwrap_or(frame.line).saturating_sub(1), )); - Some((path, pos)) + Some((path.into(), pos)) }, ); compositor.push(Box::new(picker)); @@ -706,7 +706,7 @@ pub fn dap_switch_stack_frame(cx: &mut Context) { .and_then(|source| source.path.clone()) .map(|path| { ( - path, + path.into(), Some(( frame.line.saturating_sub(1), frame.end_line.unwrap_or(frame.line).saturating_sub(1), diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 33d33440c..dcabfab1d 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -156,7 +156,7 @@ fn location_to_file_location(location: &lsp::Location) -> FileLocation { location.range.start.line as usize, location.range.end.line as usize, )); - (path, line) + (path.into(), line) } // TODO: share with symbol picker(symbol.location) diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index cca9e9bf0..3416b3194 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -230,7 +230,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi cx.editor.set_error(err); } }, - |_editor, path| Some((path.clone(), None)), + |_editor, path| Some((path.clone().into(), None)), ) } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 549119241..5e9ca3d88 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -12,18 +12,14 @@ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use tui::widgets::Widget; use std::{cmp::Ordering, time::Instant}; -use std::{ - collections::HashMap, - io::Read, - path::{Path, PathBuf}, -}; +use std::{collections::HashMap, io::Read, path::PathBuf}; use crate::ui::{Prompt, PromptEvent}; use helix_core::{movement::Direction, Position}; use helix_view::{ editor::Action, graphics::{CursorKind, Margin, Modifier, Rect}, - Document, Editor, + Document, DocumentId, Editor, }; use super::menu::Item; @@ -32,8 +28,36 @@ pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72; /// Biggest file size to preview in bytes pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024; +#[derive(PartialEq, Eq, Hash)] +pub enum PathOrId { + Id(DocumentId), + Path(PathBuf), +} + +impl PathOrId { + fn get_canonicalized(self) -> std::io::Result { + use PathOrId::*; + Ok(match self { + Path(path) => Path(helix_core::path::get_canonicalized_path(&path)?), + Id(id) => Id(id), + }) + } +} + +impl From for PathOrId { + fn from(v: PathBuf) -> Self { + Self::Path(v) + } +} + +impl From for PathOrId { + fn from(v: DocumentId) -> Self { + Self::Id(v) + } +} + /// File path and range of lines (used to align and highlight lines) -pub type FileLocation = (PathBuf, Option<(usize, usize)>); +pub type FileLocation = (PathOrId, Option<(usize, usize)>); pub struct FilePicker { picker: Picker, @@ -112,62 +136,71 @@ impl FilePicker { self.picker .selection() .and_then(|current| (self.file_fn)(editor, current)) - .and_then(|(path, line)| { - helix_core::path::get_canonicalized_path(&path) - .ok() - .zip(Some(line)) - }) + .and_then(|(path_or_id, line)| path_or_id.get_canonicalized().ok().zip(Some(line))) } /// Get (cached) preview for a given path. If a document corresponding /// to the path is already open in the editor, it is used instead. fn get_preview<'picker, 'editor>( &'picker mut self, - path: &Path, + path_or_id: PathOrId, editor: &'editor Editor, ) -> Preview<'picker, 'editor> { - if let Some(doc) = editor.document_by_path(path) { - return Preview::EditorDocument(doc); - } + match path_or_id { + PathOrId::Path(path) => { + let path = &path; + if let Some(doc) = editor.document_by_path(path) { + return Preview::EditorDocument(doc); + } - if self.preview_cache.contains_key(path) { - return Preview::Cached(&self.preview_cache[path]); - } + if self.preview_cache.contains_key(path) { + return Preview::Cached(&self.preview_cache[path]); + } - 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, - _ => { - // TODO: enable syntax highlighting; blocked by async rendering - Document::open(path, None, None) - .map(|doc| CachedPreview::Document(Box::new(doc))) - .unwrap_or(CachedPreview::NotFound) - } - }, - ) - .unwrap_or(CachedPreview::NotFound); - self.preview_cache.insert(path.to_owned(), preview); - Preview::Cached(&self.preview_cache[path]) + 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 + } + _ => { + // TODO: enable syntax highlighting; blocked by async rendering + Document::open(path, None, None) + .map(|doc| CachedPreview::Document(Box::new(doc))) + .unwrap_or(CachedPreview::NotFound) + } + }, + ) + .unwrap_or(CachedPreview::NotFound); + self.preview_cache.insert(path.to_owned(), preview); + Preview::Cached(&self.preview_cache[path]) + } + PathOrId::Id(id) => { + let doc = editor.documents.get(&id).unwrap(); + Preview::EditorDocument(doc) + } + } } fn handle_idle_timeout(&mut self, cx: &mut Context) -> EventResult { // Try to find a document in the cache let doc = self .current_file(cx.editor) - .and_then(|(path, _range)| self.preview_cache.get_mut(&path)) - .and_then(|cache| match cache { - CachedPreview::Document(doc) => Some(doc), - _ => None, + .and_then(|(path, _range)| match path { + PathOrId::Id(doc_id) => Some(doc_mut!(cx.editor, &doc_id)), + PathOrId::Path(path) => match self.preview_cache.get_mut(&path) { + Some(CachedPreview::Document(doc)) => Some(doc), + _ => None, + }, }); // Then attempt to highlight it if it has no language set @@ -224,7 +257,7 @@ impl Component for FilePicker { block.render(preview_area, surface); if let Some((path, range)) = self.current_file(cx.editor) { - let preview = self.get_preview(&path, cx.editor); + let preview = self.get_preview(path, cx.editor); let doc = match preview.document() { Some(doc) => doc, None => {