feat(explore): reveal current file

pull/9/head
wongjiahau 2 years ago
parent d04a1ce214
commit aa397ef801

@ -434,7 +434,7 @@ impl MappableCommand {
replay_macro, "Replay macro",
command_palette, "Open command pallete",
toggle_or_focus_explorer, "Toggle or focus explorer",
focus_current_file, "Focus current file in explorer",
reveal_current_file, "Reveal current file in explorer",
close_explorer, "close explorer",
);
}
@ -2232,16 +2232,16 @@ fn toggle_or_focus_explorer(cx: &mut Context) {
));
}
fn focus_current_file(cx: &mut Context) {
fn reveal_current_file(cx: &mut Context) {
cx.callback = Some(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.content.focus_current_file(cx),
Some(explore) => explore.content.reveal_current_file(cx),
None => match ui::Explorer::new(cx) {
Ok(explore) => {
let mut explorer = overlayed(explore);
explorer.content.focus_current_file(cx);
explorer.content.reveal_current_file(cx);
editor.explorer = Some(explorer);
}
Err(err) => cx.editor.set_error(format!("{}", err)),

@ -265,7 +265,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
"h" => select_references_to_symbol_under_cursor,
"?" => command_palette,
"e" => toggle_or_focus_explorer,
"E" => focus_current_file,
"E" => reveal_current_file,
},
"z" => { "View"
"z" | "c" => align_view_center,

@ -277,14 +277,32 @@ impl Explorer {
})
}
pub fn focus_current_file(&mut self, cx: &mut Context) {
pub fn reveal_current_file(&mut self, cx: &mut Context) {
let current_document_path = doc!(cx.editor).path().cloned();
match current_document_path {
None => cx.editor.set_error("No opened document."),
Some(path) => {
self.tree.focus_path(cx, path, &self.state.current_root);
Some(current_path) => {
let current_root = &self.state.current_root;
let current_path = current_path.as_path().to_string_lossy().to_string();
let current_root = current_root.as_path().to_string_lossy().to_string() + "/";
let segments = current_path
.strip_prefix(current_root.as_str())
.expect(
format!(
"Failed to strip prefix '{}' from '{}'",
current_root, current_path
)
.as_str(),
)
.split(std::path::MAIN_SEPARATOR)
.collect::<Vec<_>>();
match self.tree.reveal_item(segments) {
Ok(_) => {
self.focus();
}
Err(error) => cx.editor.set_error(error),
}
}
}
}
@ -798,20 +816,20 @@ impl Component for Explorer {
self.repeat_motion = Some(repeat_motion);
}
}
key!('b') => {
if let Some(p) = self.state.current_root.parent() {
match Self::get_items(p.to_path_buf(), cx) {
Ok(items) => {
self.state.current_root = p.to_path_buf();
let root = FileInfo::root(self.state.current_root.clone());
let children = root.get_children().expect("TODO: handle error");
self.tree = TreeView::build_tree(root, children)
.with_enter_fn(Self::toggle_current);
}
Err(e) => cx.editor.set_error(format!("{e}")),
}
}
}
// key!('b') => {
// if let Some(p) = self.state.current_root.parent() {
// match Self::get_items(p.to_path_buf(), cx) {
// Ok(items) => {
// self.state.current_root = p.to_path_buf();
// let root = FileInfo::root(self.state.current_root.clone());
// let children = root.get_children().expect("TODO: handle error");
// self.tree = TreeView::build_tree(root, children)
// .with_enter_fn(Self::toggle_current);
// }
// Err(e) => cx.editor.set_error(format!("{e}")),
// }
// }
// }
key!('f') => self.new_filter_prompt(),
key!('/') => self.new_search_prompt(true),
key!('?') => self.new_search_prompt(false),

@ -1,5 +1,3 @@
use std::iter::Peekable;
use std::slice::Iter;
use std::{cmp::Ordering, path::PathBuf};
use anyhow::Result;
@ -210,9 +208,16 @@ impl<T> Tree<T> {
pub struct TreeView<T: TreeItem> {
tree: Tree<T>,
recycle: Option<(String, Vec<Tree<T>>)>,
selected: usize, // select item index
save_view: (usize, usize), // (selected, row)
winline: usize, // view row
/// Selected item idex
selected: usize,
/// (selected, row)
save_view: (usize, usize),
/// View row
winline: usize,
area_height: usize,
col: usize,
max_len: usize,
count: usize,
@ -239,6 +244,7 @@ impl<T: TreeItem> TreeView<T> {
col: 0,
max_len: 0,
count: 0,
area_height: 0,
tree_symbol_style: "ui.text".into(),
pre_render: None,
on_opened_fn: None,
@ -292,66 +298,73 @@ impl<T: TreeItem> TreeView<T> {
}
}
/// TODO: current_path should not be PathBuf, but Vec<String> so that Tree can be generic
pub fn focus_path(&mut self, cx: &mut Context, current_path: PathBuf, current_root: &PathBuf) {
let current_path = current_path.as_path().to_string_lossy().to_string();
let current_root = current_root.as_path().to_string_lossy().to_string() + "/";
let nodes = current_path
.strip_prefix(current_root.as_str())
.expect(
format!(
"Failed to strip prefix '{}' from '{}'",
current_root, current_path
)
.as_str(),
)
.split(std::path::MAIN_SEPARATOR)
.enumerate()
.collect::<Vec<(usize, &str)>>();
let len = nodes.len();
// `preivous_item_index` is necessary to avoid choosing the first file
// that is not the current file.
// For example, consider a project that contains multiple `Cargo.toml`.
// Without `previous_item_index`, the first `Cargo.toml` will always be chosen,
// regardless of which `Cargo.toml` the user wishes to find in the explorer.
// let mut previous_item_index = 0;
// for (index, node) in nodes {
// let current_level = index + 1;
// let is_last = index == len - 1;
// match self
// .items
// .iter()
// .enumerate()
// .position(|(item_index, item)| {
// item_index >= previous_item_index
// && item.item.text_string().eq(node)
// && item.level == current_level
// }) {
// Some(index) => {
// if is_last {
// self.selected = index
// } else {
// let item = &self.items[index];
// let items = match item.item.get_childs() {
// Ok(items) => items,
// Err(e) => return cx.editor.set_error(format!("{e}")),
// };
// let inserts = vec_to_tree(items, current_level + 1);
// previous_item_index = index;
// let _: Vec<_> = self.items.splice(index + 1..index + 1, inserts).collect();
// }
// }
// None => cx.editor.set_error(format!(
// "The following file does not exist anymore: '{}'. node = {}",
// current_path, node
// )),
// }
// }
/// Reveal item in the tree based on the given `segments`.
///
/// The name of the root should be excluded.
///
/// Example `segments`:
/// ```
/// vec!["helix-term", "src", "ui", "tree.rs"]
/// ```
pub fn reveal_item(&mut self, segments: Vec<&str>) -> Result<(), String> {
// Expand the tree
segments.iter().fold(
Ok(&mut self.tree),
|current_tree, segment| match current_tree {
Err(err) => Err(err),
Ok(current_tree) => {
match current_tree
.children
.iter_mut()
.find(|tree| tree.item.text_string().eq(segment))
{
Some(tree) => {
if !tree.is_opened {
tree.children = vec_to_tree(
tree.item.get_children().map_err(|err| err.to_string())?,
);
if !tree.children.is_empty() {
tree.is_opened = true;
}
}
Ok(tree)
}
None => Err(format!(
"Unable to find path: '{}'. current_segment = {}",
segments.join("/"),
segment
)),
}
}
},
)?;
// Locate the item
self.regenerate_index();
self.selected = segments
.iter()
.fold(&self.tree, |tree, segment| {
tree.children
.iter()
.find(|tree| tree.item.text_string().eq(segment))
.expect("Should be unreachable")
})
.index;
// Center the selection
self.winline = self.max_len / 2;
self.align_view_center();
Ok(())
}
fn align_view_center(&mut self) {
self.winline = self.area_height / 2
}
fn align_view_top(&mut self) {
self.winline = 0
}
fn align_view_bottom(&mut self) {
self.winline = self.area_height
}
fn regenerate_index(&mut self) {
@ -626,11 +639,14 @@ impl<T: TreeItem> TreeView<T> {
}
self.max_len = 0;
self.winline = std::cmp::min(self.winline, area.height.saturating_sub(1) as usize);
self.area_height = area.height.saturating_sub(1) as usize;
self.winline = std::cmp::min(self.winline, self.area_height);
let style = cx.editor.theme.get(&self.tree_symbol_style);
let last_item_index = self.tree.len().saturating_sub(1);
let skip = self.selected.saturating_sub(self.winline);
cx.editor.set_error(format!("winline = {}", self.winline));
let params = RenderElemParams {
tree: &self.tree,
prefix: &"".to_string(),
@ -661,8 +677,6 @@ impl<T: TreeItem> TreeView<T> {
selected: usize,
}
cx.editor.set_error(format!("seleted = {}", self.selected));
fn render_tree<T: TreeItem>(
RenderElemParams {
tree,
@ -822,7 +836,15 @@ impl<T: TreeItem> TreeView<T> {
key!(i @ '0'..='9') => self.count = i.to_digit(10).unwrap() as usize + count * 10,
key!('k') | shift!(Tab) | key!(Up) | ctrl!('k') => self.move_up(1.max(count)),
key!('j') | key!(Tab) | key!(Down) | ctrl!('j') => self.move_down(1.max(count)),
key!('z') => self.fold_current_child(),
key!('z') => {
self.on_next_key = Some(Box::new(|_, tree, event| match event.into() {
key!('f') => tree.fold_current_child(),
key!('z') => tree.align_view_center(),
key!('t') => tree.align_view_top(),
key!('b') => tree.align_view_bottom(),
_ => {}
}));
}
key!('h') => self.go_to_parent(),
key!('l') => self.go_to_children(cx),
key!(Enter) => self.on_enter(cx, params, self.selected),

Loading…
Cancel
Save