diff --git a/asyncgit/src/sync/tree.rs b/asyncgit/src/sync/tree.rs index 65b03604..38fa634c 100644 --- a/asyncgit/src/sync/tree.rs +++ b/asyncgit/src/sync/tree.rs @@ -68,7 +68,7 @@ fn path_cmp(a: &Path, b: &Path) -> Ordering { } } -/// +/// will only work on utf8 content pub fn tree_file_content( repo_path: &str, file: &TreeFile, @@ -78,6 +78,11 @@ pub fn tree_file_content( let repo = repo(repo_path)?; let blob = repo.find_blob(file.id)?; + + if blob.is_binary() { + return Ok(String::new()); + } + let content = String::from_utf8(blob.content().into())?; Ok(content) diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index 7a850dff..cffea8d5 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -1,4 +1,6 @@ -use std::{cell::Cell, collections::BTreeSet, convert::From}; +use std::{ + cell::Cell, collections::BTreeSet, convert::From, path::Path, +}; use super::{ visibility_blocking, CommandBlocking, CommandInfo, Component, @@ -19,7 +21,11 @@ use crossbeam_channel::Sender; use crossterm::event::Event; use filetree::{FileTree, MoveSelection}; use tui::{ - backend::Backend, layout::Rect, text::Span, widgets::Clear, Frame, + backend::Backend, + layout::{Constraint, Direction, Layout, Rect}, + text::{Span, Text}, + widgets::{Block, Borders, Clear, Paragraph, Wrap}, + Frame, }; const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}"; //▸ @@ -31,12 +37,12 @@ pub struct RevisionFilesComponent { title: String, theme: SharedTheme, files: Vec, + current_file: Option<(String, String)>, tree: FileTree, scroll_top: Cell, revision: Option, visible: bool, key_config: SharedKeyConfig, - current_height: std::cell::Cell, } impl RevisionFilesComponent { @@ -53,11 +59,11 @@ impl RevisionFilesComponent { tree: FileTree::default(), theme, scroll_top: Cell::new(0), + current_file: None, files: Vec::new(), revision: None, visible: false, key_config, - current_height: std::cell::Cell::new(0), } } @@ -125,6 +131,42 @@ impl RevisionFilesComponent { true }) } + + fn selection_changed(&mut self) -> Result<()> { + if let Some(file) = self.tree.selected_file().map(|file| { + file.full_path() + .strip_prefix("./") + .unwrap_or_default() + .to_string() + }) { + let already_loaded = self + .current_file + .as_ref() + .map(|(current_file, _)| current_file == &file) + .unwrap_or_default(); + + if !already_loaded { + self.load_file(file)?; + } + } else { + self.current_file = None; + } + + Ok(()) + } + + fn load_file(&mut self, path: String) -> Result<()> { + if let Some(item) = self + .files + .iter() + .find(|f| f.path.ends_with(Path::new(&path))) + { + let content = sync::tree_file_content(CWD, item)?; + self.current_file = Some((path, content)); + } + + Ok(()) + } } impl DrawableComponent for RevisionFilesComponent { @@ -134,11 +176,21 @@ impl DrawableComponent for RevisionFilesComponent { area: Rect, ) -> Result<()> { if self.is_visible() { - let tree_height = - usize::from(area.height.saturating_sub(2)); + let chunks = Layout::default() + .direction(Direction::Horizontal) + .margin(1) + .constraints( + [ + Constraint::Percentage(40), + Constraint::Percentage(60), + ] + .as_ref(), + ) + .split(area); + + let tree_height = usize::from(chunks[0].height); let selection = self.tree.visual_selection(); - selection.map_or_else( || self.scroll_top.set(0), |selection| { @@ -162,16 +214,34 @@ impl DrawableComponent for RevisionFilesComponent { }); f.render_widget(Clear, area); - ui::draw_list( - f, + f.render_widget( + Block::default() + .borders(Borders::ALL) + .title(Span::styled( + &self.title, + self.theme.title(true), + )) + .border_style(self.theme.block(true)), area, - &self.title, - items, - true, - &self.theme, ); - self.current_height.set(area.height.into()); + ui::draw_list_block( + f, + chunks[0], + Block::default() + .borders(Borders::RIGHT) + .border_style(self.theme.block(true)), + items, + ); + + let content = Paragraph::new(Text::from( + self.current_file + .as_ref() + .map(|(_, content)| content.as_str()) + .unwrap_or_default(), + )) + .wrap(Wrap { trim: false }); + f.render_widget(content, chunks[1]); } Ok(()) @@ -225,8 +295,15 @@ impl Component for RevisionFilesComponent { } else { false } + } else if tree_nav( + &mut self.tree, + &self.key_config, + key, + ) { + self.selection_changed()?; + true } else { - tree_nav(&mut self.tree, &self.key_config, key) + false }; return Ok(consumed.into()); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index a1b3f67f..1131c7fc 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -3,7 +3,7 @@ mod scrolllist; pub mod style; pub use scrollbar::draw_scrollbar; -pub use scrolllist::draw_list; +pub use scrolllist::{draw_list, draw_list_block}; use tui::layout::{Constraint, Direction, Layout, Rect}; /// return the scroll position (line) necessary to have the `selection` in view if it is not already diff --git a/src/ui/scrolllist.rs b/src/ui/scrolllist.rs index 80bd2463..0678bcff 100644 --- a/src/ui/scrolllist.rs +++ b/src/ui/scrolllist.rs @@ -73,3 +73,15 @@ pub fn draw_list<'b, B: Backend, L>( ); f.render_widget(list, r) } + +pub fn draw_list_block<'b, B: Backend, L>( + f: &mut Frame, + r: Rect, + block: Block<'b>, + items: L, +) where + L: Iterator>, +{ + let list = ScrollableList::new(items).block(block); + f.render_widget(list, r) +}