|
|
|
@ -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<Self> {
|
|
|
|
|
use PathOrId::*;
|
|
|
|
|
Ok(match self {
|
|
|
|
|
Path(path) => Path(helix_core::path::get_canonicalized_path(&path)?),
|
|
|
|
|
Id(id) => Id(id),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<PathBuf> for PathOrId {
|
|
|
|
|
fn from(v: PathBuf) -> Self {
|
|
|
|
|
Self::Path(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<DocumentId> 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<T: Item> {
|
|
|
|
|
picker: Picker<T>,
|
|
|
|
@ -112,62 +136,71 @@ impl<T: Item> FilePicker<T> {
|
|
|
|
|
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<T: Item + 'static> Component for FilePicker<T> {
|
|
|
|
|
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 => {
|
|
|
|
|