summaryrefslogtreecommitdiffstats
path: root/src/components/filetree.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/filetree.rs')
-rw-r--r--src/components/filetree.rs1050
1 files changed, 525 insertions, 525 deletions
diff --git a/src/components/filetree.rs b/src/components/filetree.rs
index ba2a2f9c..e990fe9c 100644
--- a/src/components/filetree.rs
+++ b/src/components/filetree.rs
@@ -1,17 +1,17 @@
use super::{
- utils::{
- filetree::{FileTreeItem, FileTreeItemKind},
- statustree::{MoveSelection, StatusTree},
- },
- CommandBlocking, DrawableComponent,
+ utils::{
+ filetree::{FileTreeItem, FileTreeItemKind},
+ statustree::{MoveSelection, StatusTree},
+ },
+ CommandBlocking, DrawableComponent,
};
use crate::{
- components::{CommandInfo, Component, EventState},
- keys::SharedKeyConfig,
- queue::{InternalEvent, NeedsUpdate, Queue},
- strings::{self, order},
- ui,
- ui::style::SharedTheme,
+ components::{CommandInfo, Component, EventState},
+ keys::SharedKeyConfig,
+ queue::{InternalEvent, NeedsUpdate, Queue},
+ strings::{self, order},
+ ui,
+ ui::style::SharedTheme,
};
use anyhow::Result;
use asyncgit::{hash, StatusItem, StatusItemType};
@@ -24,535 +24,535 @@ use tui::{backend::Backend, layout::Rect, text::Span, Frame};
///
pub struct FileTreeComponent {
- title: String,
- tree: StatusTree,
- pending: bool,
- current_hash: u64,
- focused: bool,
- show_selection: bool,
- queue: Option<Queue>,
- theme: SharedTheme,
- key_config: SharedKeyConfig,
- scroll_top: Cell<usize>,
+ title: String,
+ tree: StatusTree,
+ pending: bool,
+ current_hash: u64,
+ focused: bool,
+ show_selection: bool,
+ queue: Option<Queue>,
+ theme: SharedTheme,
+ key_config: SharedKeyConfig,
+ scroll_top: Cell<usize>,
}
impl FileTreeComponent {
- ///
- pub fn new(
- title: &str,
- focus: bool,
- queue: Option<Queue>,
- theme: SharedTheme,
- key_config: SharedKeyConfig,
- ) -> Self {
- Self {
- title: title.to_string(),
- tree: StatusTree::default(),
- current_hash: 0,
- focused: focus,
- show_selection: focus,
- queue,
- theme,
- key_config,
- scroll_top: Cell::new(0),
- pending: true,
- }
- }
-
- ///
- pub fn update(&mut self, list: &[StatusItem]) -> Result<()> {
- self.pending = false;
- let new_hash = hash(list);
- if self.current_hash != new_hash {
- self.tree.update(list)?;
- self.current_hash = new_hash;
- }
-
- Ok(())
- }
-
- ///
- pub fn selection(&self) -> Option<FileTreeItem> {
- self.tree.selected_item()
- }
-
- ///
- pub fn selection_file(&self) -> Option<StatusItem> {
- self.tree.selected_item().and_then(|f| {
- if let FileTreeItemKind::File(f) = f.kind {
- Some(f)
- } else {
- None
- }
- })
- }
-
- ///
- pub fn show_selection(&mut self, show: bool) {
- self.show_selection = show;
- }
-
- /// returns true if list is empty
- pub fn is_empty(&self) -> bool {
- self.tree.is_empty()
- }
-
- ///
- pub const fn file_count(&self) -> usize {
- self.tree.tree.file_count()
- }
-
- ///
- pub fn set_title(&mut self, title: String) {
- self.title = title;
- }
-
- ///
- pub fn clear(&mut self) -> Result<()> {
- self.current_hash = 0;
- self.pending = true;
- self.tree.update(&[])
- }
-
- ///
- pub fn is_file_seleted(&self) -> bool {
- self.tree.selected_item().map_or(false, |item| {
- match item.kind {
- FileTreeItemKind::File(_) => true,
- FileTreeItemKind::Path(..) => false,
- }
- })
- }
-
- fn move_selection(&mut self, dir: MoveSelection) -> bool {
- let changed = self.tree.move_selection(dir);
-
- if changed {
- if let Some(ref queue) = self.queue {
- queue.push(InternalEvent::Update(NeedsUpdate::DIFF));
- }
- }
-
- changed
- }
-
- const fn item_status_char(item_type: StatusItemType) -> char {
- match item_type {
- StatusItemType::Modified => 'M',
- StatusItemType::New => '+',
- StatusItemType::Deleted => '-',
- StatusItemType::Renamed => 'R',
- StatusItemType::Typechange => ' ',
- StatusItemType::Conflicted => '!',
- }
- }
-
- fn item_to_text<'b>(
- string: &str,
- indent: usize,
- visible: bool,
- file_item_kind: &FileTreeItemKind,
- width: u16,
- selected: bool,
- theme: &'b SharedTheme,
- ) -> Option<Span<'b>> {
- let indent_str = if indent == 0 {
- String::from("")
- } else {
- format!("{:w$}", " ", w = (indent as usize) * 2)
- };
-
- if !visible {
- return None;
- }
-
- match file_item_kind {
- FileTreeItemKind::File(status_item) => {
- let status_char =
- Self::item_status_char(status_item.status);
- let file = Path::new(&status_item.path)
- .file_name()
- .and_then(std::ffi::OsStr::to_str)
- .expect("invalid path.");
-
- let txt = if selected {
- format!(
- "{} {}{:w$}",
- status_char,
- indent_str,
- file,
- w = width as usize
- )
- } else {
- format!("{} {}{}", status_char, indent_str, file)
- };
-
- Some(Span::styled(
- Cow::from(txt),
- theme.item(status_item.status, selected),
- ))
- }
-
- FileTreeItemKind::Path(path_collapsed) => {
- let collapse_char =
- if path_collapsed.0 { '▸' } else { '▾' };
-
- let txt = if selected {
- format!(
- " {}{}{:w$}",
- indent_str,
- collapse_char,
- string,
- w = width as usize
- )
- } else {
- format!(
- " {}{}{}",
- indent_str, collapse_char, string,
- )
- };
-
- Some(Span::styled(
- Cow::from(txt),
- theme.text(true, selected),
- ))
- }
- }
- }
-
- /// Returns a Vec<TextDrawInfo> which is used to draw the `FileTreeComponent` correctly,
- /// allowing folders to be folded up if they are alone in their directory
- fn build_vec_text_draw_info_for_drawing(
- &self,
- ) -> (Vec<TextDrawInfo>, usize, usize) {
- let mut should_skip_over: usize = 0;
- let mut selection_offset: usize = 0;
- let mut selection_offset_visible: usize = 0;
- let mut vec_draw_text_info: Vec<TextDrawInfo> = vec![];
- let tree_items = self.tree.tree.items();
-
- for (index, item) in tree_items.iter().enumerate() {
- if should_skip_over > 0 {
- should_skip_over -= 1;
- continue;
- }
-
- let index_above_select =
- index < self.tree.selection.unwrap_or(0);
-
- if !item.info.visible && index_above_select {
- selection_offset_visible += 1;
- }
-
- vec_draw_text_info.push(TextDrawInfo {
- name: item.info.path.clone(),
- indent: item.info.indent,
- visible: item.info.visible,
- item_kind: &item.kind,
- });
-
- let mut idx_temp = index;
-
- while idx_temp < tree_items.len().saturating_sub(2)
- && tree_items[idx_temp].info.indent
- < tree_items[idx_temp + 1].info.indent
- {
- // fold up the folder/file
- idx_temp += 1;
- should_skip_over += 1;
-
- // don't fold files up
- //TODO: remove once fixed (https://github.com/rust-lang/rust-clippy/issues/7383)
- #[allow(clippy::if_same_then_else)]
- if let FileTreeItemKind::File(_) =
- &tree_items[idx_temp].kind
- {
- should_skip_over -= 1;
- break;
- }
- // don't fold up if more than one folder in folder
- else if self
- .tree
- .tree
- .multiple_items_at_path(idx_temp)
- {
- should_skip_over -= 1;
- break;
- }
-
- // There is only one item at this level (i.e only one folder in the folder),
- // so do fold up
-
- let vec_draw_text_info_len = vec_draw_text_info.len();
- vec_draw_text_info[vec_draw_text_info_len - 1]
- .name += &(String::from("/")
- + &tree_items[idx_temp].info.path);
- if index_above_select {
- selection_offset += 1;
- }
- }
- }
- (
- vec_draw_text_info,
- selection_offset,
- selection_offset_visible,
- )
- }
+ ///
+ pub fn new(
+ title: &str,
+ focus: bool,
+ queue: Option<Queue>,
+ theme: SharedTheme,
+ key_config: SharedKeyConfig,
+ ) -> Self {
+ Self {
+ title: title.to_string(),
+ tree: StatusTree::default(),
+ current_hash: 0,
+ focused: focus,
+ show_selection: focus,
+ queue,
+ theme,
+ key_config,
+ scroll_top: Cell::new(0),
+ pending: true,
+ }
+ }
+
+ ///
+ pub fn update(&mut self, list: &[StatusItem]) -> Result<()> {
+ self.pending = false;
+ let new_hash = hash(list);
+ if self.current_hash != new_hash {
+ self.tree.update(list)?;
+ self.current_hash = new_hash;
+ }
+
+ Ok(())
+ }
+
+ ///
+ pub fn selection(&self) -> Option<FileTreeItem> {
+ self.tree.selected_item()
+ }
+
+ ///
+ pub fn selection_file(&self) -> Option<StatusItem> {
+ self.tree.selected_item().and_then(|f| {
+ if let FileTreeItemKind::File(f) = f.kind {
+ Some(f)
+ } else {
+ None
+ }
+ })
+ }
+
+ ///
+ pub fn show_selection(&mut self, show: bool) {
+ self.show_selection = show;
+ }
+
+ /// returns true if list is empty
+ pub fn is_empty(&self) -> bool {
+ self.tree.is_empty()
+ }
+
+ ///
+ pub const fn file_count(&self) -> usize {
+ self.tree.tree.file_count()
+ }
+
+ ///
+ pub fn set_title(&mut self, title: String) {
+ self.title = title;
+ }
+
+ ///
+ pub fn clear(&mut self) -> Result<()> {
+ self.current_hash = 0;
+ self.pending = true;
+ self.tree.update(&[])
+ }
+
+ ///
+ pub fn is_file_seleted(&self) -> bool {
+ self.tree.selected_item().map_or(false, |item| {
+ match item.kind {
+ FileTreeItemKind::File(_) => true,
+ FileTreeItemKind::Path(..) => false,
+ }
+ })
+ }
+
+ fn move_selection(&mut self, dir: MoveSelection) -> bool {
+ let changed = self.tree.move_selection(dir);
+
+ if changed {
+ if let Some(ref queue) = self.queue {
+ queue.push(InternalEvent::Update(NeedsUpdate::DIFF));
+ }
+ }
+
+ changed
+ }
+
+ const fn item_status_char(item_type: StatusItemType) -> char {
+ match item_type {
+ StatusItemType::Modified => 'M',
+ StatusItemType::New => '+',
+ StatusItemType::Deleted => '-',
+ StatusItemType::Renamed => 'R',
+ StatusItemType::Typechange => ' ',
+ StatusItemType::Conflicted => '!',
+ }
+ }
+
+ fn item_to_text<'b>(
+ string: &str,
+ indent: usize,
+ visible: bool,
+ file_item_kind: &FileTreeItemKind,
+ width: u16,
+ selected: bool,
+ theme: &'b SharedTheme,
+ ) -> Option<Span<'b>> {
+ let indent_str = if indent == 0 {
+ String::from("")
+ } else {
+ format!("{:w$}", " ", w = (indent as usize) * 2)
+ };
+
+ if !visible {
+ return None;
+ }
+
+ match file_item_kind {
+ FileTreeItemKind::File(status_item) => {
+ let status_char =
+ Self::item_status_char(status_item.status);
+ let file = Path::new(&status_item.path)
+ .file_name()
+ .and_then(std::ffi::OsStr::to_str)
+ .expect("invalid path.");
+
+ let txt = if selected {
+ format!(
+ "{} {}{:w$}",
+ status_char,
+ indent_str,
+ file,
+ w = width as usize
+ )
+ } else {
+ format!("{} {}{}", status_char, indent_str, file)
+ };
+
+ Some(Span::styled(
+ Cow::from(txt),
+ theme.item(status_item.status, selected),
+ ))
+ }
+
+ FileTreeItemKind::Path(path_collapsed) => {
+ let collapse_char =
+ if path_collapsed.0 { '▸' } else { '▾' };
+
+ let txt = if selected {
+ format!(
+ " {}{}{:w$}",
+ indent_str,
+ collapse_char,
+ string,
+ w = width as usize
+ )
+ } else {
+ format!(
+ " {}{}{}",
+ indent_str, collapse_char, string,
+ )
+ };
+
+ Some(Span::styled(
+ Cow::from(txt),
+ theme.text(true, selected),
+ ))
+ }
+ }
+ }
+
+ /// Returns a Vec<TextDrawInfo> which is used to draw the `FileTreeComponent` correctly,
+ /// allowing folders to be folded up if they are alone in their directory
+ fn build_vec_text_draw_info_for_drawing(
+ &self,
+ ) -> (Vec<TextDrawInfo>, usize, usize) {
+ let mut should_skip_over: usize = 0;
+ let mut selection_offset: usize = 0;
+ let mut selection_offset_visible: usize = 0;
+ let mut vec_draw_text_info: Vec<TextDrawInfo> = vec![];
+ let tree_items = self.tree.tree.items();
+
+ for (index, item) in tree_items.iter().enumerate() {
+ if should_skip_over > 0 {
+ should_skip_over -= 1;
+ continue;
+ }
+
+ let index_above_select =
+ index < self.tree.selection.unwrap_or(0);
+
+ if !item.info.visible && index_above_select {
+ selection_offset_visible += 1;
+ }
+
+ vec_draw_text_info.push(TextDrawInfo {
+ name: item.info.path.clone(),
+ indent: item.info.indent,
+ visible: item.info.visible,
+ item_kind: &item.kind,
+ });
+
+ let mut idx_temp = index;
+
+ while idx_temp < tree_items.len().saturating_sub(2)
+ && tree_items[idx_temp].info.indent
+ < tree_items[idx_temp + 1].info.indent
+ {
+ // fold up the folder/file
+ idx_temp += 1;
+ should_skip_over += 1;
+
+ // don't fold files up
+ //TODO: remove once fixed (https://github.com/rust-lang/rust-clippy/issues/7383)
+ #[allow(clippy::if_same_then_else)]
+ if let FileTreeItemKind::File(_) =
+ &tree_items[idx_temp].kind
+ {
+ should_skip_over -= 1;
+ break;
+ }
+ // don't fold up if more than one folder in folder
+ else if self
+ .tree
+ .tree
+ .multiple_items_at_path(idx_temp)
+ {
+ should_skip_over -= 1;
+ break;
+ }
+
+ // There is only one item at this level (i.e only one folder in the folder),
+ // so do fold up
+
+ let vec_draw_text_info_len = vec_draw_text_info.len();
+ vec_draw_text_info[vec_draw_text_info_len - 1]
+ .name += &(String::from("/")
+ + &tree_items[idx_temp].info.path);
+ if index_above_select {
+ selection_offset += 1;
+ }
+ }
+ }
+ (
+ vec_draw_text_info,
+ selection_offset,
+ selection_offset_visible,
+ )
+ }
}
/// Used for drawing the `FileTreeComponent`
struct TextDrawInfo<'a> {
- name: String,
- indent: u8,
- visible: bool,
- item_kind: &'a FileTreeItemKind,
+ name: String,
+ indent: u8,
+ visible: bool,
+ item_kind: &'a FileTreeItemKind,
}
impl DrawableComponent for FileTreeComponent {
- fn draw<B: Backend>(
- &self,
- f: &mut Frame<B>,
- r: Rect,
- ) -> Result<()> {
- if self.pending {
- let items = vec![Span::styled(
- Cow::from(strings::loading_text(&self.key_config)),
- self.theme.text(false, false),
- )];
-
- ui::draw_list(
- f,
- r,
- self.title.as_str(),
- items.into_iter(),
- self.focused,
- &self.theme,
- );
- } else {
- let (
- vec_draw_text_info,
- selection_offset,
- selection_offset_visible,
- ) = self.build_vec_text_draw_info_for_drawing();
-
- let select = self
- .tree
- .selection
- .map(|idx| idx.saturating_sub(selection_offset))
- .unwrap_or_default();
- let tree_height = r.height.saturating_sub(2) as usize;
-
- self.scroll_top.set(ui::calc_scroll_top(
- self.scroll_top.get(),
- tree_height,
- select.saturating_sub(selection_offset_visible),
- ));
-
- let items = vec_draw_text_info
- .iter()
- .enumerate()
- .filter_map(|(index, draw_text_info)| {
- Self::item_to_text(
- &draw_text_info.name,
- draw_text_info.indent as usize,
- draw_text_info.visible,
- draw_text_info.item_kind,
- r.width,
- self.show_selection && select == index,
- &self.theme,
- )
- })
- .skip(self.scroll_top.get());
- ui::draw_list(
- f,
- r,
- self.title.as_str(),
- items,
- self.focused,
- &self.theme,
- );
- }
-
- Ok(())
- }
+ fn draw<B: Backend>(
+ &self,
+ f: &mut Frame<B>,
+ r: Rect,
+ ) -> Result<()> {
+ if self.pending {
+ let items = vec![Span::styled(
+ Cow::from(strings::loading_text(&self.key_config)),
+ self.theme.text(false, false),
+ )];
+
+ ui::draw_list(
+ f,
+ r,
+ self.title.as_str(),
+ items.into_iter(),
+ self.focused,
+ &self.theme,
+ );
+ } else {
+ let (
+ vec_draw_text_info,
+ selection_offset,
+ selection_offset_visible,
+ ) = self.build_vec_text_draw_info_for_drawing();
+
+ let select = self
+ .tree
+ .selection
+ .map(|idx| idx.saturating_sub(selection_offset))
+ .unwrap_or_default();
+ let tree_height = r.height.saturating_sub(2) as usize;
+
+ self.scroll_top.set(ui::calc_scroll_top(
+ self.scroll_top.get(),
+ tree_height,
+ select.saturating_sub(selection_offset_visible),
+ ));
+
+ let items = vec_draw_text_info
+ .iter()
+ .enumerate()
+ .filter_map(|(index, draw_text_info)| {
+ Self::item_to_text(
+ &draw_text_info.name,
+ draw_text_info.indent as usize,
+ draw_text_info.visible,
+ draw_text_info.item_kind,
+ r.width,
+ self.show_selection && select == index,
+ &self.theme,
+ )
+ })
+ .skip(self.scroll_top.get());
+ ui::draw_list(
+ f,
+ r,
+ self.title.as_str(),
+ items,
+ self.focused,
+ &self.theme,
+ );
+ }
+
+ Ok(())
+ }
}
impl Component for FileTreeComponent {
- fn commands(
- &self,
- out: &mut Vec<CommandInfo>,
- force_all: bool,
- ) -> CommandBlocking {
- out.push(
- CommandInfo::new(
- strings::commands::navigate_tree(&self.key_config),
- !self.is_empty(),
- self.focused || force_all,
- )
- .order(order::NAV),
- );
- out.push(
- CommandInfo::new(
- strings::commands::blame_file(&self.key_config),
- self.selection_file().is_some(),
- self.focused || force_all,
- )
- .order(order::RARE_ACTION),
- );
-
- CommandBlocking::PassingOn
- }
-
- fn event(&mut self, ev: Event) -> Result<EventState> {
- if self.focused {
- if let Event::Key(e) = ev {
- return if e == self.key_config.blame {
- match (&self.queue, self.selection_file()) {
- (Some(queue), Some(status_item)) => {
- queue.push(InternalEvent::BlameFile(
- status_item.path,
- ));
-
- Ok(EventState::Consumed)
- }
- _ => Ok(EventState::NotConsumed),
- }
- } else if e == self.key_config.move_down {
- 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
- || e == self.key_config.shift_up
- {
- Ok(self
- .move_selection(MoveSelection::Home)
- .into())
- } else if e == self.key_config.end
- || e == self.key_config.shift_down
- {
- Ok(self.move_selection(MoveSelection::End).into())
- } else if e == self.key_config.move_left {
- Ok(self
- .move_selection(MoveSelection::Left)
- .into())
- } else if e == self.key_config.move_right {
- Ok(self
- .move_selection(MoveSelection::Right)
- .into())
- } else {
- Ok(EventState::NotConsumed)
- };
- }
- }
-
- Ok(EventState::NotConsumed)
- }
-
- fn focused(&self) -> bool {
- self.focused
- }
- fn focus(&mut self, focus: bool) {
- self.focused = focus;
- self.show_selection(focus);
- }
+ fn commands(
+ &self,
+ out: &mut Vec<CommandInfo>,
+ force_all: bool,
+ ) -> CommandBlocking {
+ out.push(
+ CommandInfo::new(
+ strings::commands::navigate_tree(&self.key_config),
+ !self.is_empty(),
+ self.focused || force_all,
+ )
+ .order(order::NAV),
+ );
+ out.push(
+ CommandInfo::new(
+ strings::commands::blame_file(&self.key_config),
+ self.selection_file().is_some(),
+ self.focused || force_all,
+ )
+ .order(order::RARE_ACTION),
+ );
+
+ CommandBlocking::PassingOn
+ }
+
+ fn event(&mut self, ev: Event) -> Result<EventState> {
+ if self.focused {
+ if let Event::Key(e) = ev {
+ return if e == self.key_config.blame {
+ match (&self.queue, self.selection_file()) {
+ (Some(queue), Some(status_item)) => {
+ queue.push(InternalEvent::BlameFile(
+ status_item.path,
+ ));
+
+ Ok(EventState::Consumed)
+ }
+ _ => Ok(EventState::NotConsumed),
+ }
+ } else if e == self.key_config.move_down {
+ 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
+ || e == self.key_config.shift_up
+ {
+ Ok(self
+ .move_selection(MoveSelection::Home)
+ .into())
+ } else if e == self.key_config.end
+ || e == self.key_config.shift_down
+ {
+ Ok(self.move_selection(MoveSelection::End).into())
+ } else if e == self.key_config.move_left {
+ Ok(self
+ .move_selection(MoveSelection::Left)
+ .into())
+ } else if e == self.key_config.move_right {
+ Ok(self
+ .move_selection(MoveSelection::Right)
+ .into())
+ } else {
+ Ok(EventState::NotConsumed)
+ };
+ }
+ }
+
+ Ok(EventState::NotConsumed)
+ }
+
+ fn focused(&self) -> bool {
+ self.focused
+ }
+ fn focus(&mut self, focus: bool) {
+ self.focused = focus;
+ self.show_selection(focus);
+ }
}
#[cfg(test)]
mod tests {
- use super::*;
- use asyncgit::StatusItemType;
-
- fn string_vec_to_status(items: &[&str]) -> Vec<StatusItem> {
- items
- .iter()
- .map(|a| StatusItem {
- path: String::from(*a),
- status: StatusItemType::Modified,
- })
- .collect::<Vec<_>>()
- }
-
- #[test]
- fn test_correct_scroll_position() {
- let items = string_vec_to_status(&[
- "a/b/b1", //
- "a/b/b2", //
- "a/c/c1", //
- ]);
-
- //0 a/
- //1 b/
- //2 b1
- //3 b2
- //4 c/
- //5 c1
-
- // Set up test terminal
- let test_backend = tui::backend::TestBackend::new(100, 100);
- let mut terminal = tui::Terminal::new(test_backend)
- .expect("Unable to set up terminal");
- let mut frame = terminal.get_frame();
-
- // set up file tree
- let mut ftc = FileTreeComponent::new(
- "title",
- true,
- None,
- SharedTheme::default(),
- SharedKeyConfig::default(),
- );
- ftc.update(&items)
- .expect("Updating FileTreeComponent failed");
-
- ftc.move_selection(MoveSelection::Down); // Move to b/
- ftc.move_selection(MoveSelection::Left); // Fold b/
- ftc.move_selection(MoveSelection::Down); // Move to c/
-
- ftc.draw(&mut frame, Rect::new(0, 0, 10, 5))
- .expect("Draw failed");
-
- assert_eq!(ftc.scroll_top.get(), 0); // should still be at top
- }
-
- #[test]
- fn test_correct_foldup_and_not_visible_scroll_position() {
- let items = string_vec_to_status(&[
- "a/b/b1", //
- "c/d1", //
- "c/d2", //
- ]);
-
- //0 a/b/
- //2 b1
- //3 c/
- //4 d1
- //5 d2
-
- // Set up test terminal
- let test_backend = tui::backend::TestBackend::new(100, 100);
- let mut terminal = tui::Terminal::new(test_backend)
- .expect("Unable to set up terminal");
- let mut frame = terminal.get_frame();
-
- // set up file tree
- let mut ftc = FileTreeComponent::new(
- "title",
- true,
- None,
- SharedTheme::default(),
- SharedKeyConfig::default(),
- );
- ftc.update(&items)
- .expect("Updating FileTreeComponent failed");
-
- ftc.move_selection(MoveSelection::Left); // Fold a/b/
- ftc.move_selection(MoveSelection::Down); // Move to c/
-
- ftc.draw(&mut frame, Rect::new(0, 0, 10, 5))
- .expect("Draw failed");
-
- assert_eq!(ftc.scroll_top.get(), 0); // should still be at top
- }
+ use super::*;
+ use asyncgit::StatusItemType;
+
+ fn string_vec_to_status(items: &[&str]) -> Vec<StatusItem> {
+ items
+ .iter()
+ .map(|a| StatusItem {
+ path: String::from(*a),
+ status: StatusItemType::Modified,
+ })
+ .collect::<Vec<_>>()
+ }
+
+ #[test]
+ fn test_correct_scroll_position() {
+ let items = string_vec_to_status(&[
+ "a/b/b1", //
+ "a/b/b2", //
+ "a/c/c1", //
+ ]);
+
+ //0 a/
+ //1 b/
+ //2 b1
+ //3 b2
+ //4 c/
+ //5 c1
+
+ // Set up test terminal
+ let test_backend = tui::backend::TestBackend::new(100, 100);
+ let mut terminal = tui::Terminal::new(test_backend)
+ .expect("Unable to set up terminal");
+ let mut frame = terminal.get_frame();
+
+ // set up file tree
+ let mut ftc = FileTreeComponent::new(
+ "title",
+ true,
+ None,
+ SharedTheme::default(),
+ SharedKeyConfig::default(),
+ );
+ ftc.update(&items)
+ .expect("Updating FileTreeComponent failed");
+
+ ftc.move_selection(MoveSelection::Down); // Move to b/
+ ftc.move_selection(MoveSelection::Left); // Fold b/
+ ftc.move_selection(MoveSelection::Down); // Move to c/
+
+ ftc.draw(&mut frame, Rect::new(0, 0, 10, 5))
+ .expect("Draw failed");
+
+ assert_eq!(ftc.scroll_top.get(), 0); // should still be at top
+ }
+
+ #[test]
+ fn test_correct_foldup_and_not_visible_scroll_position() {
+ let items = string_vec_to_status(&[
+ "a/b/b1", //
+ "c/d1", //
+ "c/d2", //
+ ]);
+
+ //0 a/b/
+ //2 b1
+ //3 c/
+ //4 d1
+ //5 d2
+
+ // Set up test terminal
+ let test_backend = tui::backend::TestBackend::new(100, 100);
+ let mut terminal = tui::Terminal::new(test_backend)
+ .expect("Unable to set up terminal");
+ let mut frame = terminal.get_frame();
+
+ // set up file tree
+ let mut ftc = FileTreeComponent::new(
+ "title",
+ true,
+ None,
+ SharedTheme::default(),
+ SharedKeyConfig::default(),
+ );
+ ftc.update(&items)
+ .expect("Updating FileTreeComponent failed");
+
+ ftc.move_selection(MoveSelection::Left); // Fold a/b/
+ ftc.move_selection(MoveSelection::Down); // Move to c/
+
+ ftc.draw(&mut frame, Rect::new(0, 0, 10, 5))
+ .expect("Draw failed");
+
+ assert_eq!(ftc.scroll_top.get(), 0); // should still be at top
+ }
}