mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 17:08:21 +00:00
parent
fa8070b1ba
commit
702415c40d
18 changed files with 706 additions and 192 deletions
|
|
@ -1,6 +1,6 @@
|
|||
use super::{utils::repo, CommitId};
|
||||
use crate::{error::Result, StatusItem, StatusItemType};
|
||||
use git2::DiffDelta;
|
||||
use git2::{Diff, DiffDelta, DiffOptions, Repository};
|
||||
use scopetime::scope_time;
|
||||
|
||||
/// get all files that are part of a commit
|
||||
|
|
@ -12,19 +12,7 @@ pub fn get_commit_files(
|
|||
|
||||
let repo = repo(repo_path)?;
|
||||
|
||||
let commit = repo.find_commit(id.into())?;
|
||||
let commit_tree = commit.tree()?;
|
||||
let parent = if commit.parent_count() > 0 {
|
||||
Some(repo.find_commit(commit.parent_id(0)?)?.tree()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let diff = repo.diff_tree_to_tree(
|
||||
parent.as_ref(),
|
||||
Some(&commit_tree),
|
||||
None,
|
||||
)?;
|
||||
let diff = get_commit_diff(&repo, id, None)?;
|
||||
|
||||
let mut res = Vec::new();
|
||||
|
||||
|
|
@ -48,6 +36,37 @@ pub fn get_commit_files(
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
///
|
||||
pub(crate) fn get_commit_diff(
|
||||
repo: &Repository,
|
||||
id: CommitId,
|
||||
pathspec: Option<String>,
|
||||
) -> Result<Diff<'_>> {
|
||||
// scope_time!("get_commit_diff");
|
||||
|
||||
let commit = repo.find_commit(id.into())?;
|
||||
let commit_tree = commit.tree()?;
|
||||
let parent = if commit.parent_count() > 0 {
|
||||
Some(repo.find_commit(commit.parent_id(0)?)?.tree()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut opt = pathspec.map(|p| {
|
||||
let mut opts = DiffOptions::new();
|
||||
opts.pathspec(p);
|
||||
opts
|
||||
});
|
||||
|
||||
let diff = repo.diff_tree_to_tree(
|
||||
parent.as_ref(),
|
||||
Some(&commit_tree),
|
||||
opt.as_mut(),
|
||||
)?;
|
||||
|
||||
Ok(diff)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::get_commit_files;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! sync git api for fetching a diff
|
||||
|
||||
use super::utils;
|
||||
use super::{commit_files::get_commit_diff, utils, CommitId};
|
||||
use crate::{error::Error, error::Result, hash};
|
||||
use git2::{
|
||||
Delta, Diff, DiffDelta, DiffFormat, DiffHunk, DiffOptions, Patch,
|
||||
|
|
@ -81,6 +81,8 @@ pub(crate) fn get_diff_raw<'a>(
|
|||
stage: bool,
|
||||
reverse: bool,
|
||||
) -> Result<Diff<'a>> {
|
||||
// scope_time!("get_diff_raw");
|
||||
|
||||
let mut opt = DiffOptions::new();
|
||||
opt.pathspec(p);
|
||||
opt.reverse(reverse);
|
||||
|
|
@ -119,7 +121,7 @@ pub(crate) fn get_diff_raw<'a>(
|
|||
Ok(diff)
|
||||
}
|
||||
|
||||
///
|
||||
/// returns diff of a specific file either in `stage` or workdir
|
||||
pub fn get_diff(
|
||||
repo_path: &str,
|
||||
p: String,
|
||||
|
|
@ -131,6 +133,32 @@ pub fn get_diff(
|
|||
let work_dir = work_dir(&repo);
|
||||
let diff = get_diff_raw(&repo, &p, stage, false)?;
|
||||
|
||||
raw_diff_to_file_diff(&diff, work_dir)
|
||||
}
|
||||
|
||||
/// returns diff of a specific file inside a commit
|
||||
/// see `get_commit_diff`
|
||||
pub fn get_diff_commit(
|
||||
repo_path: &str,
|
||||
id: CommitId,
|
||||
p: String,
|
||||
) -> Result<FileDiff> {
|
||||
scope_time!("get_diff_commit");
|
||||
|
||||
let repo = utils::repo(repo_path)?;
|
||||
let work_dir = work_dir(&repo);
|
||||
let diff = get_commit_diff(&repo, id, Some(p))?;
|
||||
|
||||
raw_diff_to_file_diff(&diff, work_dir)
|
||||
}
|
||||
|
||||
///
|
||||
fn raw_diff_to_file_diff<'a>(
|
||||
diff: &'a Diff,
|
||||
work_dir: &Path,
|
||||
) -> Result<FileDiff> {
|
||||
// scope_time!("raw_diff_to_file_diff");
|
||||
|
||||
let mut res: FileDiff = FileDiff::default();
|
||||
let mut current_lines = Vec::new();
|
||||
let mut current_hunk: Option<HunkHeader> = None;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ pub mod utils;
|
|||
pub use commit_details::{get_commit_details, CommitDetails};
|
||||
pub use commit_files::get_commit_files;
|
||||
pub use commits_info::{get_commits_info, CommitId, CommitInfo};
|
||||
pub use diff::get_diff_commit;
|
||||
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
|
||||
pub use hunks::{stage_hunk, unstage_hunk};
|
||||
pub use ignore::add_to_ignore;
|
||||
|
|
|
|||
35
src/app.rs
35
src/app.rs
|
|
@ -3,8 +3,9 @@ use crate::{
|
|||
cmdbar::CommandBar,
|
||||
components::{
|
||||
event_pump, CommandBlocking, CommandInfo, CommitComponent,
|
||||
Component, DrawableComponent, HelpComponent, MsgComponent,
|
||||
ResetComponent, StashMsgComponent,
|
||||
Component, DrawableComponent, HelpComponent,
|
||||
InspectCommitComponent, MsgComponent, ResetComponent,
|
||||
StashMsgComponent,
|
||||
},
|
||||
keys,
|
||||
queue::{Action, InternalEvent, NeedsUpdate, Queue},
|
||||
|
|
@ -33,6 +34,7 @@ pub struct App {
|
|||
reset: ResetComponent,
|
||||
commit: CommitComponent,
|
||||
stashmsg_popup: StashMsgComponent,
|
||||
inspect_commit_popup: InspectCommitComponent,
|
||||
cmdbar: CommandBar,
|
||||
tab: usize,
|
||||
revlog: Revlog,
|
||||
|
|
@ -58,12 +60,15 @@ impl App {
|
|||
queue.clone(),
|
||||
&theme,
|
||||
),
|
||||
inspect_commit_popup: InspectCommitComponent::new(
|
||||
&queue, sender, &theme,
|
||||
),
|
||||
do_quit: false,
|
||||
cmdbar: CommandBar::new(&theme),
|
||||
help: HelpComponent::new(&theme),
|
||||
msg: MsgComponent::new(&theme),
|
||||
tab: 0,
|
||||
revlog: Revlog::new(sender, &theme),
|
||||
revlog: Revlog::new(&queue, sender, &theme),
|
||||
status_tab: Status::new(sender, &queue, &theme),
|
||||
stashing_tab: Stashing::new(sender, &queue, &theme),
|
||||
stashlist_tab: StashList::new(&queue, &theme),
|
||||
|
|
@ -159,8 +164,11 @@ impl App {
|
|||
if flags.contains(NeedsUpdate::ALL) {
|
||||
self.update()?;
|
||||
}
|
||||
//TODO: make this a queue event?
|
||||
//NOTE: set when any tree component changed selection
|
||||
if flags.contains(NeedsUpdate::DIFF) {
|
||||
self.status_tab.update_diff()?;
|
||||
self.inspect_commit_popup.update_diff()?;
|
||||
}
|
||||
if flags.contains(NeedsUpdate::COMMANDS) {
|
||||
self.update_commands();
|
||||
|
|
@ -191,6 +199,7 @@ impl App {
|
|||
self.status_tab.update_git(ev)?;
|
||||
self.stashing_tab.update_git(ev)?;
|
||||
self.revlog.update_git(ev)?;
|
||||
self.inspect_commit_popup.update_git(ev)?;
|
||||
|
||||
if let AsyncNotification::Status = ev {
|
||||
//TODO: is that needed?
|
||||
|
|
@ -210,6 +219,7 @@ impl App {
|
|||
self.status_tab.anything_pending()
|
||||
|| self.revlog.any_work_pending()
|
||||
|| self.stashing_tab.anything_pending()
|
||||
|| self.inspect_commit_popup.any_work_pending()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,6 +232,7 @@ impl App {
|
|||
reset,
|
||||
commit,
|
||||
stashmsg_popup,
|
||||
inspect_commit_popup,
|
||||
help,
|
||||
revlog,
|
||||
status_tab,
|
||||
|
|
@ -356,6 +367,10 @@ impl App {
|
|||
self.stashmsg_popup.show()?
|
||||
}
|
||||
InternalEvent::TabSwitch => self.set_tab(0)?,
|
||||
InternalEvent::InspectCommit(id) => {
|
||||
self.inspect_commit_popup.open(id)?;
|
||||
flags.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(flags)
|
||||
|
|
@ -407,19 +422,31 @@ impl App {
|
|||
|| self.help.is_visible()
|
||||
|| self.reset.is_visible()
|
||||
|| self.msg.is_visible()
|
||||
|| self.stashmsg_popup.is_visible()
|
||||
|| self.inspect_commit_popup.is_visible()
|
||||
}
|
||||
|
||||
fn draw_popups<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
) -> Result<()> {
|
||||
let size = f.size();
|
||||
let size = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(self.cmdbar.height()),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.size())[0];
|
||||
|
||||
self.commit.draw(f, size)?;
|
||||
self.stashmsg_popup.draw(f, size)?;
|
||||
self.reset.draw(f, size)?;
|
||||
self.help.draw(f, size)?;
|
||||
self.msg.draw(f, size)?;
|
||||
self.inspect_commit_popup.draw(f, size)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ impl ChangesComponent {
|
|||
files: FileTreeComponent::new(
|
||||
title,
|
||||
focus,
|
||||
queue.clone(),
|
||||
Some(queue.clone()),
|
||||
theme,
|
||||
),
|
||||
is_working_dir,
|
||||
|
|
@ -75,7 +75,8 @@ impl ChangesComponent {
|
|||
|
||||
///
|
||||
pub fn focus_select(&mut self, focus: bool) {
|
||||
self.files.focus_select(focus)
|
||||
self.files.focus(focus);
|
||||
self.files.show_selection(focus);
|
||||
}
|
||||
|
||||
/// returns true if list is empty
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
use super::{
|
||||
dialog_paragraph, utils::time_to_string, DrawableComponent,
|
||||
use crate::{
|
||||
components::{
|
||||
dialog_paragraph, utils::time_to_string, CommandBlocking,
|
||||
CommandInfo, Component, DrawableComponent,
|
||||
},
|
||||
strings,
|
||||
ui::style::Theme,
|
||||
};
|
||||
use crate::{strings, ui::style::Theme};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{
|
||||
sync::{self, CommitDetails, CommitId},
|
||||
AsyncCommitFiles, AsyncNotification, StatusItem, CWD,
|
||||
sync::{self, CommitDetails},
|
||||
CWD,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use std::borrow::Cow;
|
||||
use sync::Tags;
|
||||
use sync::{CommitId, Tags};
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
|
|
@ -18,121 +22,39 @@ use tui::{
|
|||
Frame,
|
||||
};
|
||||
|
||||
pub struct CommitDetailsComponent {
|
||||
pub struct DetailsComponent {
|
||||
data: Option<CommitDetails>,
|
||||
tags: Vec<String>,
|
||||
files: Option<Vec<StatusItem>>,
|
||||
theme: Theme,
|
||||
git_commit_files: AsyncCommitFiles,
|
||||
}
|
||||
|
||||
impl DrawableComponent for CommitDetailsComponent {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
rect: Rect,
|
||||
) -> Result<()> {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(8),
|
||||
Constraint::Min(10),
|
||||
Constraint::Length(12),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(rect);
|
||||
|
||||
f.render_widget(
|
||||
dialog_paragraph(
|
||||
strings::commit::DETAILS_INFO_TITLE,
|
||||
self.get_text_info().iter(),
|
||||
),
|
||||
chunks[0],
|
||||
);
|
||||
|
||||
f.render_widget(
|
||||
dialog_paragraph(
|
||||
strings::commit::DETAILS_MESSAGE_TITLE,
|
||||
self.get_text_message().iter(),
|
||||
)
|
||||
.wrap(true),
|
||||
chunks[1],
|
||||
);
|
||||
|
||||
let files_loading = self.files.is_none();
|
||||
let files_count = self.files.as_ref().map_or(0, Vec::len);
|
||||
|
||||
let txt = self
|
||||
.files
|
||||
.as_ref()
|
||||
.map_or(vec![], |f| self.get_text_files(f));
|
||||
|
||||
let title = if files_loading {
|
||||
strings::commit::DETAILS_FILES_LOADING_TITLE.to_string()
|
||||
} else {
|
||||
format!(
|
||||
"{} {}",
|
||||
strings::commit::DETAILS_FILES_TITLE,
|
||||
files_count
|
||||
)
|
||||
};
|
||||
f.render_widget(
|
||||
dialog_paragraph(title.as_str(), txt.iter()),
|
||||
chunks[2],
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CommitDetailsComponent {
|
||||
impl DetailsComponent {
|
||||
///
|
||||
pub fn new(
|
||||
sender: &Sender<AsyncNotification>,
|
||||
theme: &Theme,
|
||||
) -> Self {
|
||||
pub const fn new(theme: &Theme) -> Self {
|
||||
Self {
|
||||
theme: *theme,
|
||||
data: None,
|
||||
tags: Vec::new(),
|
||||
files: None,
|
||||
git_commit_files: AsyncCommitFiles::new(sender),
|
||||
theme: *theme,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn set_commit(
|
||||
&mut self,
|
||||
id: Option<CommitId>,
|
||||
tags: &Tags,
|
||||
) -> Result<()> {
|
||||
self.tags.clear();
|
||||
|
||||
self.data = if let Some(id) = id {
|
||||
sync::get_commit_details(CWD, id).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.tags.clear();
|
||||
self.files = None;
|
||||
|
||||
if let Some(id) = id {
|
||||
if let Some(tags) = tags.get(&id) {
|
||||
self.tags.extend(tags.clone());
|
||||
}
|
||||
|
||||
if let Some((fetched_id, res)) =
|
||||
self.git_commit_files.current()?
|
||||
{
|
||||
if fetched_id == id {
|
||||
self.files = Some(res);
|
||||
} else {
|
||||
self.git_commit_files.fetch(id)?;
|
||||
}
|
||||
} else {
|
||||
self.git_commit_files.fetch(id)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -161,25 +83,6 @@ impl CommitDetailsComponent {
|
|||
vec![]
|
||||
}
|
||||
|
||||
fn get_text_files<'a>(
|
||||
&self,
|
||||
files: &'a [StatusItem],
|
||||
) -> Vec<Text<'a>> {
|
||||
let new_line = Text::Raw(Cow::from("\n"));
|
||||
|
||||
let mut res = Vec::with_capacity(files.len());
|
||||
|
||||
for file in files {
|
||||
res.push(Text::Styled(
|
||||
Cow::from(file.path.as_str()),
|
||||
self.theme.text(true, false),
|
||||
));
|
||||
res.push(new_line.clone());
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn get_text_info(&self) -> Vec<Text> {
|
||||
let new_line = Text::Raw(Cow::from("\n"));
|
||||
|
||||
|
|
@ -271,9 +174,57 @@ impl CommitDetailsComponent {
|
|||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn any_work_pending(&self) -> bool {
|
||||
self.git_commit_files.is_pending()
|
||||
impl DrawableComponent for DetailsComponent {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
rect: Rect,
|
||||
) -> Result<()> {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[Constraint::Length(8), Constraint::Min(10)].as_ref(),
|
||||
)
|
||||
.split(rect);
|
||||
|
||||
f.render_widget(
|
||||
dialog_paragraph(
|
||||
strings::commit::DETAILS_INFO_TITLE,
|
||||
self.get_text_info().iter(),
|
||||
&self.theme,
|
||||
false,
|
||||
),
|
||||
chunks[0],
|
||||
);
|
||||
|
||||
f.render_widget(
|
||||
dialog_paragraph(
|
||||
strings::commit::DETAILS_MESSAGE_TITLE,
|
||||
self.get_text_message().iter(),
|
||||
&self.theme,
|
||||
false,
|
||||
)
|
||||
.wrap(true),
|
||||
chunks[1],
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for DetailsComponent {
|
||||
fn commands(
|
||||
&self,
|
||||
_out: &mut Vec<CommandInfo>,
|
||||
_force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
// visibility_blocking(self)
|
||||
CommandBlocking::PassingOn
|
||||
}
|
||||
|
||||
fn event(&mut self, _ev: Event) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
173
src/components/commit_details/mod.rs
Normal file
173
src/components/commit_details/mod.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
mod details;
|
||||
|
||||
use super::{
|
||||
command_pump, event_pump, CommandBlocking, CommandInfo,
|
||||
Component, DrawableComponent, FileTreeComponent,
|
||||
};
|
||||
use crate::{accessors, queue::Queue, strings, ui::style::Theme};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{
|
||||
sync::{CommitId, Tags},
|
||||
AsyncCommitFiles, AsyncNotification,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use details::DetailsComponent;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
Frame,
|
||||
};
|
||||
|
||||
pub struct CommitDetailsComponent {
|
||||
details: DetailsComponent,
|
||||
file_tree: FileTreeComponent,
|
||||
git_commit_files: AsyncCommitFiles,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl CommitDetailsComponent {
|
||||
accessors!(self, [details, file_tree]);
|
||||
|
||||
///
|
||||
pub fn new(
|
||||
queue: &Queue,
|
||||
sender: &Sender<AsyncNotification>,
|
||||
theme: &Theme,
|
||||
) -> Self {
|
||||
Self {
|
||||
details: DetailsComponent::new(theme),
|
||||
git_commit_files: AsyncCommitFiles::new(sender),
|
||||
file_tree: FileTreeComponent::new(
|
||||
"",
|
||||
false,
|
||||
Some(queue.clone()),
|
||||
theme,
|
||||
),
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_files_title(&self) -> String {
|
||||
let files_loading = self.git_commit_files.is_pending();
|
||||
let files_count = self.file_tree.file_count();
|
||||
|
||||
if files_loading {
|
||||
strings::commit::DETAILS_FILES_LOADING_TITLE.to_string()
|
||||
} else {
|
||||
format!(
|
||||
"{} {}",
|
||||
strings::commit::DETAILS_FILES_TITLE,
|
||||
files_count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn set_commit(
|
||||
&mut self,
|
||||
id: Option<CommitId>,
|
||||
tags: &Tags,
|
||||
) -> Result<()> {
|
||||
self.details.set_commit(id, tags)?;
|
||||
|
||||
if let Some(id) = id {
|
||||
if let Some((fetched_id, res)) =
|
||||
self.git_commit_files.current()?
|
||||
{
|
||||
if fetched_id == id {
|
||||
self.file_tree.update(res.as_slice())?;
|
||||
} else {
|
||||
self.file_tree.clear()?;
|
||||
self.git_commit_files.fetch(id)?;
|
||||
}
|
||||
} else {
|
||||
self.file_tree.clear()?;
|
||||
self.git_commit_files.fetch(id)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.file_tree.set_title(self.get_files_title());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn any_work_pending(&self) -> bool {
|
||||
self.git_commit_files.is_pending()
|
||||
}
|
||||
|
||||
///
|
||||
pub const fn files(&self) -> &FileTreeComponent {
|
||||
&self.file_tree
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for CommitDetailsComponent {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
rect: Rect,
|
||||
) -> Result<()> {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(70),
|
||||
Constraint::Percentage(30),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(rect);
|
||||
|
||||
self.details.draw(f, chunks[0])?;
|
||||
self.file_tree.draw(f, chunks[1])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for CommitDetailsComponent {
|
||||
fn commands(
|
||||
&self,
|
||||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
if self.visible || force_all {
|
||||
command_pump(
|
||||
out,
|
||||
force_all,
|
||||
self.components().as_slice(),
|
||||
);
|
||||
}
|
||||
|
||||
CommandBlocking::PassingOn
|
||||
}
|
||||
|
||||
fn event(&mut self, ev: Event) -> Result<bool> {
|
||||
if event_pump(ev, self.components_mut().as_mut_slice())? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
fn hide(&mut self) {
|
||||
self.visible = false;
|
||||
}
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn focused(&self) -> bool {
|
||||
self.details.focused() || self.file_tree.focused()
|
||||
}
|
||||
fn focus(&mut self, focus: bool) {
|
||||
self.file_tree.focus(focus);
|
||||
self.file_tree.show_selection(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -35,13 +35,13 @@ pub struct DiffComponent {
|
|||
focused: bool,
|
||||
current: Current,
|
||||
selected_hunk: Option<usize>,
|
||||
queue: Queue,
|
||||
queue: Option<Queue>,
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
impl DiffComponent {
|
||||
///
|
||||
pub fn new(queue: Queue, theme: &Theme) -> Self {
|
||||
pub fn new(queue: Option<Queue>, theme: &Theme) -> Self {
|
||||
Self {
|
||||
focused: false,
|
||||
queue,
|
||||
|
|
@ -270,12 +270,18 @@ impl DiffComponent {
|
|||
if let Some(hunk) = self.selected_hunk {
|
||||
let hash = self.diff.hunks[hunk].header_hash;
|
||||
self.queue
|
||||
.as_ref()
|
||||
.expect("try using queue in immutable diff")
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::AddHunk(hash));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_immutable(&self) -> bool {
|
||||
self.queue.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for DiffComponent {
|
||||
|
|
@ -325,16 +331,18 @@ impl Component for DiffComponent {
|
|||
.hidden(),
|
||||
);
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
commands::DIFF_HUNK_REMOVE,
|
||||
self.selected_hunk.is_some(),
|
||||
self.focused && self.current.is_stage,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
commands::DIFF_HUNK_ADD,
|
||||
self.selected_hunk.is_some(),
|
||||
self.focused && !self.current.is_stage,
|
||||
));
|
||||
if !self.is_immutable() {
|
||||
out.push(CommandInfo::new(
|
||||
commands::DIFF_HUNK_REMOVE,
|
||||
self.selected_hunk.is_some(),
|
||||
self.focused && self.current.is_stage,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
commands::DIFF_HUNK_ADD,
|
||||
self.selected_hunk.is_some(),
|
||||
self.focused && !self.current.is_stage,
|
||||
));
|
||||
}
|
||||
|
||||
CommandBlocking::PassingOn
|
||||
}
|
||||
|
|
@ -367,7 +375,7 @@ impl Component for DiffComponent {
|
|||
self.scroll(ScrollType::PageDown)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::ENTER => {
|
||||
keys::ENTER if !self.is_immutable() => {
|
||||
self.add_hunk()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
|
@ -382,7 +390,6 @@ impl Component for DiffComponent {
|
|||
fn focused(&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
|
||||
fn focus(&mut self, focus: bool) {
|
||||
self.focused = focus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ pub struct FileTreeComponent {
|
|||
current_hash: u64,
|
||||
focused: bool,
|
||||
show_selection: bool,
|
||||
queue: Queue,
|
||||
queue: Option<Queue>,
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ impl FileTreeComponent {
|
|||
pub fn new(
|
||||
title: &str,
|
||||
focus: bool,
|
||||
queue: Queue,
|
||||
queue: Option<Queue>,
|
||||
theme: &Theme,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
|
@ -67,9 +67,19 @@ impl FileTreeComponent {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn focus_select(&mut self, focus: bool) {
|
||||
self.focus(focus);
|
||||
self.show_selection = focus;
|
||||
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
|
||||
|
|
@ -77,6 +87,22 @@ impl FileTreeComponent {
|
|||
self.tree.is_empty()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn file_count(&self) -> usize {
|
||||
self.tree.tree.len()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn set_title(&mut self, title: String) {
|
||||
self.title = title;
|
||||
}
|
||||
|
||||
///
|
||||
pub fn clear(&mut self) -> Result<()> {
|
||||
self.current_hash = 0;
|
||||
self.tree.update(&[])
|
||||
}
|
||||
|
||||
///
|
||||
pub fn is_file_seleted(&self) -> bool {
|
||||
if let Some(item) = self.tree.selected_item() {
|
||||
|
|
@ -93,9 +119,11 @@ impl FileTreeComponent {
|
|||
let changed = self.tree.move_selection(dir);
|
||||
|
||||
if changed {
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::Update(NeedsUpdate::DIFF));
|
||||
if let Some(ref queue) = self.queue {
|
||||
queue.borrow_mut().push_back(InternalEvent::Update(
|
||||
NeedsUpdate::DIFF,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
changed
|
||||
|
|
@ -286,6 +314,6 @@ impl Component for FileTreeComponent {
|
|||
self.focused
|
||||
}
|
||||
fn focus(&mut self, focus: bool) {
|
||||
self.focused = focus
|
||||
self.focused = focus;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
215
src/components/inspect_commit.rs
Normal file
215
src/components/inspect_commit.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
use super::{
|
||||
command_pump, event_pump, visibility_blocking, CommandBlocking,
|
||||
CommandInfo, CommitDetailsComponent, Component, DiffComponent,
|
||||
DrawableComponent,
|
||||
};
|
||||
use crate::{
|
||||
accessors, keys, queue::Queue, strings, ui::style::Theme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{sync, AsyncNotification, CWD};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use strings::commands;
|
||||
use sync::{CommitId, Tags};
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
widgets::Clear,
|
||||
Frame,
|
||||
};
|
||||
|
||||
pub struct InspectCommitComponent {
|
||||
commit_id: Option<CommitId>,
|
||||
diff: DiffComponent,
|
||||
details: CommitDetailsComponent,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl DrawableComponent for InspectCommitComponent {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
rect: Rect,
|
||||
) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
let percentages = if self.diff.focused() {
|
||||
(30, 70)
|
||||
} else {
|
||||
(50, 50)
|
||||
};
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(percentages.0),
|
||||
Constraint::Percentage(percentages.1),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(rect);
|
||||
|
||||
f.render_widget(Clear, rect);
|
||||
|
||||
self.details.draw(f, chunks[0])?;
|
||||
self.diff.draw(f, chunks[1])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for InspectCommitComponent {
|
||||
fn commands(
|
||||
&self,
|
||||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
if self.is_visible() || force_all {
|
||||
command_pump(
|
||||
out,
|
||||
force_all,
|
||||
self.components().as_slice(),
|
||||
);
|
||||
}
|
||||
|
||||
out.push(
|
||||
CommandInfo::new(
|
||||
commands::CLOSE_POPUP,
|
||||
true,
|
||||
self.is_visible(),
|
||||
)
|
||||
.order(1),
|
||||
);
|
||||
|
||||
visibility_blocking(self)
|
||||
}
|
||||
|
||||
fn event(&mut self, ev: Event) -> Result<bool> {
|
||||
if self.is_visible() {
|
||||
if event_pump(ev, self.components_mut().as_mut_slice())? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Event::Key(e) = ev {
|
||||
match e {
|
||||
keys::EXIT_POPUP => {
|
||||
self.hide();
|
||||
}
|
||||
keys::FOCUS_RIGHT if self.can_focus_diff() => {
|
||||
self.details.focus(false);
|
||||
self.diff.focus(true);
|
||||
}
|
||||
keys::FOCUS_LEFT if self.diff.focused() => {
|
||||
self.details.focus(true);
|
||||
self.diff.focus(false);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// stop key event propagation
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
fn hide(&mut self) {
|
||||
self.visible = false;
|
||||
}
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
self.details.show()?;
|
||||
self.details.focus(true);
|
||||
self.diff.focus(false);
|
||||
self.update()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl InspectCommitComponent {
|
||||
accessors!(self, [diff, details]);
|
||||
|
||||
///
|
||||
pub fn new(
|
||||
queue: &Queue,
|
||||
sender: &Sender<AsyncNotification>,
|
||||
theme: &Theme,
|
||||
) -> Self {
|
||||
Self {
|
||||
details: CommitDetailsComponent::new(
|
||||
queue, sender, theme,
|
||||
),
|
||||
diff: DiffComponent::new(None, theme),
|
||||
commit_id: None,
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn open(&mut self, id: CommitId) -> Result<()> {
|
||||
self.commit_id = Some(id);
|
||||
self.show()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn any_work_pending(&self) -> bool {
|
||||
self.details.any_work_pending()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn update_git(
|
||||
&mut self,
|
||||
ev: AsyncNotification,
|
||||
) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
if let AsyncNotification::CommitFiles = ev {
|
||||
self.update()?
|
||||
} else if let AsyncNotification::Diff = ev {
|
||||
self.update()?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// called when any tree component changed selection
|
||||
pub fn update_diff(&mut self) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
if let Some(id) = self.commit_id {
|
||||
if let Some(f) = self.details.files().selection_file()
|
||||
{
|
||||
self.diff.update(
|
||||
f.path.clone(),
|
||||
false,
|
||||
sync::get_diff_commit(CWD, id, f.path)?,
|
||||
)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
self.diff.clear()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self) -> Result<()> {
|
||||
self.details.set_commit(self.commit_id, &Tags::new())?;
|
||||
self.update_diff()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn can_focus_diff(&self) -> bool {
|
||||
self.details.files().selection_file().is_some()
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ mod commitlist;
|
|||
mod diff;
|
||||
mod filetree;
|
||||
mod help;
|
||||
mod inspect_commit;
|
||||
mod msg;
|
||||
mod reset;
|
||||
mod stashmsg;
|
||||
|
|
@ -21,15 +22,16 @@ use crossterm::event::Event;
|
|||
pub use diff::DiffComponent;
|
||||
pub use filetree::FileTreeComponent;
|
||||
pub use help::HelpComponent;
|
||||
pub use inspect_commit::InspectCommitComponent;
|
||||
pub use msg::MsgComponent;
|
||||
pub use reset::ResetComponent;
|
||||
pub use stashmsg::StashMsgComponent;
|
||||
pub use utils::filetree::FileTreeItemKind;
|
||||
|
||||
use crate::ui::style::Theme;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::Alignment,
|
||||
layout::Rect,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{Block, BorderType, Borders, Paragraph, Text},
|
||||
Frame,
|
||||
};
|
||||
|
|
@ -152,23 +154,23 @@ pub trait Component {
|
|||
fn show(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
fn toggle_visible(&mut self) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
self.hide();
|
||||
Ok(())
|
||||
} else {
|
||||
self.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dialog_paragraph<'a, 't, T>(
|
||||
title: &'a str,
|
||||
content: T,
|
||||
) -> Paragraph<'a, 't, T>
|
||||
where
|
||||
T: Iterator<Item = &'t Text<'t>>,
|
||||
{
|
||||
Paragraph::new(content)
|
||||
.block(Block::default().title(title).borders(Borders::ALL))
|
||||
.alignment(Alignment::Left)
|
||||
}
|
||||
|
||||
fn popup_paragraph<'a, 't, T>(
|
||||
title: &'a str,
|
||||
content: T,
|
||||
theme: &Theme,
|
||||
focused: bool,
|
||||
) -> Paragraph<'a, 't, T>
|
||||
where
|
||||
T: Iterator<Item = &'t Text<'t>>,
|
||||
|
|
@ -178,7 +180,29 @@ where
|
|||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Thick),
|
||||
.title_style(theme.title(focused))
|
||||
.border_style(theme.block(focused)),
|
||||
)
|
||||
.alignment(Alignment::Left)
|
||||
}
|
||||
|
||||
fn popup_paragraph<'a, 't, T>(
|
||||
title: &'a str,
|
||||
content: T,
|
||||
theme: &Theme,
|
||||
focused: bool,
|
||||
) -> Paragraph<'a, 't, T>
|
||||
where
|
||||
T: Iterator<Item = &'t Text<'t>>,
|
||||
{
|
||||
Paragraph::new(content)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Thick)
|
||||
.title_style(theme.title(focused))
|
||||
.border_style(theme.block(focused)),
|
||||
)
|
||||
.alignment(Alignment::Left)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,10 @@ impl DrawableComponent for ResetComponent {
|
|||
|
||||
let area = ui::centered_rect(30, 20, f.size());
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(popup_paragraph(title, txt.iter()), area);
|
||||
f.render_widget(
|
||||
popup_paragraph(title, txt.iter(), &self.theme, true),
|
||||
area,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -76,7 +76,12 @@ impl DrawableComponent for TextInputComponent {
|
|||
let area = ui::centered_rect(60, 20, f.size());
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(
|
||||
popup_paragraph(self.title.as_str(), txt.iter()),
|
||||
popup_paragraph(
|
||||
self.title.as_str(),
|
||||
txt.iter(),
|
||||
&self.theme,
|
||||
true,
|
||||
),
|
||||
area,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,12 +41,14 @@ pub enum InternalEvent {
|
|||
ShowErrorMsg(String),
|
||||
///
|
||||
Update(NeedsUpdate),
|
||||
///
|
||||
/// open commit msg input
|
||||
OpenCommit,
|
||||
///
|
||||
PopupStashing(StashingOptions),
|
||||
///
|
||||
TabSwitch,
|
||||
///
|
||||
InspectCommit(CommitId),
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
|||
|
|
@ -249,9 +249,15 @@ pub mod commands {
|
|||
);
|
||||
|
||||
///
|
||||
pub static LOG_DETAILS_OPEN: CommandText = CommandText::new(
|
||||
pub static LOG_DETAILS_TOGGLE: CommandText = CommandText::new(
|
||||
"Details [enter]",
|
||||
"open details of selected commit",
|
||||
CMD_GROUP_LOG,
|
||||
);
|
||||
///
|
||||
pub static LOG_DETAILS_OPEN: CommandText = CommandText::new(
|
||||
"Inspect [\u{2192}]", //→
|
||||
"inspect selected commit in detail",
|
||||
CMD_GROUP_LOG,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use crate::{
|
|||
CommitDetailsComponent, CommitList, Component,
|
||||
DrawableComponent,
|
||||
},
|
||||
keys, strings,
|
||||
keys,
|
||||
queue::{InternalEvent, Queue},
|
||||
strings,
|
||||
ui::style::Theme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
|
@ -12,6 +14,7 @@ use asyncgit::{sync, AsyncLog, AsyncNotification, FetchStatus, CWD};
|
|||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use strings::commands;
|
||||
use sync::CommitId;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
|
|
@ -25,24 +28,25 @@ pub struct Revlog {
|
|||
commit_details: CommitDetailsComponent,
|
||||
list: CommitList,
|
||||
git_log: AsyncLog,
|
||||
queue: Queue,
|
||||
visible: bool,
|
||||
details_open: bool,
|
||||
}
|
||||
|
||||
impl Revlog {
|
||||
///
|
||||
pub fn new(
|
||||
queue: &Queue,
|
||||
sender: &Sender<AsyncNotification>,
|
||||
theme: &Theme,
|
||||
) -> Self {
|
||||
Self {
|
||||
queue: queue.clone(),
|
||||
commit_details: CommitDetailsComponent::new(
|
||||
sender, theme,
|
||||
queue, sender, theme,
|
||||
),
|
||||
list: CommitList::new(strings::LOG_TITLE, theme),
|
||||
git_log: AsyncLog::new(sender),
|
||||
visible: false,
|
||||
details_open: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,9 +76,9 @@ impl Revlog {
|
|||
self.list.set_tags(sync::get_tags(CWD)?);
|
||||
}
|
||||
|
||||
if self.details_open {
|
||||
if self.commit_details.is_visible() {
|
||||
self.commit_details.set_commit(
|
||||
self.list.selected_entry().map(|e| e.id),
|
||||
self.selected_commit(),
|
||||
self.list.tags().expect("tags"),
|
||||
)?;
|
||||
}
|
||||
|
|
@ -115,6 +119,10 @@ impl Revlog {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn selected_commit(&self) -> Option<CommitId> {
|
||||
self.list.selected_entry().map(|e| e.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for Revlog {
|
||||
|
|
@ -134,7 +142,7 @@ impl DrawableComponent for Revlog {
|
|||
)
|
||||
.split(area);
|
||||
|
||||
if self.details_open {
|
||||
if self.commit_details.is_visible() {
|
||||
self.list.draw(f, chunks[0])?;
|
||||
self.commit_details.draw(f, chunks[1])?;
|
||||
} else {
|
||||
|
|
@ -154,9 +162,18 @@ impl Component for Revlog {
|
|||
self.update()?;
|
||||
return Ok(true);
|
||||
} else if let Event::Key(keys::LOG_COMMIT_DETAILS) = ev {
|
||||
self.details_open = !self.details_open;
|
||||
self.commit_details.toggle_visible()?;
|
||||
self.update()?;
|
||||
return Ok(true);
|
||||
} else if let Event::Key(keys::FOCUS_RIGHT) = ev {
|
||||
return if let Some(id) = self.selected_commit() {
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::InspectCommit(id));
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -173,11 +190,18 @@ impl Component for Revlog {
|
|||
}
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
commands::LOG_DETAILS_OPEN,
|
||||
commands::LOG_DETAILS_TOGGLE,
|
||||
true,
|
||||
self.visible,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
commands::LOG_DETAILS_OPEN,
|
||||
true,
|
||||
(self.visible && self.commit_details.is_visible())
|
||||
|| force_all,
|
||||
));
|
||||
|
||||
visibility_blocking(self)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ impl Stashing {
|
|||
index: FileTreeComponent::new(
|
||||
strings::STASHING_FILES_TITLE,
|
||||
true,
|
||||
queue.clone(),
|
||||
Some(queue.clone()),
|
||||
theme,
|
||||
),
|
||||
visible: false,
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ impl Status {
|
|||
queue.clone(),
|
||||
theme,
|
||||
),
|
||||
diff: DiffComponent::new(queue.clone(), theme),
|
||||
diff: DiffComponent::new(Some(queue.clone()), theme),
|
||||
git_diff: AsyncDiff::new(sender.clone()),
|
||||
git_status_workdir: AsyncStatus::new(sender.clone()),
|
||||
git_status_stage: AsyncStatus::new(sender.clone()),
|
||||
|
|
|
|||
Loading…
Reference in a new issue