fix(explore): help overflow

- render with Info
pull/9/head
wongjiahau 2 years ago
parent 2e7709e505
commit a259c205c0

@ -27,18 +27,23 @@ TODO
- [x] Remove comments
- [x] fix warnings
- [x] refactor, add tree.expand_children() method
- [] Change '[' to "go to previous root"
- [] Change 'b' to "go to parent"
- [] Use C-o for jumping to previous position
- [] add integration testing (test explorer rendering)
New:
- [x] Change '[' to "go to previous root"
- [x] Change 'b' to "go to parent"
- [x] Use C-o for jumping to previous position
- [x] on focus indication
- [x] support creating files and folder and the same time (`mkdir -p`)
- [x] Ctrl-o should work for 'h', 'gg', 'ge', etc
- [x] add unit test for TreeView
- [x] explorer(help): overflow
- [] add integration test for Explorer
- [] search highlight matching word
- [] Error didn't clear
- [] bind "o" to open/close file/folder
- [] on focus indication
- [] should preview be there by default?
- [] support creating files and folder and the same time (`mkdir -p`)
- [] Fix panic bugs (see github comments)
- [] Sticky ancestors
- [] Ctrl-o should work for 'h', 'gg', 'ge', etc
- [] explorer(previow): overflow where bufferline is there
- [] explorer(preview): overflow where bufferline is there
- [] explorer(preview): content not sorted
- [] explorer(preview): implement scrolling C-j/C-k

@ -8,6 +8,7 @@ use helix_core::Position;
use helix_view::{
editor::{Action, ExplorerPositionEmbed},
graphics::{CursorKind, Rect},
info::Info,
input::{Event, KeyEvent},
theme::Modifier,
DocumentId, Editor,
@ -158,8 +159,6 @@ pub struct Explorer {
prompt: Option<(PromptAction, Prompt)>,
#[allow(clippy::type_complexity)]
on_next_key: Option<Box<dyn FnMut(&mut Context, &mut Self, &KeyEvent) -> EventResult>>,
#[allow(clippy::type_complexity)]
repeat_motion: Option<Box<dyn FnMut(&mut Self, PromptAction, &mut Context) + 'static>>,
column_width: u16,
}
@ -169,9 +168,8 @@ impl Explorer {
Ok(Self {
tree: Self::new_tree_view(current_root.clone())?,
history: vec![],
show_help: false,
show_help: true,
state: State::new(true, current_root),
repeat_motion: None,
prompt: None,
on_next_key: None,
column_width: cx.editor.config().explorer.column_width as u16,
@ -251,58 +249,21 @@ impl Explorer {
}
fn render_preview(&mut self, area: Rect, surface: &mut Surface, editor: &Editor) {
// if area.height <= 2 || area.width < 60 {
// return;
// }
let item = self.tree.current().item();
let head_area = render_block(area.clip_bottom(area.height - 2), surface, Borders::BOTTOM);
let path_str = format!("{}", item.path.display());
surface.set_stringn(
head_area.x,
head_area.y,
if self.show_help {
"[HELP]".to_string()
} else {
path_str
},
path_str,
head_area.width as usize,
get_theme!(editor.theme, "ui.explorer.dir", "ui.text"),
);
let body_area = area.clip_top(2);
let style = editor.theme.get("ui.text");
let content = if self.show_help {
let instructions = vec![
("?", "Toggle help"),
("a", "Add file"),
("A", "Add folder"),
("r", "Rename file/folder"),
("d", "Delete file"),
("b", "Change root to parent folder"),
("]", "Change root to current folder"),
("[", "Go to previous root"),
("+", "Increase size"),
("-", "Decrease size"),
("q", "Close"),
]
.into_iter()
.chain(ui::tree::tree_view_help().into_iter())
.collect::<Vec<_>>();
let max_left_length = instructions
.iter()
.map(|(key, _)| key.chars().count())
.max()
.unwrap_or(0);
instructions
.into_iter()
.map(|(key, description)| {
format!("{:width$}{}", key, description, width = max_left_length + 1)
})
.collect::<Vec<_>>()
} else {
get_preview(&item.path, body_area.height as usize)
.unwrap_or_else(|err| vec![err.to_string()])
};
let content = get_preview(&item.path, body_area.height as usize)
.unwrap_or_else(|err| vec![err.to_string()]);
content.into_iter().enumerate().for_each(|(row, line)| {
surface.set_stringn(
body_area.x,
@ -465,7 +426,11 @@ impl Explorer {
prompt.render(promp_area, surface, cx);
preview_area = area;
}
self.render_preview(preview_area, surface, cx.editor);
if self.show_help {
self.render_help(preview_area, surface, cx);
} else {
self.render_preview(preview_area, surface, cx.editor);
}
let list_area = render_block(area.clip_right(preview_area.width), surface, Borders::RIGHT);
self.render_tree(list_area, surface, cx)
@ -542,33 +507,43 @@ impl Explorer {
}
if self.is_focus() {
const PREVIEW_AREA_MAX_WIDTH: u16 = 90;
const PREVIEW_AREA_MAX_HEIGHT: u16 = 30;
let preview_area_width = (area.width - side_area.width).min(PREVIEW_AREA_MAX_WIDTH);
let preview_area_height = area.height.min(PREVIEW_AREA_MAX_HEIGHT);
let preview_area = match position {
ExplorerPositionEmbed::Left => area.clip_left(side_area.width),
ExplorerPositionEmbed::Right => (Rect {
x: area.width - side_area.width - preview_area_width,
..area
})
.clip_right(side_area.width),
}
.clip_bottom(2);
if preview_area.width < 30 || preview_area.height < 3 {
return;
}
let y = self.tree.winline().saturating_sub(1) as u16;
let y = if (preview_area_height + y) > preview_area.height {
preview_area.height - preview_area_height
if self.show_help {
let help_area = match position {
ExplorerPositionEmbed::Left => area,
ExplorerPositionEmbed::Right => {
area.clip_right(list_area.width.saturating_add(2))
}
};
self.render_help(help_area, surface, cx);
} else {
y
};
let area = Rect::new(preview_area.x, y, preview_area_width, preview_area_height);
surface.clear_with(area, background);
let area = render_block(area, surface, Borders::all());
self.render_preview(area, surface, cx.editor);
const PREVIEW_AREA_MAX_WIDTH: u16 = 90;
const PREVIEW_AREA_MAX_HEIGHT: u16 = 30;
let preview_area_width = (area.width - side_area.width).min(PREVIEW_AREA_MAX_WIDTH);
let preview_area_height = area.height.min(PREVIEW_AREA_MAX_HEIGHT);
let preview_area = match position {
ExplorerPositionEmbed::Left => area.clip_left(side_area.width),
ExplorerPositionEmbed::Right => (Rect {
x: area.width - side_area.width - preview_area_width,
..area
})
.clip_right(side_area.width),
}
.clip_bottom(2);
if preview_area.width < 30 || preview_area.height < 3 {
return;
}
let y = self.tree.winline().saturating_sub(1) as u16;
let y = if (preview_area_height + y) > preview_area.height {
preview_area.height - preview_area_height
} else {
y
};
let area = Rect::new(preview_area.x, y, preview_area_width, preview_area_height);
surface.clear_with(area, background);
let area = render_block(area, surface, Borders::all());
self.render_preview(area, surface, cx.editor);
}
}
if let Some((_, prompt)) = self.prompt.as_mut() {
@ -576,30 +551,27 @@ impl Explorer {
}
}
fn handle_filter_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
let (action, mut prompt) = self.prompt.take().unwrap();
(|| -> Result<()> {
match event {
key!(Enter) => {
if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) {
self.tree.refresh(prompt.line())?;
}
}
key!(Esc) | ctrl!('c') => {
self.state.filter.clear();
}
_ => {
if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) {
self.tree.refresh(prompt.line())?;
}
self.state.filter = prompt.line().clone();
self.prompt = Some((action, prompt));
}
};
Ok(())
})()
.unwrap_or_else(|err| cx.editor.set_error(format!("{err}")));
EventResult::Consumed(None)
fn render_help(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
Info::new(
"Explorer",
&[
("?", "Toggle help"),
("a", "Add file"),
("A", "Add folder"),
("r", "Rename file/folder"),
("d", "Delete file"),
("b", "Change root to parent folder"),
("]", "Change root to current folder"),
("[", "Go to previous root"),
("+", "Increase size"),
("-", "Decrease size"),
("q", "Close"),
]
.into_iter()
.chain(ui::tree::tree_view_help().into_iter())
.collect::<Vec<_>>(),
)
.render(area, surface, cx)
}
fn handle_prompt_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
@ -624,14 +596,14 @@ impl Explorer {
if line == "y" {
let item = explorer.tree.current_item();
std::fs::remove_dir_all(&item.path)?;
explorer.tree.remove_current();
explorer.tree.refresh()?;
}
}
(PromptAction::RemoveFile(document_id), key!(Enter)) => {
if line == "y" {
let item = explorer.tree.current_item();
std::fs::remove_file(&item.path).map_err(anyhow::Error::from)?;
explorer.tree.remove_current();
explorer.tree.refresh()?;
if let Some(id) = document_id {
cx.editor.close_document(*id, true)?
}
@ -640,7 +612,7 @@ impl Explorer {
(PromptAction::RenameFile(document_id), key!(Enter)) => {
let item = explorer.tree.current_item();
std::fs::rename(&item.path, line)?;
explorer.tree.remove_current();
explorer.tree.refresh()?;
explorer.reveal_file(PathBuf::from(line))?;
if let Some(id) = document_id {
cx.editor.close_document(*id, true)?

@ -268,7 +268,7 @@ impl<T> Tree<T> {
self.children = index_elems(0, items);
}
fn remove(&mut self, index: usize) {
pub fn remove(&mut self, index: usize) {
let children = std::mem::replace(&mut self.children, vec![]);
self.children = children
.into_iter()
@ -286,20 +286,11 @@ impl<T> Tree<T> {
.collect();
self.regenerate_index()
}
pub fn parent_index(&self) -> Option<usize> {
self.parent_index
}
pub fn index(&self) -> usize {
self.index
}
}
#[derive(Clone, Debug)]
struct SavedView {
selected: usize,
winline: usize,
}
pub struct TreeView<T: TreeViewItem> {
@ -397,7 +388,7 @@ impl<T: TreeViewItem> TreeView<T> {
/// vec!["helix-term", "src", "ui", "tree.rs"]
/// ```
pub fn reveal_item(&mut self, segments: Vec<&str>, filter: &String) -> Result<()> {
self.refresh(filter)?;
self.refresh_with_filter(filter)?;
// Expand the tree
segments.iter().fold(
@ -482,7 +473,11 @@ impl<T: TreeViewItem> TreeView<T> {
}
}
pub fn refresh(&mut self, filter: &String) -> Result<()> {
pub fn refresh(&mut self) -> Result<()> {
self.refresh_with_filter(&self.filter.clone())
}
fn refresh_with_filter(&mut self, filter: &String) -> Result<()> {
self.tree.refresh(filter)?;
self.set_selected(self.selected);
Ok(())
@ -496,6 +491,7 @@ impl<T: TreeViewItem> TreeView<T> {
self.move_down(usize::MAX / 2)
}
#[cfg(test)]
fn set_previous_area(&mut self, area: Rect) {
self.previous_area = area
}
@ -580,7 +576,6 @@ impl<T: TreeViewItem> TreeView<T> {
fn saved_view(&self) -> SavedView {
self.saved_view.clone().unwrap_or_else(|| SavedView {
selected: self.selected,
winline: self.winline,
})
}
@ -689,17 +684,9 @@ impl<T: TreeViewItem> TreeView<T> {
fn save_view(&mut self) {
self.saved_view = Some(SavedView {
selected: self.selected,
winline: self.winline,
})
}
fn restore_view(&mut self) {
SavedView {
selected: self.selected,
winline: self.winline,
} = self.saved_view();
}
fn get(&self, index: usize) -> &Tree<T> {
self.tree
.get(index)
@ -735,47 +722,6 @@ impl<T: TreeViewItem> TreeView<T> {
pub fn winline(&self) -> usize {
self.winline
}
pub fn remove_current(&mut self) {
self.tree.remove(self.selected);
self.set_selected(self.selected.min(self.tree.len().saturating_sub(1)));
}
pub fn replace_current(&mut self, item: T) {
self.current_mut().item = item
}
pub fn add_child(&mut self, index: usize, item: T, filter: &String) -> Result<()> {
match self.tree.get_mut(index) {
None => Err(anyhow::anyhow!(format!(
"No item found at index = {}",
index
))),
Some(tree) => {
let item_name = item.name();
if !tree.is_opened {
tree.open(filter)?;
} else {
tree.refresh(filter)?;
}
self.regenerate_index();
let tree = self.get(index);
// Focus the added sibling
if let Some(tree) = tree
.children
.iter()
.find(|tree| tree.item.name().eq(&item_name))
{
let index = tree.index;
self.set_selected(index)
};
Ok(())
}
}
}
}
struct RenderedLine {
@ -908,6 +854,7 @@ impl<T: TreeViewItem + Clone> TreeView<T> {
}
}
#[cfg(test)]
fn render_to_string(&mut self, filter: &String) -> String {
let area = self.previous_area;
let lines = self.render_lines(area, filter);
@ -1052,7 +999,7 @@ impl<T: TreeViewItem + Clone> TreeView<T> {
key!(PageUp) => self.move_up_page(),
shift!('R') => {
let filter = self.filter.clone();
if let Err(error) = self.refresh(&filter) {
if let Err(error) = self.refresh_with_filter(&filter) {
cx.editor.set_error(error.to_string())
}
}
@ -1073,18 +1020,18 @@ impl<T: TreeViewItem + Clone> TreeView<T> {
if let EventResult::Consumed(_) =
prompt.handle_event(&Event::Key(*event), cx)
{
self.refresh(prompt.line())?;
self.refresh_with_filter(prompt.line())?;
}
}
key!(Esc) | ctrl!('c') => {
self.filter.clear();
self.refresh(&"".to_string())?;
self.refresh_with_filter(&"".to_string())?;
}
_ => {
if let EventResult::Consumed(_) =
prompt.handle_event(&Event::Key(*event), cx)
{
self.refresh(prompt.line())?;
self.refresh_with_filter(prompt.line())?;
}
self.filter = prompt.line().clone();
self.filter_prompt = Some(prompt);
@ -1960,7 +1907,7 @@ krabby_patty
);
// 2. Refreshes the tree with a filter that will remove the last child
view.refresh(&"ar".to_string()).unwrap();
view.refresh_with_filter(&"ar".to_string()).unwrap();
// 3. Get the current item
let item = view.current_item();

Loading…
Cancel
Save