diff options
author | Stephan Dilly <dilly.stephan@gmail.com> | 2021-05-21 14:52:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-21 14:52:05 +0200 |
commit | 0e31d57a33c0e0d5028d5efc65f7794b66e2f139 (patch) | |
tree | 1de033a5862f56b217dee25ec0c37343cd95c2c2 /src | |
parent | ca35426c6c30e95f38948bf746f5288725248d93 (diff) |
New file tree (#718)
Diffstat (limited to 'src')
-rw-r--r-- | src/components/filetree.rs | 5 | ||||
-rw-r--r-- | src/components/mod.rs | 2 | ||||
-rw-r--r-- | src/components/revision_files.rs | 130 | ||||
-rw-r--r-- | src/keys.rs | 4 | ||||
-rw-r--r-- | src/ui/style.rs | 14 |
5 files changed, 139 insertions, 16 deletions
diff --git a/src/components/filetree.rs b/src/components/filetree.rs index 7dd9f589..c42dadd8 100644 --- a/src/components/filetree.rs +++ b/src/components/filetree.rs @@ -417,8 +417,9 @@ impl Component for FileTreeComponent { _ => Ok(EventState::NotConsumed), } } else if e == self.key_config.move_down { - Ok(self.move_selection(MoveSelection::Down)) - .map(Into::into) + Ok(self + .move_selection(MoveSelection::Down) + .into()) } else if e == self.key_config.move_up { Ok(self.move_selection(MoveSelection::Up).into()) } else if e == self.key_config.home diff --git a/src/components/mod.rs b/src/components/mod.rs index fb8c88eb..aebe1c65 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -24,6 +24,7 @@ mod tag_commit; mod textinput; mod utils; +pub use self::filetree::FileTreeComponent; pub use blame_file::BlameFileComponent; pub use branchlist::BranchListComponent; pub use changes::ChangesComponent; @@ -34,7 +35,6 @@ pub use commitlist::CommitList; pub use create_branch::CreateBranchComponent; pub use diff::DiffComponent; pub use externaleditor::ExternalEditorComponent; -pub use filetree::FileTreeComponent; pub use help::HelpComponent; pub use inspect_commit::InspectCommitComponent; pub use msg::MsgComponent; diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index 78a7d111..182864a0 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -1,3 +1,5 @@ +use std::{cell::Cell, collections::BTreeSet, convert::From}; + use super::{ visibility_blocking, CommandBlocking, CommandInfo, Component, DrawableComponent, EventState, @@ -15,15 +17,21 @@ use asyncgit::{ }; use crossbeam_channel::Sender; use crossterm::event::Event; +use filetree::{FileTree, MoveSelection}; use tui::{ backend::Backend, layout::Rect, text::Span, widgets::Clear, Frame, }; +const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}"; //▸ +const FOLDER_ICON_EXPANDED: &str = "\u{25be}"; //▾ +const EMPTY_STR: &str = ""; + pub struct RevisionFilesComponent { title: String, theme: SharedTheme, - // queue: Queue, files: Vec<TreeFile>, + tree: FileTree, + scroll_top: Cell<usize>, revision: Option<CommitId>, visible: bool, key_config: SharedKeyConfig, @@ -40,10 +48,11 @@ impl RevisionFilesComponent { ) -> Self { Self { title: String::new(), + tree: FileTree::default(), theme, + scroll_top: Cell::new(0), files: Vec::new(), revision: None, - // queue: queue.clone(), visible: false, key_config, current_height: std::cell::Cell::new(0), @@ -53,9 +62,16 @@ impl RevisionFilesComponent { /// pub fn open(&mut self, commit: CommitId) -> Result<()> { self.files = sync::tree_files(CWD, commit)?; + let filenames: Vec<&str> = self + .files + .iter() + .map(|f| f.path.to_str().unwrap_or_default()) + .collect(); + self.tree = FileTree::new(&filenames, &BTreeSet::new())?; + self.tree.collapse_but_root(); self.revision = Some(commit); self.title = format!( - "File Tree at {}", + "File Tree at [{}]", self.revision .map(|c| c.get_short_string()) .unwrap_or_default() @@ -64,6 +80,39 @@ impl RevisionFilesComponent { Ok(()) } + + fn tree_item_to_span<'a>( + item: &'a filetree::FileTreeItem, + theme: &SharedTheme, + selected: bool, + ) -> Span<'a> { + let path = item.info().path(); + let indent = item.info().indent(); + + let indent_str = if indent == 0 { + String::from("") + } else { + format!("{:w$}", " ", w = (indent as usize) * 2) + }; + + let is_path = item.kind().is_path(); + let path_arrow = if is_path { + if item.kind().is_path_collapsed() { + FOLDER_ICON_COLLAPSED + } else { + FOLDER_ICON_EXPANDED + } + } else { + EMPTY_STR + }; + + let path = format!("{}{}{}", indent_str, path_arrow, path); + Span::styled(path, theme.file_tree_item(is_path, selected)) + } + + fn move_selection(&mut self, dir: MoveSelection) -> bool { + self.tree.move_selection(dir) + } } impl DrawableComponent for RevisionFilesComponent { @@ -73,18 +122,45 @@ impl DrawableComponent for RevisionFilesComponent { area: Rect, ) -> Result<()> { if self.is_visible() { - let items = self.files.iter().map(|f| { - Span::styled( - f.path.to_string_lossy(), - self.theme.text(true, false), - ) - }); + let tree_height = + usize::from(area.height.saturating_sub(2)); + + let selection = self.tree.visual_selection(); + + selection.map_or_else( + || self.scroll_top.set(0), + |selection| { + self.scroll_top.set(ui::calc_scroll_top( + self.scroll_top.get(), + tree_height, + selection.index, + )) + }, + ); + + let items = self + .tree + .iterate(self.scroll_top.get(), tree_height) + .map(|(item, selected)| { + Self::tree_item_to_span( + item, + &self.theme, + selected, + ) + }); f.render_widget(Clear, area); ui::draw_list( f, area, &self.title, + // &format!( + // "{}/{} (height: {}) (top: {})", + // selection.index, + // selection.count, + // tree_height, + // self.scroll_top.get() + // ), items, true, &self.theme, @@ -123,11 +199,39 @@ impl Component for RevisionFilesComponent { ) -> Result<EventState> { if self.is_visible() { if let Event::Key(key) = event { - if key == self.key_config.exit_popup { + let consumed = if key == self.key_config.exit_popup { self.hide(); - } - - return Ok(EventState::Consumed); + true + } else if key == self.key_config.move_down { + self.move_selection(MoveSelection::Down) + } else if key == self.key_config.move_up { + self.move_selection(MoveSelection::Up) + } else if key == self.key_config.move_right { + self.move_selection(MoveSelection::Right) + } else if key == self.key_config.move_left { + self.move_selection(MoveSelection::Left) + } else if key == self.key_config.home + || key == self.key_config.shift_up + { + self.move_selection(MoveSelection::Top) + } else if key == self.key_config.end + || key == self.key_config.shift_down + { + self.move_selection(MoveSelection::End) + } else if key + == self.key_config.tree_collapse_recursive + { + self.tree.collapse_recursive(); + true + } else if key == self.key_config.tree_expand_recursive + { + self.tree.expand_recursive(); + true + } else { + false + }; + + return Ok(consumed.into()); } } diff --git a/src/keys.rs b/src/keys.rs index 7c4e46d4..aa223809 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -39,6 +39,8 @@ pub struct KeyConfig { pub open_help: KeyEvent, pub move_left: KeyEvent, pub move_right: KeyEvent, + pub tree_collapse_recursive: KeyEvent, + pub tree_expand_recursive: KeyEvent, pub home: KeyEvent, pub end: KeyEvent, pub move_up: KeyEvent, @@ -99,6 +101,8 @@ impl Default for KeyConfig { open_help: KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::empty()}, move_left: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::empty()}, move_right: KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::empty()}, + tree_collapse_recursive: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::SHIFT}, + tree_expand_recursive: KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::SHIFT}, home: KeyEvent { code: KeyCode::Home, modifiers: KeyModifiers::empty()}, end: KeyEvent { code: KeyCode::End, modifiers: KeyModifiers::empty()}, move_up: KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::empty()}, diff --git a/src/ui/style.rs b/src/ui/style.rs index 2a19e1f2..f62f3b4c 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -144,6 +144,20 @@ impl Theme { self.apply_select(style, selected) } + pub fn file_tree_item( + &self, + is_folder: bool, + selected: bool, + ) -> Style { + let style = if is_folder { + Style::default() + } else { + Style::default().fg(self.diff_file_modified) + }; + + self.apply_select(style, selected) + } + fn apply_select(&self, style: Style, selected: bool) -> Style { if selected { style.bg(self.selection_bg) |