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 14 commits
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
6 changes: 6 additions & 0 deletions helix-core/src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,12 @@ impl Selection {
}
}

impl From<Range> for Selection {
fn from(range: Range) -> Self {
Self::single(range.anchor, range.head)
}
}
sudormrfbin marked this conversation as resolved.
Show resolved Hide resolved

impl<'a> IntoIterator for &'a Selection {
type Item = &'a Range;
type IntoIter = std::slice::Iter<'a, Range>;
Expand Down
36 changes: 28 additions & 8 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, Picker, Popup, 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 = Picker::new(
let picker = FilePicker::new(
cx.editor
.documents
.iter()
Expand All @@ -2061,6 +2061,15 @@ fn buffer_picker(cx: &mut Context) {
|editor: &mut Editor, (id, _path): &(DocumentId, Option<PathBuf>), _action| {
editor.switch(*id, Action::Replace);
},
|editor, (id, path)| {
let doc = &editor.documents.get(*id)?;
let &view_id = doc.selections().keys().next()?;
let line = doc
.selection(view_id)
.primary()
.cursor_line(doc.text().slice(..));
Some((path.clone()?, line))
},
);
cx.push_layer(Box::new(picker));
}
Expand Down Expand Up @@ -2114,7 +2123,7 @@ fn symbol_picker(cx: &mut Context) {
}
};

let picker = Picker::new(
let picker = FilePicker::new(
symbols,
|symbol| (&symbol.name).into(),
move |editor: &mut Editor, symbol, _action| {
Expand All @@ -2124,10 +2133,17 @@ fn symbol_picker(cx: &mut Context) {
if let Some(range) =
lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
{
doc.set_selection(view.id, Selection::single(range.to(), range.from()));
doc.set_selection(view.id, Selection::from(range));
align_view(doc, view, Align::Center);
}
},
move |editor, symbol| {
let view = editor.tree.get(editor.tree.focus);
let doc = &editor.documents[view.doc];
doc.path()
.cloned()
.zip(Some(symbol.location.range.start.line as usize))
},
);
compositor.push(Box::new(picker))
}
Expand Down Expand Up @@ -2158,7 +2174,7 @@ pub fn code_action(cx: &mut Context) {
compositor: &mut Compositor,
response: Option<lsp::CodeActionResponse>| {
if let Some(actions) = response {
let picker = Picker::new(
let picker = FilePicker::new(
actions,
|action| match action {
lsp::CodeActionOrCommand::CodeAction(action) => {
Expand All @@ -2178,6 +2194,7 @@ pub fn code_action(cx: &mut Context) {
}
}
},
|_editor, _action| None,
);
compositor.push(Box::new(picker))
}
Expand Down Expand Up @@ -2505,7 +2522,7 @@ fn goto_impl(
editor.set_error("No definition found.".to_string());
}
_locations => {
let picker = ui::Picker::new(
let picker = FilePicker::new(
locations,
|location| {
let file = location.uri.as_str();
Expand All @@ -2515,6 +2532,10 @@ fn goto_impl(
move |editor: &mut Editor, location, action| {
jump_to(editor, location, offset_encoding, action)
},
|_editor, location| {
let path = location.uri.to_file_path().unwrap();
Some((path, location.range.start.line as usize))
},
);
compositor.push(Box::new(picker));
}
Expand Down Expand Up @@ -3472,8 +3493,7 @@ fn keep_primary_selection(cx: &mut Context) {
let (view, doc) = current!(cx.editor);

let range = doc.selection(view.id).primary();
let selection = Selection::single(range.anchor, range.head);
doc.set_selection(view.id, selection);
doc.set_selection(view.id, Selection::from(range));
}

fn completion(cx: &mut Context) {
Expand Down
7 changes: 5 additions & 2 deletions helix-term/src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub trait Component: Any + AnyComponent {
(None, CursorKind::Hidden)
}

fn prepare_for_render(&mut self, _ctx: &Context) {}

/// May be used by the parent component to compute the child area.
/// viewport is the maximum allowed area, and the child should stay within those bounds.
fn required_size(&mut self, _viewport: (u16, u16)) -> Option<(u16, u16)> {
Expand Down Expand Up @@ -137,8 +139,9 @@ impl Compositor {

let area = *surface.area();

for layer in &self.layers {
layer.render(area, surface, cx)
for layer in &mut self.layers {
layer.prepare_for_render(cx);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you just do this in render()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would require changing render() to take a mutable ref to self, since it calculates the preview and stores it in the cache. I didn't want to do that since having a render function that could potentially change the state seemed like questionable design; should I simply have render() take &mut self ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature of render already takes a mut: pub fn render(&mut self, cx: &mut Context) I think I changed that for some ui component previously for the same reasons.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant Component's render function actually; changed it to take &mut self anyway since the topmost render takes mutable reference anyway.

layer.render(area, surface, cx);
}

let (pos, kind) = self.cursor(area, cx.editor);
Expand Down
148 changes: 99 additions & 49 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub struct EditorView {
last_insert: (commands::Command, Vec<KeyEvent>),
completion: Option<Completion>,
spinners: ProgressSpinners,
pub autoinfo: Option<Info>,
autoinfo: Option<Info>,
}

const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
Expand Down Expand Up @@ -104,9 +104,10 @@ impl EditorView {
self.render_statusline(doc, view, area, surface, theme, is_focused);
}

/// Render a document into a Rect with syntax highlighting,
/// diagnostics, matching brackets and selections.
#[allow(clippy::too_many_arguments)]
pub fn render_buffer(
&self,
pub fn render_doc(
doc: &Document,
view: &View,
viewport: Rect,
Expand All @@ -116,9 +117,7 @@ impl EditorView {
loader: &syntax::Loader,
) {
let text = doc.text().slice(..);

let last_line = view.last_line(doc);

let range = {
// calculate viewport byte ranges
let start = text.line_to_byte(view.first_line);
Expand All @@ -128,7 +127,7 @@ impl EditorView {
};

// TODO: range doesn't actually restrict source, just highlight range
let highlights: Vec<_> = match doc.syntax() {
let highlights = match doc.syntax() {
Some(syntax) => {
let scopes = theme.scopes();
syntax
Expand All @@ -150,20 +149,16 @@ impl EditorView {
Some(config_ref)
})
})
.map(|event| event.unwrap())
.collect() // TODO: we collect here to avoid holding the lock, fix later
}
None => vec![Ok(HighlightEvent::Source {
None => vec![HighlightEvent::Source {
start: range.start,
end: range.end,
})],
};
let mut spans = Vec::new();
let mut visual_x = 0u16;
let mut line = 0u16;
let tab_width = doc.tab_width();
let tab = " ".repeat(tab_width);

let highlights = highlights.into_iter().map(|event| match event.unwrap() {
}],
}
.into_iter()
.map(|event| match event {
// convert byte offsets to char offset
HighlightEvent::Source { start, end } => {
let start = ensure_grapheme_boundary_next(text, text.byte_to_char(start));
Expand Down Expand Up @@ -250,6 +245,12 @@ impl EditorView {
.collect(),
));

let mut spans = Vec::new();
let mut visual_x = 0u16;
let mut line = 0u16;
let tab_width = doc.tab_width();
let tab = " ".repeat(tab_width);

'outer: for event in highlights {
match event {
HighlightEvent::HighlightStart(span) => {
Expand Down Expand Up @@ -323,7 +324,72 @@ impl EditorView {
}
}

// render gutters
if is_focused {
let screen = {
let start = text.line_to_char(view.first_line);
let end = text.line_to_char(last_line + 1) + 1; // +1 for cursor at end of text.
Range::new(start, end)
};

let selection = doc.selection(view.id);

for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
let head = view.screen_coords_at_pos(
doc,
text,
if selection.head > selection.anchor {
selection.head - 1
} else {
selection.head
},
);
if head.is_some() {
// TODO: set cursor position for IME
if let Some(syntax) = doc.syntax() {
use helix_core::match_brackets;
let pos = doc
.selection(view.id)
.primary()
.cursor(doc.text().slice(..));
let pos = match_brackets::find(syntax, doc.text(), pos)
.and_then(|pos| view.screen_coords_at_pos(doc, text, pos));

if let Some(pos) = pos {
// ensure col is on screen
if (pos.col as u16) < viewport.width + view.first_col as u16
&& pos.col >= view.first_col
{
let style = theme.try_get("ui.cursor.match").unwrap_or_else(|| {
Style::default()
.add_modifier(Modifier::REVERSED)
.add_modifier(Modifier::DIM)
});

surface
.get_mut(
viewport.x + pos.col as u16,
viewport.y + pos.row as u16,
)
.set_style(style);
}
}
}
}
}
}
}

#[allow(clippy::too_many_arguments)]
pub fn render_gutter(
doc: &Document,
view: &View,
viewport: Rect,
surface: &mut Surface,
theme: &Theme,
is_focused: bool,
) {
let text = doc.text().slice(..);
let last_line = view.last_line(doc);

let linenr: Style = theme.get("ui.linenr");
let warning: Style = theme.get("warning");
Expand Down Expand Up @@ -368,7 +434,7 @@ impl EditorView {
);
}

// render selections and selected linenr(s)
// render selected linenr(s)
let linenr_select: Style = theme
.try_get("ui.linenr.selected")
.unwrap_or_else(|| theme.get("ui.linenr"));
Expand Down Expand Up @@ -407,42 +473,26 @@ impl EditorView {
5,
linenr_select,
);

// TODO: set cursor position for IME
if let Some(syntax) = doc.syntax() {
use helix_core::match_brackets;
let pos = doc
.selection(view.id)
.primary()
.cursor(doc.text().slice(..));
let pos = match_brackets::find(syntax, doc.text(), pos)
.and_then(|pos| view.screen_coords_at_pos(doc, text, pos));

if let Some(pos) = pos {
// ensure col is on screen
if (pos.col as u16) < viewport.width + view.first_col as u16
&& pos.col >= view.first_col
{
let style = theme.try_get("ui.cursor.match").unwrap_or_else(|| {
Style::default()
.add_modifier(Modifier::REVERSED)
.add_modifier(Modifier::DIM)
});

surface
.get_mut(
viewport.x + pos.col as u16,
viewport.y + pos.row as u16,
)
.set_style(style);
}
}
}
}
}
}
}

#[allow(clippy::too_many_arguments)]
pub fn render_buffer(
&self,
doc: &Document,
view: &View,
viewport: Rect,
surface: &mut Surface,
theme: &Theme,
is_focused: bool,
loader: &syntax::Loader,
) {
Self::render_doc(doc, view, viewport, surface, theme, is_focused, loader);
Self::render_gutter(doc, view, viewport, surface, theme, is_focused);
}

pub fn render_diagnostics(
&self,
doc: &Document,
Expand Down
Loading