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

feat(file-tree): integrate the file tree from pinelang/helix-tree-explorer #2

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 book/src/keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ This layer is a kludge of mappings, mostly pickers.
| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
| `/` | Global search in workspace folder | `global_search` |
| `?` | Open command palette | `command_palette` |
| `e` | Reveal current file in explorer | `reveal_current_file` |


> 💡 Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.

Expand Down Expand Up @@ -479,3 +481,7 @@ Keys to use within prompt, Remapping currently not supported.
| `Tab` | Select next completion item |
| `BackTab` | Select previous completion item |
| `Enter` | Open selected |

# File explorer

Press `?` to see keymaps. Remapping currently not supported.
55 changes: 55 additions & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ impl MappableCommand {
command_palette, "Open command palette",
goto_word, "Jump to a two-character label",
extend_to_word, "Extend to a two-character label",
open_or_focus_explorer, "Open or focus explorer",
reveal_current_file, "Reveal current file in explorer",
);
}

Expand Down Expand Up @@ -2902,6 +2904,59 @@ fn file_picker_in_current_directory(cx: &mut Context) {
cx.push_layer(Box::new(overlaid(picker)));
}

fn open_or_focus_explorer(cx: &mut Context) {
let mut callbacks: Vec<Box<dyn FnOnce(&mut Compositor, &mut compositor::Context<'_>)>> =
Vec::with_capacity(1);

callbacks.push(Box::new(
|compositor: &mut Compositor, cx: &mut compositor::Context| {
if let Some(editor) = compositor.find::<ui::EditorView>() {
match editor.explorer.as_mut() {
Some(explore) => explore.focus(),
None => match ui::Explorer::new(cx) {
Ok(explore) => editor.explorer = Some(explore),
Err(err) => cx.editor.set_error(format!("{}", err)),
},
}
}
},
));

cx.callback = callbacks;
}

fn reveal_file(cx: &mut Context, path: Option<PathBuf>) {
let mut callbacks: Vec<Box<dyn FnOnce(&mut Compositor, &mut compositor::Context<'_>)>> =
Vec::with_capacity(1);

callbacks.push(Box::new(
|compositor: &mut Compositor, cx: &mut compositor::Context| {
if let Some(editor) = compositor.find::<ui::EditorView>() {
(|| match editor.explorer.as_mut() {
Some(explorer) => match path {
Some(path) => explorer.reveal_file(path),
None => explorer.reveal_current_file(cx),
},
None => {
editor.explorer = Some(ui::Explorer::new(cx)?);
if let Some(explorer) = editor.explorer.as_mut() {
explorer.reveal_current_file(cx)?;
}
Ok(())
}
})()
.unwrap_or_else(|err| cx.editor.set_error(err.to_string()))
}
},
));

cx.callback = callbacks;
}

fn reveal_current_file(cx: &mut Context) {
reveal_file(cx, None)
}

fn buffer_picker(cx: &mut Context) {
let current = view!(cx.editor).doc;

Expand Down
62 changes: 62 additions & 0 deletions helix-term/src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,52 @@ impl<'a> Context<'a> {
tokio::task::block_in_place(|| helix_lsp::block_on(self.editor.flush_writes()))?;
Ok(())
}

/// Purpose: to test `handle_event` without escalating the test case to integration test
/// Usage:
/// ```
/// let mut editor = Context::dummy_editor();
/// let mut jobs = Context::dummy_jobs();
/// let mut cx = Context::dummy(&mut jobs, &mut editor);
/// ```
#[cfg(test)]
pub fn dummy(jobs: &'a mut Jobs, editor: &'a mut helix_view::Editor) -> Context<'a> {
Context {
jobs,
scroll: None,
editor,
}
}

#[cfg(test)]
pub fn dummy_jobs() -> Jobs {
Jobs::new()
}

#[cfg(test)]
pub fn dummy_editor() -> Editor {
// TODO
todo!()
/*use crate::config::Config;
use arc_swap::{access::Map, ArcSwap};
use helix_core::syntax::{self, Configuration};
use helix_view::theme;
use std::{collections::HashMap, sync::Arc};

let config = Arc::new(ArcSwap::from_pointee(Config::default()));
Editor::new(
Rect::new(0, 0, 60, 120),
Arc::new(theme::Loader::new(&[])),
Arc::new(syntax::Loader::new(Configuration {
language: vec![],
language_server: HashMap::new(),
})),
Arc::new(Arc::new(Map::new(
Arc::clone(&config),
|config: &Config| &config.editor,
))),
)*/
}
}

pub trait Component: Any + AnyComponent {
Expand Down Expand Up @@ -73,6 +119,22 @@ pub trait Component: Any + AnyComponent {
fn id(&self) -> Option<&'static str> {
None
}

#[cfg(test)]
/// Utility method for testing `handle_event` without using integration test.
/// Especially useful for testing helper components such as `Prompt`, `TreeView` etc
fn handle_events(&mut self, events: &str) -> anyhow::Result<()> {
// TODO
/*use helix_view::input::parse_macro;

let mut editor = Context::dummy_editor();
let mut jobs = Context::dummy_jobs();
let mut cx = Context::dummy(&mut jobs, &mut editor);
for event in parse_macro(events)? {
self.handle_event(&Event::Key(event), &mut cx);
}*/
Ok(())
}
}

pub struct Compositor {
Expand Down
1 change: 1 addition & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"C" => toggle_block_comments,
"A-c" => toggle_line_comments,
"?" => command_palette,
"e" => reveal_current_file,
},
"z" => { "View"
"z" | "c" => align_view_center,
Expand Down
70 changes: 63 additions & 7 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
keymap::{KeymapResult, Keymaps},
ui::{
document::{render_document, LinePos, TextRenderer, TranslatedPosition},
Completion, ProgressSpinners,
Completion, Explorer, ProgressSpinners,
},
};

Expand All @@ -21,7 +21,7 @@ use helix_core::{
};
use helix_view::{
document::{Mode, SavePoint, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig},
editor::{CompleteAction, CursorShapeConfig, ExplorerPosition},
graphics::{Color, CursorKind, Modifier, Rect, Style},
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers},
Expand All @@ -43,6 +43,7 @@ pub struct EditorView {
spinners: ProgressSpinners,
/// Tracks if the terminal window is focused by reaction to terminal focus events
terminal_focused: bool,
pub(crate) explorer: Option<Explorer>,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -72,6 +73,7 @@ impl EditorView {
completion: None,
spinners: ProgressSpinners::default(),
terminal_focused: true,
explorer: None,
}
}

Expand Down Expand Up @@ -1309,6 +1311,11 @@ impl Component for EditorView {
event: &Event,
context: &mut crate::compositor::Context,
) -> EventResult {
if let Some(explore) = self.explorer.as_mut() {
if let EventResult::Consumed(callback) = explore.handle_event(event, context) {
return EventResult::Consumed(callback);
}
}
let mut cx = commands::Context {
editor: context.editor,
count: None,
Expand Down Expand Up @@ -1466,6 +1473,8 @@ impl Component for EditorView {
surface.set_style(area, cx.editor.theme.get("ui.background"));
let config = cx.editor.config();

let editor_area = area.clip_bottom(1);

// check if bufferline should be rendered
use helix_view::editor::BufferLine;
let use_bufferline = match config.bufferline {
Expand All @@ -1474,15 +1483,43 @@ impl Component for EditorView {
_ => false,
};

// -1 for commandline and -1 for bufferline
let mut editor_area = area.clip_bottom(1);
if use_bufferline {
editor_area = editor_area.clip_top(1);
}
let editor_area = if use_bufferline {
editor_area.clip_top(1)
} else {
editor_area
};

let editor_area = if let Some(explorer) = &self.explorer {
let explorer_column_width = if explorer.is_opened() {
explorer.column_width().saturating_add(2)
} else {
0
};
// For future developer:
// We should have a Dock trait that allows a component to dock to the top/left/bottom/right
// of another component.
match config.explorer.position {
ExplorerPosition::Left => editor_area.clip_left(explorer_column_width),
ExplorerPosition::Right => editor_area.clip_right(explorer_column_width),
}
} else {
editor_area
};

// if the terminal size suddenly changed, we need to trigger a resize
cx.editor.resize(editor_area);

if let Some(explorer) = self.explorer.as_mut() {
if !explorer.is_focus() {
let area = if use_bufferline {
area.clip_top(1)
} else {
area
};
explorer.render(area, surface, cx);
}
}

if use_bufferline {
Self::render_bufferline(cx.editor, area.with_height(1), surface);
}
Expand Down Expand Up @@ -1561,9 +1598,28 @@ impl Component for EditorView {
if let Some(completion) = self.completion.as_mut() {
completion.render(area, surface, cx);
}

if let Some(explore) = self.explorer.as_mut() {
if explore.is_focus() {
let area = if use_bufferline {
area.clip_top(1)
} else {
area
};
explore.render(area, surface, cx);
}
}
}

fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
if let Some(explore) = &self.explorer {
if explore.is_focus() {
let cursor = explore.cursor(_area, editor);
if cursor.0.is_some() {
return cursor;
}
}
}
match editor.cursor() {
// all block cursors are drawn manually
(pos, CursorKind::Block) => {
Expand Down
Loading
Loading