Fix 730 files tab (#743)

This commit is contained in:
Stephan Dilly 2021-05-28 00:15:02 +02:00 committed by GitHub
parent 1149ddddd3
commit 739d7e7f84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 383 additions and 128 deletions

View File

@ -8,7 +8,7 @@ use crate::{
ExternalEditorComponent, HelpComponent,
InspectCommitComponent, MsgComponent, PullComponent,
PushComponent, PushTagsComponent, RenameBranchComponent,
ResetComponent, RevisionFilesComponent, StashMsgComponent,
ResetComponent, RevisionFilesPopup, StashMsgComponent,
TagCommitComponent, TagListComponent,
},
input::{Input, InputEvent, InputState},
@ -16,7 +16,7 @@ use crate::{
queue::{Action, InternalEvent, NeedsUpdate, Queue},
setup_popups,
strings::{self, order},
tabs::{Revlog, StashList, Stashing, Status},
tabs::{FilesTab, Revlog, StashList, Stashing, Status},
ui::style::{SharedTheme, Theme},
};
use anyhow::{bail, Result};
@ -47,7 +47,7 @@ pub struct App {
stashmsg_popup: StashMsgComponent,
inspect_commit_popup: InspectCommitComponent,
external_editor_popup: ExternalEditorComponent,
revision_files_popup: RevisionFilesComponent,
revision_files_popup: RevisionFilesPopup,
push_popup: PushComponent,
push_tags_popup: PushTagsComponent,
pull_popup: PullComponent,
@ -62,6 +62,7 @@ pub struct App {
status_tab: Status,
stashing_tab: Stashing,
stashlist_tab: StashList,
files_tab: FilesTab,
queue: Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
@ -105,7 +106,7 @@ impl App {
theme.clone(),
key_config.clone(),
),
revision_files_popup: RevisionFilesComponent::new(
revision_files_popup: RevisionFilesPopup::new(
&queue,
sender,
theme.clone(),
@ -203,6 +204,12 @@ impl App {
theme.clone(),
key_config.clone(),
),
files_tab: FilesTab::new(
sender,
&queue,
theme.clone(),
key_config.clone(),
),
queue,
theme,
key_config,
@ -237,8 +244,9 @@ impl App {
match self.tab {
0 => self.status_tab.draw(f, chunks_main[1])?,
1 => self.revlog.draw(f, chunks_main[1])?,
2 => self.stashing_tab.draw(f, chunks_main[1])?,
3 => self.stashlist_tab.draw(f, chunks_main[1])?,
2 => self.files_tab.draw(f, chunks_main[1])?,
3 => self.stashing_tab.draw(f, chunks_main[1])?,
4 => self.stashlist_tab.draw(f, chunks_main[1])?,
_ => bail!("unknown tab"),
};
@ -271,6 +279,7 @@ impl App {
NeedsUpdate::COMMANDS
} else if k == self.key_config.tab_status
|| k == self.key_config.tab_log
|| k == self.key_config.tab_files
|| k == self.key_config.tab_stashing
|| k == self.key_config.tab_stashes
{
@ -322,6 +331,7 @@ impl App {
self.commit.update()?;
self.status_tab.update()?;
self.revlog.update()?;
self.files_tab.update()?;
self.stashing_tab.update()?;
self.stashlist_tab.update()?;
@ -339,6 +349,7 @@ impl App {
self.status_tab.update_git(ev)?;
self.stashing_tab.update_git(ev)?;
self.files_tab.update_git(ev)?;
self.revlog.update_git(ev)?;
self.blame_file_popup.update_git(ev)?;
self.inspect_commit_popup.update_git(ev)?;
@ -364,6 +375,7 @@ impl App {
self.status_tab.anything_pending()
|| self.revlog.any_work_pending()
|| self.stashing_tab.anything_pending()
|| self.files_tab.anything_pending()
|| self.blame_file_popup.any_work_pending()
|| self.inspect_commit_popup.any_work_pending()
|| self.input.is_state_changing()
@ -408,6 +420,7 @@ impl App {
help,
revlog,
status_tab,
files_tab,
stashing_tab,
stashlist_tab
]
@ -450,6 +463,7 @@ impl App {
vec![
&mut self.status_tab,
&mut self.revlog,
&mut self.files_tab,
&mut self.stashing_tab,
&mut self.stashlist_tab,
]
@ -471,10 +485,12 @@ impl App {
self.set_tab(0)?
} else if k == self.key_config.tab_log {
self.set_tab(1)?
} else if k == self.key_config.tab_stashing {
} else if k == self.key_config.tab_files {
self.set_tab(2)?
} else if k == self.key_config.tab_stashes {
} else if k == self.key_config.tab_stashing {
self.set_tab(3)?
} else if k == self.key_config.tab_stashes {
self.set_tab(4)?
}
Ok(())
@ -748,6 +764,7 @@ impl App {
let tabs = [
Span::raw(strings::tab_status(&self.key_config)),
Span::raw(strings::tab_log(&self.key_config)),
Span::raw(strings::tab_files(&self.key_config)),
Span::raw(strings::tab_stashing(&self.key_config)),
Span::raw(strings::tab_stashes(&self.key_config)),
]

View File

@ -19,6 +19,7 @@ mod push_tags;
mod rename_branch;
mod reset;
mod revision_files;
mod revision_files_popup;
mod stashmsg;
mod syntax_text;
mod tag_commit;
@ -46,6 +47,7 @@ pub use push_tags::PushTagsComponent;
pub use rename_branch::RenameBranchComponent;
pub use reset::ResetComponent;
pub use revision_files::RevisionFilesComponent;
pub use revision_files_popup::RevisionFilesPopup;
pub use stashmsg::StashMsgComponent;
pub use syntax_text::SyntaxTextComponent;
pub use tag_commit::TagCommitComponent;

View File

@ -1,6 +1,6 @@
use super::{
visibility_blocking, CommandBlocking, CommandInfo, Component,
DrawableComponent, EventState, SyntaxTextComponent,
CommandBlocking, CommandInfo, Component, DrawableComponent,
EventState, SyntaxTextComponent,
};
use crate::{
keys::SharedKeyConfig,
@ -23,7 +23,7 @@ use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
text::Span,
widgets::{Block, Borders, Clear},
widgets::{Block, Borders},
Frame,
};
@ -38,7 +38,6 @@ enum Focus {
pub struct RevisionFilesComponent {
queue: Queue,
title: String,
theme: SharedTheme,
//TODO: store TreeFiles in `tree`
files: Vec<TreeFile>,
@ -46,7 +45,6 @@ pub struct RevisionFilesComponent {
tree: FileTree,
scroll_top: Cell<usize>,
revision: Option<CommitId>,
visible: bool,
focus: Focus,
key_config: SharedKeyConfig,
}
@ -61,7 +59,6 @@ impl RevisionFilesComponent {
) -> Self {
Self {
queue: queue.clone(),
title: String::new(),
tree: FileTree::default(),
scroll_top: Cell::new(0),
current_file: SyntaxTextComponent::new(
@ -72,30 +69,26 @@ impl RevisionFilesComponent {
theme,
files: Vec::new(),
revision: None,
visible: false,
focus: Focus::Tree,
key_config,
}
}
///
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!(
"Files at [{}]",
self.revision
.map(|c| c.get_short_string())
.unwrap_or_default()
);
self.show()?;
pub fn set_commit(&mut self, commit: CommitId) -> Result<()> {
let same_id =
self.revision.map(|c| c == commit).unwrap_or_default();
if !same_id {
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);
}
Ok(())
}
@ -228,35 +221,21 @@ impl DrawableComponent for RevisionFilesComponent {
f: &mut Frame<B>,
area: Rect,
) -> Result<()> {
if self.is_visible() {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.margin(1)
.constraints(
[
Constraint::Percentage(40),
Constraint::Percentage(60),
]
.as_ref(),
)
.split(area);
let chunks = Layout::default()
.direction(Direction::Horizontal)
.margin(1)
.constraints(
[
Constraint::Percentage(40),
Constraint::Percentage(60),
]
.as_ref(),
)
.split(area);
f.render_widget(Clear, area);
f.render_widget(
Block::default()
.borders(Borders::TOP)
.title(Span::styled(
format!(" {}", self.title),
self.theme.title(true),
))
.border_style(self.theme.block(true)),
area,
);
self.draw_tree(f, chunks[0]);
self.draw_tree(f, chunks[0]);
self.current_file.draw(f, chunks[1])?;
}
self.current_file.draw(f, chunks[1])?;
Ok(())
}
@ -268,90 +247,60 @@ impl Component for RevisionFilesComponent {
out: &mut Vec<CommandInfo>,
force_all: bool,
) -> CommandBlocking {
if self.is_visible() || force_all {
if matches!(self.focus, Focus::Tree) || force_all {
out.push(
CommandInfo::new(
strings::commands::close_popup(&self.key_config),
true,
strings::commands::blame_file(&self.key_config),
self.tree.selected_file().is_some(),
true,
)
.order(1),
.order(order::NAV),
);
if matches!(self.focus, Focus::Tree) || force_all {
out.push(
CommandInfo::new(
strings::commands::blame_file(
&self.key_config,
),
self.tree.selected_file().is_some(),
true,
)
.order(order::NAV),
);
tree_nav_cmds(&self.tree, &self.key_config, out);
} else {
self.current_file.commands(out, force_all);
}
tree_nav_cmds(&self.tree, &self.key_config, out);
} else {
self.current_file.commands(out, force_all);
}
visibility_blocking(self)
CommandBlocking::PassingOn
}
fn event(
&mut self,
event: crossterm::event::Event,
) -> Result<EventState> {
if self.is_visible() {
if let Event::Key(key) = event {
let is_tree_focused =
matches!(self.focus, Focus::Tree);
if key == self.key_config.exit_popup {
if let Event::Key(key) = event {
let is_tree_focused = matches!(self.focus, Focus::Tree);
if is_tree_focused
&& tree_nav(&mut self.tree, &self.key_config, key)
{
self.selection_changed();
return Ok(EventState::Consumed);
} else if key == self.key_config.blame {
if self.blame() {
self.hide();
} else if is_tree_focused
&& tree_nav(&mut self.tree, &self.key_config, key)
{
self.selection_changed();
} else if key == self.key_config.blame {
if self.blame() {
self.hide();
}
} else if key == self.key_config.move_right {
if is_tree_focused {
self.focus = Focus::File;
self.current_file.focus(true);
self.focus(true);
}
} else if key == self.key_config.move_left {
if !is_tree_focused {
self.focus = Focus::Tree;
self.current_file.focus(false);
self.focus(false);
}
} else if !is_tree_focused {
self.current_file.event(event)?;
return Ok(EventState::Consumed);
}
} else if key == self.key_config.move_right {
if is_tree_focused {
self.focus = Focus::File;
self.current_file.focus(true);
self.focus(true);
return Ok(EventState::Consumed);
}
} else if key == self.key_config.move_left {
if !is_tree_focused {
self.focus = Focus::Tree;
self.current_file.focus(false);
self.focus(false);
return Ok(EventState::Consumed);
}
} else if !is_tree_focused {
return self.current_file.event(event);
}
return Ok(EventState::Consumed);
}
Ok(EventState::NotConsumed)
}
fn is_visible(&self) -> bool {
self.visible
}
fn hide(&mut self) {
self.visible = false
}
fn show(&mut self) -> Result<()> {
self.visible = true;
Ok(())
}
}
//TODO: reuse for other tree usages

View File

@ -0,0 +1,153 @@
use super::{
revision_files::RevisionFilesComponent, visibility_blocking,
CommandBlocking, CommandInfo, Component, DrawableComponent,
EventState,
};
use crate::{
keys::SharedKeyConfig,
queue::Queue,
strings::{self},
ui::style::SharedTheme,
};
use anyhow::Result;
use asyncgit::{sync::CommitId, AsyncNotification};
use crossbeam_channel::Sender;
use crossterm::event::Event;
use tui::{
backend::Backend,
layout::Rect,
text::Span,
widgets::{Block, Borders, Clear},
Frame,
};
pub struct RevisionFilesPopup {
title: String,
theme: SharedTheme,
visible: bool,
key_config: SharedKeyConfig,
files: RevisionFilesComponent,
}
impl RevisionFilesPopup {
///
pub fn new(
queue: &Queue,
sender: &Sender<AsyncNotification>,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
title: String::new(),
files: RevisionFilesComponent::new(
queue,
sender,
theme.clone(),
key_config.clone(),
),
theme,
visible: false,
key_config,
}
}
///
pub fn open(&mut self, commit: CommitId) -> Result<()> {
self.files.set_commit(commit)?;
self.title =
format!("Files at [{}]", commit.get_short_string());
self.show()?;
Ok(())
}
///
pub fn update(&mut self, ev: AsyncNotification) {
self.files.update(ev);
}
///
pub fn any_work_pending(&self) -> bool {
self.files.any_work_pending()
}
}
impl DrawableComponent for RevisionFilesPopup {
fn draw<B: Backend>(
&self,
f: &mut Frame<B>,
area: Rect,
) -> Result<()> {
if self.is_visible() {
f.render_widget(Clear, area);
f.render_widget(
Block::default()
.borders(Borders::TOP)
.title(Span::styled(
format!(" {}", self.title),
self.theme.title(true),
))
.border_style(self.theme.block(true)),
area,
);
self.files.draw(f, area)?;
}
Ok(())
}
}
impl Component for RevisionFilesPopup {
fn commands(
&self,
out: &mut Vec<CommandInfo>,
force_all: bool,
) -> CommandBlocking {
if self.is_visible() || force_all {
out.push(
CommandInfo::new(
strings::commands::close_popup(&self.key_config),
true,
true,
)
.order(1),
);
self.files.commands(out, force_all);
}
visibility_blocking(self)
}
fn event(
&mut self,
event: crossterm::event::Event,
) -> Result<EventState> {
if self.is_visible() {
if let Event::Key(key) = &event {
if *key == self.key_config.exit_popup {
self.hide();
}
}
return self.files.event(event);
}
Ok(EventState::NotConsumed)
}
fn is_visible(&self) -> bool {
self.visible
}
fn hide(&mut self) {
self.visible = false
}
fn show(&mut self) -> Result<()> {
self.visible = true;
Ok(())
}
}

View File

@ -23,6 +23,7 @@ pub type SharedKeyConfig = Rc<KeyConfig>;
pub struct KeyConfig {
pub tab_status: KeyEvent,
pub tab_log: KeyEvent,
pub tab_files: KeyEvent,
pub tab_stashing: KeyEvent,
pub tab_stashes: KeyEvent,
pub tab_toggle: KeyEvent,
@ -88,8 +89,9 @@ impl Default for KeyConfig {
Self {
tab_status: KeyEvent { code: KeyCode::Char('1'), modifiers: KeyModifiers::empty()},
tab_log: KeyEvent { code: KeyCode::Char('2'), modifiers: KeyModifiers::empty()},
tab_stashing: KeyEvent { code: KeyCode::Char('3'), modifiers: KeyModifiers::empty()},
tab_stashes: KeyEvent { code: KeyCode::Char('4'), modifiers: KeyModifiers::empty()},
tab_files: KeyEvent { code: KeyCode::Char('3'), modifiers: KeyModifiers::empty()},
tab_stashing: KeyEvent { code: KeyCode::Char('4'), modifiers: KeyModifiers::empty()},
tab_stashes: KeyEvent { code: KeyCode::Char('5'), modifiers: KeyModifiers::empty()},
tab_toggle: KeyEvent { code: KeyCode::Tab, modifiers: KeyModifiers::empty()},
tab_toggle_reverse: KeyEvent { code: KeyCode::BackTab, modifiers: KeyModifiers::SHIFT},
toggle_workarea: KeyEvent { code: KeyCode::Char('w'), modifiers: KeyModifiers::empty()},

View File

@ -41,6 +41,9 @@ pub fn tab_status(key_config: &SharedKeyConfig) -> String {
pub fn tab_log(key_config: &SharedKeyConfig) -> String {
format!("Log [{}]", key_config.get_hint(key_config.tab_log))
}
pub fn tab_files(key_config: &SharedKeyConfig) -> String {
format!("Files [{}]", key_config.get_hint(key_config.tab_files))
}
pub fn tab_stashing(key_config: &SharedKeyConfig) -> String {
format!(
"Stashing [{}]",

127
src/tabs/files.rs Normal file
View File

@ -0,0 +1,127 @@
#![allow(
dead_code,
clippy::missing_const_for_fn,
clippy::unused_self
)]
use crate::{
components::{
visibility_blocking, CommandBlocking, CommandInfo, Component,
DrawableComponent, EventState, RevisionFilesComponent,
},
keys::SharedKeyConfig,
queue::Queue,
ui::style::SharedTheme,
};
use anyhow::Result;
use asyncgit::{sync, AsyncNotification, CWD};
use crossbeam_channel::Sender;
pub struct FilesTab {
visible: bool,
theme: SharedTheme,
queue: Queue,
key_config: SharedKeyConfig,
files: RevisionFilesComponent,
}
impl FilesTab {
///
pub fn new(
sender: &Sender<AsyncNotification>,
queue: &Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
visible: false,
queue: queue.clone(),
files: RevisionFilesComponent::new(
queue,
sender,
theme.clone(),
key_config.clone(),
),
theme,
key_config,
}
}
///
pub fn update(&mut self) -> Result<()> {
if self.is_visible() {
self.files.set_commit(sync::get_head(CWD)?)?;
}
Ok(())
}
///
pub fn anything_pending(&self) -> bool {
self.files.any_work_pending()
}
///
pub fn update_git(
&mut self,
ev: AsyncNotification,
) -> Result<()> {
if self.is_visible() {
self.files.update(ev);
}
Ok(())
}
}
impl DrawableComponent for FilesTab {
fn draw<B: tui::backend::Backend>(
&self,
f: &mut tui::Frame<B>,
rect: tui::layout::Rect,
) -> Result<()> {
if self.is_visible() {
self.files.draw(f, rect)?;
}
Ok(())
}
}
impl Component for FilesTab {
fn commands(
&self,
out: &mut Vec<CommandInfo>,
force_all: bool,
) -> CommandBlocking {
if self.visible || force_all {
return self.files.commands(out, force_all);
}
visibility_blocking(self)
}
fn event(
&mut self,
ev: crossterm::event::Event,
) -> Result<EventState> {
if self.visible {
return self.files.event(ev);
}
Ok(EventState::NotConsumed)
}
fn is_visible(&self) -> bool {
self.visible
}
fn hide(&mut self) {
self.visible = false;
}
fn show(&mut self) -> Result<()> {
self.visible = true;
self.update()?;
Ok(())
}
}

View File

@ -1,8 +1,10 @@
mod files;
mod revlog;
mod stashing;
mod stashlist;
mod status;
pub use files::FilesTab;
pub use revlog::Revlog;
pub use stashing::{Stashing, StashingOptions};
pub use stashlist::StashList;

View File

@ -201,7 +201,6 @@ impl Component for StashList {
self.drop_stash()
} else if k == self.key_config.stash_open {
self.inspect()
} else {
}
}
}

View File

@ -11,8 +11,9 @@
(
tab_status: ( code: Char('1'), modifiers: ( bits: 0,),),
tab_log: ( code: Char('2'), modifiers: ( bits: 0,),),
tab_stashing: ( code: Char('3'), modifiers: ( bits: 0,),),
tab_stashes: ( code: Char('4'), modifiers: ( bits: 0,),),
tab_files: ( code: Char('3'), modifiers: ( bits: 0,),),
tab_stashing: ( code: Char('4'), modifiers: ( bits: 0,),),
tab_stashes: ( code: Char('5'), modifiers: ( bits: 0,),),
tab_toggle: ( code: Tab, modifiers: ( bits: 0,),),
tab_toggle_reverse: ( code: BackTab, modifiers: ( bits: 1,),),