Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show file preview in split pane in fuzzy finder #534

Merged
merged 21 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7a1dbed
Add preview pane for fuzzy finder
sudormrfbin Jul 30, 2021
ab41e45
Fix picker preview lag by caching
sudormrfbin Jul 31, 2021
c62d5aa
Add picker preview for document symbols
sudormrfbin Jul 31, 2021
5325eea
Cache picker preview per document instead of view
sudormrfbin Jul 31, 2021
6df7cfa
Use line instead of range for preview doc
sudormrfbin Jul 31, 2021
2ed40c2
Add picker preview for buffer picker
sudormrfbin Jul 31, 2021
5785258
Fix render bug and refactor picker
sudormrfbin Jul 31, 2021
42ebb1b
Refactor picker preview rendering
sudormrfbin Aug 1, 2021
bd5c7b5
Split picker and preview and compose
sudormrfbin Aug 1, 2021
29b0757
Refactor out clones in previewed picker
sudormrfbin Aug 2, 2021
3ab2823
Retrieve doc from editor if possible in filepicker
sudormrfbin Aug 3, 2021
a524cd8
Disable syntax highlight for picker preview
sudormrfbin Aug 6, 2021
f902c5f
Ignore directory symlinks in file picker
sudormrfbin Aug 6, 2021
820b85e
Cleanup unnecessary pubs and derives
sudormrfbin Aug 6, 2021
a2e8a33
Remove unnecessary highlight from file picker
sudormrfbin Aug 7, 2021
805c0e0
Reorganize buffer rendering
sudormrfbin Aug 9, 2021
0bb5a13
Merge branch 'master' into fuzzy-finder-preview
sudormrfbin Aug 9, 2021
ecc857b
Use normal picker for code actions
sudormrfbin Aug 10, 2021
014cdd1
Remove unnecessary generics and trait impls
sudormrfbin Aug 11, 2021
bbd5071
Remove prepare_for_render and make render mutable
sudormrfbin Aug 12, 2021
a8d5832
Skip picker preview if screen small, less padding
sudormrfbin Aug 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use movement::Movement;

use crate::{
compositor::{self, Component, Compositor},
ui::{self, Popup, PreviewedPicker, Prompt, PromptEvent},
ui::{self, FilePicker, Popup, Prompt, PromptEvent},
};

use crate::job::{self, Job, Jobs};
Expand Down Expand Up @@ -2039,7 +2039,7 @@ fn file_picker(cx: &mut Context) {
fn buffer_picker(cx: &mut Context) {
let current = view!(cx.editor).doc;

let picker = PreviewedPicker::new(
let picker = FilePicker::new(
cx.editor
.documents
.iter()
Expand Down Expand Up @@ -2123,7 +2123,7 @@ fn symbol_picker(cx: &mut Context) {
}
};

let picker = PreviewedPicker::new(
let picker = FilePicker::new(
symbols,
|symbol| (&symbol.name).into(),
move |editor: &mut Editor, symbol, _action| {
Expand Down Expand Up @@ -2177,7 +2177,7 @@ pub fn code_action(cx: &mut Context) {
compositor: &mut Compositor,
response: Option<lsp::CodeActionResponse>| {
if let Some(actions) = response {
let picker = PreviewedPicker::new(
let picker = FilePicker::new(
actions,
|action| match action {
lsp::CodeActionOrCommand::CodeAction(action) => {
Expand Down Expand Up @@ -2525,7 +2525,7 @@ fn goto_impl(
editor.set_error("No definition found.".to_string());
}
_locations => {
let picker = PreviewedPicker::new(
let picker = FilePicker::new(
locations,
|location| {
let file = location.uri.as_str();
Expand Down
6 changes: 3 additions & 3 deletions helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use completion::Completion;
pub use editor::EditorView;
pub use markdown::Markdown;
pub use menu::Menu;
pub use picker::PreviewedPicker;
pub use picker::FilePicker;
pub use popup::Popup;
pub use prompt::{Prompt, PromptEvent};
pub use spinner::{ProgressSpinners, Spinner};
Expand Down Expand Up @@ -73,7 +73,7 @@ pub fn regex_prompt(
)
}

pub fn file_picker(root: PathBuf) -> PreviewedPicker<PathBuf> {
pub fn file_picker(root: PathBuf) -> FilePicker<PathBuf> {
use ignore::Walk;
use std::time;
let files = Walk::new(root.clone()).filter_map(|entry| match entry {
Expand Down Expand Up @@ -109,7 +109,7 @@ pub fn file_picker(root: PathBuf) -> PreviewedPicker<PathBuf> {

let files = files.into_iter().map(|(path, _)| path).collect();

PreviewedPicker::new(
FilePicker::new(
files,
move |path: &PathBuf| {
// format_fn
Expand Down
106 changes: 62 additions & 44 deletions helix-term/src/ui/picker.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::{
commands::{self, Align},
compositor::{Component, Compositor, Context, EventResult},
ui::EditorView,
};
Expand All @@ -16,63 +15,72 @@ use tui::widgets::Widget;
use std::{borrow::Cow, collections::HashMap, path::PathBuf};

use crate::ui::{Prompt, PromptEvent};
use helix_core::{hashmap, Position, Range, Selection};
use helix_core::{Position, Selection};
use helix_view::{
document::canonicalize_path,
editor::Action,
graphics::{Color, CursorKind, Rect, Style},
Document, Editor, View,
Document, Editor, View, ViewId,
};

pub struct PreviewedPicker<T> {
/// File path and line number
type FileLocation = (PathBuf, usize);

pub struct FilePicker<T> {
picker: Picker<T>,
// Caches paths to docs to line number to view
preview_cache: HashMap<PathBuf, (Document, HashMap<usize, View>)>,
#[allow(clippy::type_complexity)]
preview_fn: Box<dyn Fn(&Editor, &T) -> Option<(PathBuf, usize)>>,
/// Caches paths to documents
preview_cache: HashMap<PathBuf, Document>,
/// Given an item in the picker, return the file path and line number to display.
file_fn: Box<dyn Fn(&Editor, &T) -> Option<FileLocation>>,
// A view id to be shared by all documents in the cache. Mostly a hack since a doc
// requires at least one selection.
_preview_view_id: ViewId,
}

impl<T> PreviewedPicker<T> {
impl<T> FilePicker<T> {
pub fn new(
options: Vec<T>,
format_fn: impl Fn(&T) -> Cow<str> + 'static,
callback_fn: impl Fn(&mut Editor, &T, Action) + 'static,
preview_fn: impl Fn(&Editor, &T) -> Option<(PathBuf, usize)> + 'static,
preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static,
) -> Self {
Self {
picker: Picker::new(options, format_fn, callback_fn),
preview_cache: HashMap::new(),
preview_fn: Box::new(preview_fn),
file_fn: Box::new(preview_fn),
_preview_view_id: ViewId::default(),
}
}

fn calculate_preview(&mut self, editor: &Editor) {
if let Some((path, line)) = self
.picker
fn current_file(&self, editor: &Editor) -> Option<FileLocation> {
self.picker
.selection()
.and_then(|current| (self.preview_fn)(editor, current))
.and_then(|current| (self.file_fn)(editor, current))
.and_then(|(path, line)| canonicalize_path(&path).ok().zip(Some(line)))
{
let &mut (ref mut doc, ref mut range_map) =
self.preview_cache.entry(path.clone()).or_insert_with(|| {
let doc =
Document::open(path, None, Some(&editor.theme), Some(&editor.syn_loader))
.unwrap();
let view = View::new(doc.id());
(doc, hashmap!(line => view))
});
let view = range_map.entry(line).or_insert_with(|| View::new(doc.id()));

let range = Range::point(doc.text().line_to_char(line));
doc.set_selection(view.id, Selection::from(range));
// FIXME: gets aligned top instead of center
commands::align_view(doc, view, Align::Center);
}

fn calculate_preview(&mut self, editor: &Editor) {
if let Some((path, _line)) = self.current_file(editor) {
if !self.preview_cache.contains_key(&path) && editor.document_by_path(&path).is_none() {
let mut doc =
Document::open(&path, None, Some(&editor.theme), Some(&editor.syn_loader))
.unwrap();
// HACK: a doc needs atleast one selection
doc.set_selection(self._preview_view_id, Selection::point(0));
sudormrfbin marked this conversation as resolved.
Show resolved Hide resolved
self.preview_cache.insert(path, doc);
}
}
}
}

impl<T: 'static> Component for PreviewedPicker<T> {
impl<T: 'static> Component for FilePicker<T> {
fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
// |---------| |---------|
// |prompt | |preview |
// |---------| | |
// |picker | | |
// | | | |
// |---------| |---------|
sudormrfbin marked this conversation as resolved.
Show resolved Hide resolved
let area = inner_rect(area);
// -- Render the frame:
// clear area
Expand All @@ -96,29 +104,39 @@ impl<T: 'static> Component for PreviewedPicker<T> {

block.render(preview_area, surface);

if let Some((doc, view)) = self
.picker
.selection()
.and_then(|current| (self.preview_fn)(cx.editor, current))
.and_then(|(path, line)| canonicalize_path(&path).ok().zip(Some(line)))
.and_then(|(path, line)| {
self.preview_cache
.get(&path)
.and_then(|(doc, range_map)| Some((doc, range_map.get(&line)?)))
})
{
if let Some((doc, line)) = self.current_file(cx.editor).and_then(|(path, line)| {
cx.editor
.document_by_path(&path)
.or_else(|| self.preview_cache.get(&path))
.zip(Some(line))
}) {
// FIXME: last line will not be highlighted because of a -1 in View::last_line
let mut view = view.clone();
let mut view = View::new(doc.id());
view.id = if doc.selections().contains_key(&self._preview_view_id) {
self._preview_view_id // doc from cache
} else {
// Any view will do since we do not depend on doc selections for highlighting
*doc.selections().keys().next().unwrap() // doc from editor
};
view.first_col = 0;
// align to middle
view.first_line = line.saturating_sub(inner.height as usize / 2);
view.area = inner;
EditorView::render_doc(
doc,
&view,
inner,
surface,
&cx.editor.theme,
true, // is_focused
false, // is_focused
&cx.editor.syn_loader,
);
// highlight the line
for x in inner.left()..inner.right() {
surface
.get_mut(x, inner.y + line.saturating_sub(view.first_line) as u16)
.set_style(cx.editor.theme.get("ui.selection.primary"));
}
}
}

Expand Down
9 changes: 5 additions & 4 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,15 +431,16 @@ impl Document {
// TODO: async fn?
/// Create a new document from `path`. Encoding is auto-detected, but it can be manually
/// overwritten with the `encoding` parameter.
pub fn open(
path: PathBuf,
pub fn open<P: AsRef<Path>>(
sudormrfbin marked this conversation as resolved.
Show resolved Hide resolved
path: P,
encoding: Option<&'static encoding_rs::Encoding>,
theme: Option<&Theme>,
config_loader: Option<&syntax::Loader>,
) -> Result<Self, Error> {
let path = path.as_ref();
let (rope, encoding) = if path.exists() {
let mut file =
std::fs::File::open(&path).context(format!("unable to open {:?}", path))?;
std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
from_reader(&mut file, encoding)?
} else {
let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
Expand All @@ -449,7 +450,7 @@ impl Document {
let mut doc = Self::from(rope, Some(encoding));

// set the path and try detecting the language
doc.set_path(&path)?;
doc.set_path(path)?;
if let Some(loader) = config_loader {
doc.detect_language(theme, loader);
}
Expand Down
11 changes: 10 additions & 1 deletion helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ use crate::{
};

use futures_util::future;
use std::{path::PathBuf, sync::Arc, time::Duration};
use std::{
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};

use slotmap::SlotMap;

Expand Down Expand Up @@ -286,6 +290,11 @@ impl Editor {
self.documents.iter_mut().map(|(_id, doc)| doc)
}

pub fn document_by_path<P: AsRef<Path>>(&self, path: P) -> Option<&Document> {
self.documents()
.find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
}

// pub fn current_document(&self) -> Document {
// let id = self.view().doc;
// let doc = &mut editor.documents[id];
Expand Down