From a259c205c0fe8a4271f20862989f53b282bb8ba9 Mon Sep 17 00:00:00 2001 From: wongjiahau Date: Wed, 22 Feb 2023 10:05:54 +0800 Subject: [PATCH] fix(explore): help overflow - render with Info --- changes | 21 +++-- helix-term/src/ui/explore.rs | 168 +++++++++++++++-------------------- helix-term/src/ui/tree.rs | 81 +++-------------- 3 files changed, 97 insertions(+), 173 deletions(-) diff --git a/changes b/changes index c3e5b379..f2ce7add 100644 --- a/changes +++ b/changes @@ -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 diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index 2c030512..f5d1982c 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -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 EventResult>>, - #[allow(clippy::type_complexity)] - repeat_motion: Option>, 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::>(); - 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::>() - } 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::>(), + ) + .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)? diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs index 8405af7c..fb52d0ba 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -268,7 +268,7 @@ impl Tree { 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 Tree { .collect(); self.regenerate_index() } - - pub fn parent_index(&self) -> Option { - self.parent_index - } - - pub fn index(&self) -> usize { - self.index - } } #[derive(Clone, Debug)] struct SavedView { selected: usize, - winline: usize, } pub struct TreeView { @@ -397,7 +388,7 @@ impl TreeView { /// 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 TreeView { } } - 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 TreeView { 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 TreeView { fn saved_view(&self) -> SavedView { self.saved_view.clone().unwrap_or_else(|| SavedView { selected: self.selected, - winline: self.winline, }) } @@ -689,17 +684,9 @@ impl TreeView { 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 { self.tree .get(index) @@ -735,47 +722,6 @@ impl TreeView { 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 TreeView { } } + #[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 TreeView { 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 TreeView { 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();