summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorStephan Dilly <dilly.stephan@gmail.com>2021-05-21 14:52:05 +0200
committerGitHub <noreply@github.com>2021-05-21 14:52:05 +0200
commit0e31d57a33c0e0d5028d5efc65f7794b66e2f139 (patch)
tree1de033a5862f56b217dee25ec0c37343cd95c2c2 /src
parentca35426c6c30e95f38948bf746f5288725248d93 (diff)
New file tree (#718)
Diffstat (limited to 'src')
-rw-r--r--src/components/filetree.rs5
-rw-r--r--src/components/mod.rs2
-rw-r--r--src/components/revision_files.rs130
-rw-r--r--src/keys.rs4
-rw-r--r--src/ui/style.rs14
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)