mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 00:48:35 +00:00
generic popup stacking solution (#1124)
* generic popup stacking solution * allow going back to file-revision popup * do not select diff in coming back to files-revlog * handle filetree popup via stacking * allow going back to inspect commit * allow coming back to compare/inspect commit
This commit is contained in:
parent
e9d8de1be4
commit
284c57fb72
19 changed files with 635 additions and 231 deletions
|
|
@ -53,6 +53,11 @@ impl FileTree {
|
|||
self.items.file_count() == 0
|
||||
}
|
||||
|
||||
///
|
||||
pub const fn selection(&self) -> Option<usize> {
|
||||
self.selection
|
||||
}
|
||||
|
||||
///
|
||||
pub fn collapse_but_root(&mut self) {
|
||||
if !self.is_empty() {
|
||||
|
|
|
|||
76
src/app.rs
76
src/app.rs
|
|
@ -15,7 +15,10 @@ use crate::{
|
|||
},
|
||||
input::{Input, InputEvent, InputState},
|
||||
keys::{KeyConfig, SharedKeyConfig},
|
||||
queue::{Action, InternalEvent, NeedsUpdate, Queue},
|
||||
popup_stack::PopupStack,
|
||||
queue::{
|
||||
Action, InternalEvent, NeedsUpdate, Queue, StackablePopupOpen,
|
||||
},
|
||||
setup_popups,
|
||||
strings::{self, order},
|
||||
tabs::{FilesTab, Revlog, StashList, Stashing, Status},
|
||||
|
|
@ -79,6 +82,7 @@ pub struct App {
|
|||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
input: Input,
|
||||
popup_stack: PopupStack,
|
||||
|
||||
// "Flags"
|
||||
requires_redraw: Cell<bool>,
|
||||
|
|
@ -284,6 +288,7 @@ impl App {
|
|||
requires_redraw: Cell::new(false),
|
||||
file_to_open: None,
|
||||
repo,
|
||||
popup_stack: PopupStack::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -666,6 +671,31 @@ impl App {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn open_popup(
|
||||
&mut self,
|
||||
popup: StackablePopupOpen,
|
||||
) -> Result<()> {
|
||||
match popup {
|
||||
StackablePopupOpen::BlameFile(params) => {
|
||||
self.blame_file_popup.open(params)?;
|
||||
}
|
||||
StackablePopupOpen::FileRevlog(param) => {
|
||||
self.file_revlog_popup.open(param)?;
|
||||
}
|
||||
StackablePopupOpen::FileTree(param) => {
|
||||
self.revision_files_popup.open(param)?;
|
||||
}
|
||||
StackablePopupOpen::InspectCommit(param) => {
|
||||
self.inspect_commit_popup.open(param)?;
|
||||
}
|
||||
StackablePopupOpen::CompareCommits(param) => {
|
||||
self.compare_commits_popup.open(param)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_internal_events(&mut self) -> Result<NeedsUpdate> {
|
||||
let mut flags = NeedsUpdate::empty();
|
||||
|
||||
|
|
@ -715,16 +745,7 @@ impl App {
|
|||
InternalEvent::TagCommit(id) => {
|
||||
self.tag_commit_popup.open(id)?;
|
||||
}
|
||||
InternalEvent::BlameFile(path, commit_id) => {
|
||||
self.blame_file_popup.open(&path, commit_id)?;
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::OpenFileRevlog(path) => {
|
||||
self.file_revlog_popup.open(&path)?;
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
|
||||
InternalEvent::CreateBranch => {
|
||||
self.create_branch_popup.open()?;
|
||||
}
|
||||
|
|
@ -739,11 +760,6 @@ impl App {
|
|||
self.tags_popup.open()?;
|
||||
}
|
||||
InternalEvent::TabSwitchStatus => self.set_tab(0)?,
|
||||
InternalEvent::InspectCommit(id, tags) => {
|
||||
self.inspect_commit_popup.open(id, tags)?;
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::SelectCommitInRevlog(id) => {
|
||||
if let Err(error) = self.revlog.select_commit(id) {
|
||||
self.queue.push(InternalEvent::ShowErrorMsg(
|
||||
|
|
@ -788,11 +804,6 @@ impl App {
|
|||
InternalEvent::StatusLastFileMoved => {
|
||||
self.status_tab.last_file_moved()?;
|
||||
}
|
||||
InternalEvent::OpenFileTree(c) => {
|
||||
self.revision_files_popup.open(c)?;
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::OpenFileFinder(files) => {
|
||||
self.find_file_popup.open(&files)?;
|
||||
flags
|
||||
|
|
@ -812,17 +823,30 @@ impl App {
|
|||
|
||||
flags.insert(NeedsUpdate::ALL);
|
||||
}
|
||||
InternalEvent::CompareCommits(id, other) => {
|
||||
self.compare_commits_popup.open(id, other)?;
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::FileFinderChanged(file) => {
|
||||
self.files_tab.file_finder_update(&file);
|
||||
self.revision_files_popup.file_finder_update(&file);
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::OpenPopup(popup) => {
|
||||
self.open_popup(popup)?;
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::PopupStackPop => {
|
||||
if let Some(popup) = self.popup_stack.pop() {
|
||||
self.open_popup(popup)?;
|
||||
flags.insert(
|
||||
NeedsUpdate::ALL | NeedsUpdate::COMMANDS,
|
||||
);
|
||||
}
|
||||
}
|
||||
InternalEvent::PopupStackPush(popup) => {
|
||||
self.popup_stack.push(popup);
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(flags)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use super::{
|
||||
utils, visibility_blocking, CommandBlocking, CommandInfo,
|
||||
Component, DrawableComponent, EventState,
|
||||
Component, DrawableComponent, EventState, FileRevOpen,
|
||||
InspectCommitOpen,
|
||||
};
|
||||
use crate::{
|
||||
components::{utils::string_width_align, ScrollType},
|
||||
keys::SharedKeyConfig,
|
||||
queue::{InternalEvent, Queue},
|
||||
queue::{InternalEvent, Queue, StackablePopupOpen},
|
||||
string_utils::tabs_to_spaces,
|
||||
strings,
|
||||
ui::{self, style::SharedTheme},
|
||||
|
|
@ -27,41 +28,31 @@ use tui::{
|
|||
Frame,
|
||||
};
|
||||
|
||||
static NO_COMMIT_ID: &str = "0000000";
|
||||
static NO_AUTHOR: &str = "<no author>";
|
||||
static MIN_AUTHOR_WIDTH: usize = 3;
|
||||
static MAX_AUTHOR_WIDTH: usize = 20;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlameFileOpen {
|
||||
pub file_path: String,
|
||||
pub commit_id: Option<CommitId>,
|
||||
pub selection: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct BlameFileComponent {
|
||||
title: String,
|
||||
theme: SharedTheme,
|
||||
queue: Queue,
|
||||
async_blame: AsyncBlame,
|
||||
visible: bool,
|
||||
open_request: Option<BlameFileOpen>,
|
||||
params: Option<BlameParams>,
|
||||
file_blame: Option<FileBlame>,
|
||||
table_state: std::cell::Cell<TableState>,
|
||||
key_config: SharedKeyConfig,
|
||||
current_height: std::cell::Cell<usize>,
|
||||
}
|
||||
|
||||
static NO_COMMIT_ID: &str = "0000000";
|
||||
static NO_AUTHOR: &str = "<no author>";
|
||||
static MIN_AUTHOR_WIDTH: usize = 3;
|
||||
static MAX_AUTHOR_WIDTH: usize = 20;
|
||||
|
||||
fn get_author_width(width: usize) -> usize {
|
||||
(width.saturating_sub(19) / 3)
|
||||
.clamp(MIN_AUTHOR_WIDTH, MAX_AUTHOR_WIDTH)
|
||||
}
|
||||
|
||||
const fn number_of_digits(number: usize) -> usize {
|
||||
let mut rest = number;
|
||||
let mut result = 0;
|
||||
|
||||
while rest > 0 {
|
||||
rest /= 10;
|
||||
result += 1;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
impl DrawableComponent for BlameFileComponent {
|
||||
fn draw<B: Backend>(
|
||||
&self,
|
||||
|
|
@ -197,7 +188,7 @@ impl Component for BlameFileComponent {
|
|||
if self.is_visible() {
|
||||
if let Event::Key(key) = event {
|
||||
if key == self.key_config.keys.exit_popup {
|
||||
self.hide();
|
||||
self.hide_stacked(false);
|
||||
} else if key == self.key_config.keys.move_up {
|
||||
self.move_selection(ScrollType::Up);
|
||||
} else if key == self.key_config.keys.move_down {
|
||||
|
|
@ -215,11 +206,13 @@ impl Component for BlameFileComponent {
|
|||
} else if key == self.key_config.keys.page_up {
|
||||
self.move_selection(ScrollType::PageUp);
|
||||
} else if key == self.key_config.keys.focus_right {
|
||||
if let Some(id) = self.selected_commit() {
|
||||
self.hide();
|
||||
self.queue.push(
|
||||
InternalEvent::InspectCommit(id, None),
|
||||
);
|
||||
if let Some(commit_id) = self.selected_commit() {
|
||||
self.hide_stacked(true);
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::InspectCommit(
|
||||
InspectCommitOpen::new(commit_id),
|
||||
),
|
||||
));
|
||||
}
|
||||
} else if key == self.key_config.keys.file_history {
|
||||
if let Some(filepath) = self
|
||||
|
|
@ -227,10 +220,12 @@ impl Component for BlameFileComponent {
|
|||
.as_ref()
|
||||
.map(|p| p.file_path.clone())
|
||||
{
|
||||
self.hide();
|
||||
self.queue.push(
|
||||
InternalEvent::OpenFileRevlog(filepath),
|
||||
);
|
||||
self.hide_stacked(true);
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::FileRevlog(
|
||||
FileRevOpen::new(filepath),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,10 +240,6 @@ impl Component for BlameFileComponent {
|
|||
self.visible
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false;
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
|
||||
|
|
@ -277,25 +268,40 @@ impl BlameFileComponent {
|
|||
visible: false,
|
||||
params: None,
|
||||
file_blame: None,
|
||||
open_request: None,
|
||||
table_state: std::cell::Cell::new(TableState::default()),
|
||||
key_config,
|
||||
current_height: std::cell::Cell::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn hide_stacked(&mut self, stack: bool) {
|
||||
self.visible = false;
|
||||
if stack {
|
||||
if let Some(request) = self.open_request.clone() {
|
||||
self.queue.push(InternalEvent::PopupStackPush(
|
||||
StackablePopupOpen::BlameFile(BlameFileOpen {
|
||||
file_path: request.file_path,
|
||||
commit_id: request.commit_id,
|
||||
selection: self.get_selection(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.queue.push(InternalEvent::PopupStackPop);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn open(
|
||||
&mut self,
|
||||
file_path: &str,
|
||||
commit_id: Option<CommitId>,
|
||||
) -> Result<()> {
|
||||
pub fn open(&mut self, open: BlameFileOpen) -> Result<()> {
|
||||
self.open_request = Some(open.clone());
|
||||
self.params = Some(BlameParams {
|
||||
file_path: file_path.into(),
|
||||
commit_id,
|
||||
file_path: open.file_path,
|
||||
commit_id: open.commit_id,
|
||||
});
|
||||
self.file_blame = None;
|
||||
self.table_state.get_mut().select(Some(0));
|
||||
self.show()?;
|
||||
self.visible = true;
|
||||
|
||||
self.update()?;
|
||||
|
||||
|
|
@ -329,6 +335,7 @@ impl BlameFileComponent {
|
|||
{
|
||||
if previous_blame_params == *params {
|
||||
self.file_blame = Some(last_file_blame);
|
||||
self.set_open_selection();
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -526,6 +533,28 @@ impl BlameFileComponent {
|
|||
needs_update
|
||||
}
|
||||
|
||||
fn set_open_selection(&mut self) {
|
||||
if let Some(selection) =
|
||||
self.open_request.as_ref().and_then(|req| req.selection)
|
||||
{
|
||||
let mut table_state = self.table_state.take();
|
||||
table_state.select(Some(selection));
|
||||
self.table_state.set(table_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_selection(&self) -> Option<usize> {
|
||||
self.file_blame.as_ref().and_then(|_| {
|
||||
let table_state = self.table_state.take();
|
||||
|
||||
let selection = table_state.selected();
|
||||
|
||||
self.table_state.set(table_state);
|
||||
|
||||
selection
|
||||
})
|
||||
}
|
||||
|
||||
fn selected_commit(&self) -> Option<CommitId> {
|
||||
self.file_blame.as_ref().and_then(|file_blame| {
|
||||
let table_state = self.table_state.take();
|
||||
|
|
@ -544,3 +573,20 @@ impl BlameFileComponent {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_author_width(width: usize) -> usize {
|
||||
(width.saturating_sub(19) / 3)
|
||||
.clamp(MIN_AUTHOR_WIDTH, MAX_AUTHOR_WIDTH)
|
||||
}
|
||||
|
||||
const fn number_of_digits(number: usize) -> usize {
|
||||
let mut rest = number;
|
||||
let mut result = 0;
|
||||
|
||||
while rest > 0 {
|
||||
rest /= 10;
|
||||
result += 1;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use super::{
|
||||
utils::scroll_vertical::VerticalScroll, visibility_blocking,
|
||||
CommandBlocking, CommandInfo, Component, DrawableComponent,
|
||||
EventState,
|
||||
EventState, InspectCommitOpen,
|
||||
};
|
||||
use crate::{
|
||||
components::ScrollType,
|
||||
keys::SharedKeyConfig,
|
||||
queue::{Action, InternalEvent, NeedsUpdate, Queue},
|
||||
queue::{
|
||||
Action, InternalEvent, NeedsUpdate, Queue, StackablePopupOpen,
|
||||
},
|
||||
strings, try_or_popup,
|
||||
ui::{self, Size},
|
||||
};
|
||||
|
|
@ -286,18 +288,17 @@ impl Component for BranchListComponent {
|
|||
} else if e == self.key_config.keys.move_right
|
||||
&& self.valid_selection()
|
||||
{
|
||||
self.hide();
|
||||
if let Some(b) = self.get_selected() {
|
||||
self.queue
|
||||
.push(InternalEvent::InspectCommit(b, None));
|
||||
}
|
||||
self.inspect_head_of_branch();
|
||||
} else if e == self.key_config.keys.compare_commits
|
||||
&& self.valid_selection()
|
||||
{
|
||||
self.hide();
|
||||
if let Some(b) = self.get_selected() {
|
||||
self.queue
|
||||
.push(InternalEvent::CompareCommits(b, None));
|
||||
if let Some(commit_id) = self.get_selected() {
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::CompareCommits(
|
||||
InspectCommitOpen::new(commit_id),
|
||||
),
|
||||
));
|
||||
}
|
||||
} else if e == self.key_config.keys.pull
|
||||
&& !self.local && self.has_remotes
|
||||
|
|
@ -432,6 +433,17 @@ impl BranchListComponent {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn inspect_head_of_branch(&mut self) {
|
||||
if let Some(commit_id) = self.get_selected() {
|
||||
self.hide();
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::InspectCommit(
|
||||
InspectCommitOpen::new(commit_id),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const fn get_branch_type(&self) -> BranchType {
|
||||
if self.local {
|
||||
BranchType::Local
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ impl ChangesComponent {
|
|||
|
||||
///
|
||||
pub fn set_items(&mut self, list: &[StatusItem]) -> Result<()> {
|
||||
self.files.show()?;
|
||||
self.files.update(list)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,6 +160,10 @@ impl DrawableComponent for CommitDetailsComponent {
|
|||
f: &mut Frame<B>,
|
||||
rect: Rect,
|
||||
) -> Result<()> {
|
||||
if !self.visible {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let constraints = if self.is_compare() {
|
||||
[Constraint::Length(10), Constraint::Min(0)]
|
||||
} else {
|
||||
|
|
@ -215,6 +219,10 @@ impl Component for CommitDetailsComponent {
|
|||
if event_pump(ev, self.components_mut().as_mut_slice())?
|
||||
.is_consumed()
|
||||
{
|
||||
if !self.file_tree.is_visible() {
|
||||
self.hide();
|
||||
}
|
||||
|
||||
return Ok(EventState::Consumed);
|
||||
}
|
||||
|
||||
|
|
@ -250,6 +258,7 @@ impl Component for CommitDetailsComponent {
|
|||
}
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
self.file_tree.show()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
use super::{
|
||||
command_pump, event_pump, visibility_blocking, CommandBlocking,
|
||||
CommandInfo, CommitDetailsComponent, Component, DiffComponent,
|
||||
DrawableComponent, EventState,
|
||||
DrawableComponent, EventState, InspectCommitOpen,
|
||||
};
|
||||
use crate::{
|
||||
accessors, keys::SharedKeyConfig, queue::Queue, strings,
|
||||
accessors,
|
||||
keys::SharedKeyConfig,
|
||||
queue::{InternalEvent, Queue, StackablePopupOpen},
|
||||
strings,
|
||||
ui::style::SharedTheme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
|
@ -24,12 +27,13 @@ use tui::{
|
|||
|
||||
pub struct CompareCommitsComponent {
|
||||
repo: RepoPathRef,
|
||||
commit_ids: Option<(CommitId, CommitId)>,
|
||||
open_request: Option<InspectCommitOpen>,
|
||||
diff: DiffComponent,
|
||||
details: CommitDetailsComponent,
|
||||
git_diff: AsyncDiff,
|
||||
visible: bool,
|
||||
key_config: SharedKeyConfig,
|
||||
queue: Queue,
|
||||
}
|
||||
|
||||
impl DrawableComponent for CompareCommitsComponent {
|
||||
|
|
@ -109,12 +113,15 @@ impl Component for CompareCommitsComponent {
|
|||
if event_pump(ev, self.components_mut().as_mut_slice())?
|
||||
.is_consumed()
|
||||
{
|
||||
if !self.details.is_visible() {
|
||||
self.hide_stacked(true);
|
||||
}
|
||||
return Ok(EventState::Consumed);
|
||||
}
|
||||
|
||||
if let Event::Key(e) = ev {
|
||||
if e == self.key_config.keys.exit_popup {
|
||||
self.hide();
|
||||
self.hide_stacked(false);
|
||||
} else if e == self.key_config.keys.focus_right
|
||||
&& self.can_focus_diff()
|
||||
{
|
||||
|
|
@ -126,7 +133,7 @@ impl Component for CompareCommitsComponent {
|
|||
self.details.focus(true);
|
||||
self.diff.focus(false);
|
||||
} else if e == self.key_config.keys.focus_left {
|
||||
self.hide();
|
||||
self.hide_stacked(false);
|
||||
}
|
||||
|
||||
return Ok(EventState::Consumed);
|
||||
|
|
@ -179,25 +186,26 @@ impl CompareCommitsComponent {
|
|||
key_config.clone(),
|
||||
true,
|
||||
),
|
||||
commit_ids: None,
|
||||
open_request: None,
|
||||
git_diff: AsyncDiff::new(repo.borrow().clone(), sender),
|
||||
visible: false,
|
||||
key_config,
|
||||
queue: queue.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn open(
|
||||
&mut self,
|
||||
id: CommitId,
|
||||
other: Option<CommitId>,
|
||||
) -> Result<()> {
|
||||
let other = if let Some(other) = other {
|
||||
other
|
||||
pub fn open(&mut self, open: InspectCommitOpen) -> Result<()> {
|
||||
let compare_id = if let Some(compare_id) = open.compare_id {
|
||||
compare_id
|
||||
} else {
|
||||
sync::get_head_tuple(&self.repo.borrow())?.id
|
||||
};
|
||||
self.commit_ids = Some((id, other));
|
||||
self.open_request = Some(InspectCommitOpen {
|
||||
commit_id: open.commit_id,
|
||||
compare_id: Some(compare_id),
|
||||
tags: open.tags,
|
||||
});
|
||||
self.show()?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -224,10 +232,22 @@ impl CompareCommitsComponent {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_ids(&self) -> Option<(CommitId, CommitId)> {
|
||||
let other = self
|
||||
.open_request
|
||||
.as_ref()
|
||||
.and_then(|open| open.compare_id);
|
||||
|
||||
self.open_request
|
||||
.as_ref()
|
||||
.map(|open| open.commit_id)
|
||||
.zip(other)
|
||||
}
|
||||
|
||||
/// called when any tree component changed selection
|
||||
pub fn update_diff(&mut self) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
if let Some(ids) = self.commit_ids {
|
||||
if let Some(ids) = self.get_ids() {
|
||||
if let Some(f) = self.details.files().selection_file()
|
||||
{
|
||||
let diff_params = DiffParams {
|
||||
|
|
@ -259,7 +279,7 @@ impl CompareCommitsComponent {
|
|||
|
||||
fn update(&mut self) -> Result<()> {
|
||||
self.details.set_commits(
|
||||
self.commit_ids.map(CommitFilesParams::from),
|
||||
self.get_ids().map(CommitFilesParams::from),
|
||||
None,
|
||||
)?;
|
||||
self.update_diff()?;
|
||||
|
|
@ -270,4 +290,17 @@ impl CompareCommitsComponent {
|
|||
fn can_focus_diff(&self) -> bool {
|
||||
self.details.files().selection_file().is_some()
|
||||
}
|
||||
|
||||
fn hide_stacked(&mut self, stack: bool) {
|
||||
self.hide();
|
||||
if stack {
|
||||
if let Some(request) = self.open_request.clone() {
|
||||
self.queue.push(InternalEvent::PopupStackPush(
|
||||
StackablePopupOpen::CompareCommits(request),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.queue.push(InternalEvent::PopupStackPop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use super::visibility_blocking;
|
||||
use super::{utils::logitems::ItemBatch, SharedOptions};
|
||||
use super::{visibility_blocking, BlameFileOpen, InspectCommitOpen};
|
||||
use crate::queue::StackablePopupOpen;
|
||||
use crate::{
|
||||
components::{
|
||||
event_pump, CommandBlocking, CommandInfo, Component,
|
||||
|
|
@ -31,6 +32,21 @@ use tui::{
|
|||
|
||||
const SLICE_SIZE: usize = 1200;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileRevOpen {
|
||||
pub file_path: String,
|
||||
pub selection: Option<usize>,
|
||||
}
|
||||
|
||||
impl FileRevOpen {
|
||||
pub const fn new(file_path: String) -> Self {
|
||||
Self {
|
||||
file_path,
|
||||
selection: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub struct FileRevlogComponent {
|
||||
git_log: Option<AsyncLog>,
|
||||
|
|
@ -41,7 +57,7 @@ pub struct FileRevlogComponent {
|
|||
diff: DiffComponent,
|
||||
visible: bool,
|
||||
repo_path: RepoPathRef,
|
||||
file_path: Option<String>,
|
||||
open_request: Option<FileRevOpen>,
|
||||
table_state: std::cell::Cell<TableState>,
|
||||
items: ItemBatch,
|
||||
count_total: usize,
|
||||
|
|
@ -79,7 +95,7 @@ impl FileRevlogComponent {
|
|||
),
|
||||
visible: false,
|
||||
repo_path: repo_path.clone(),
|
||||
file_path: None,
|
||||
open_request: None,
|
||||
table_state: std::cell::Cell::new(TableState::default()),
|
||||
items: ItemBatch::default(),
|
||||
count_total: 0,
|
||||
|
|
@ -95,12 +111,12 @@ impl FileRevlogComponent {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn open(&mut self, file_path: &str) -> Result<()> {
|
||||
self.file_path = Some(file_path.into());
|
||||
pub fn open(&mut self, open_request: FileRevOpen) -> Result<()> {
|
||||
self.open_request = Some(open_request.clone());
|
||||
|
||||
let filter = diff_contains_file(
|
||||
self.repo_path.borrow().clone(),
|
||||
file_path.into(),
|
||||
open_request.file_path,
|
||||
);
|
||||
self.git_log = Some(AsyncLog::new(
|
||||
self.repo_path.borrow().clone(),
|
||||
|
|
@ -109,6 +125,8 @@ impl FileRevlogComponent {
|
|||
));
|
||||
self.table_state.get_mut().select(Some(0));
|
||||
self.show()?;
|
||||
|
||||
self.diff.focus(false);
|
||||
self.diff.clear(false);
|
||||
|
||||
self.update()?;
|
||||
|
|
@ -139,6 +157,7 @@ impl FileRevlogComponent {
|
|||
|| log_changed
|
||||
{
|
||||
self.fetch_commits()?;
|
||||
self.set_open_selection();
|
||||
}
|
||||
|
||||
self.update_diff()?;
|
||||
|
|
@ -167,9 +186,9 @@ impl FileRevlogComponent {
|
|||
pub fn update_diff(&mut self) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
if let Some(commit_id) = self.selected_commit() {
|
||||
if let Some(file_path) = &self.file_path {
|
||||
if let Some(open_request) = &self.open_request {
|
||||
let diff_params = DiffParams {
|
||||
path: file_path.clone(),
|
||||
path: open_request.file_path.clone(),
|
||||
diff_type: DiffType::Commit(commit_id),
|
||||
options: self.options.borrow().diff,
|
||||
};
|
||||
|
|
@ -179,7 +198,7 @@ impl FileRevlogComponent {
|
|||
{
|
||||
if params == diff_params {
|
||||
self.diff.update(
|
||||
file_path.to_string(),
|
||||
open_request.file_path.to_string(),
|
||||
false,
|
||||
last,
|
||||
);
|
||||
|
|
@ -253,11 +272,13 @@ impl FileRevlogComponent {
|
|||
};
|
||||
let revisions = self.get_max_selection();
|
||||
|
||||
self.file_path.as_ref().map_or(
|
||||
self.open_request.as_ref().map_or(
|
||||
"<no history available>".into(),
|
||||
|file_path| {
|
||||
|open_request| {
|
||||
strings::file_log_title(
|
||||
file_path, selected, revisions,
|
||||
&open_request.file_path,
|
||||
selected,
|
||||
revisions,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -333,6 +354,24 @@ impl FileRevlogComponent {
|
|||
needs_update
|
||||
}
|
||||
|
||||
fn set_open_selection(&mut self) {
|
||||
if let Some(selection) =
|
||||
self.open_request.as_ref().and_then(|req| req.selection)
|
||||
{
|
||||
let mut table_state = self.table_state.take();
|
||||
table_state.select(Some(selection));
|
||||
self.table_state.set(table_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_selection(&self) -> Option<usize> {
|
||||
let table_state = self.table_state.take();
|
||||
let selection = table_state.selected();
|
||||
self.table_state.set(table_state);
|
||||
|
||||
selection
|
||||
}
|
||||
|
||||
fn draw_revlog<B: Backend>(&self, f: &mut Frame<B>, area: Rect) {
|
||||
let constraints = [
|
||||
// type of change: (A)dded, (M)odified, (D)eleted
|
||||
|
|
@ -377,6 +416,23 @@ impl FileRevlogComponent {
|
|||
self.current_width.set(area.width.into());
|
||||
self.current_height.set(area.height.into());
|
||||
}
|
||||
|
||||
fn hide_stacked(&mut self, stack: bool) {
|
||||
self.hide();
|
||||
|
||||
if stack {
|
||||
if let Some(open_request) = self.open_request.clone() {
|
||||
self.queue.push(InternalEvent::PopupStackPush(
|
||||
StackablePopupOpen::FileRevlog(FileRevOpen {
|
||||
file_path: open_request.file_path,
|
||||
selection: self.get_selection(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.queue.push(InternalEvent::PopupStackPop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for FileRevlogComponent {
|
||||
|
|
@ -427,7 +483,7 @@ impl Component for FileRevlogComponent {
|
|||
|
||||
if let Event::Key(key) = event {
|
||||
if key == self.key_config.keys.exit_popup {
|
||||
self.hide();
|
||||
self.hide_stacked(false);
|
||||
} else if key == self.key_config.keys.focus_right
|
||||
&& self.can_focus_diff()
|
||||
{
|
||||
|
|
@ -437,18 +493,27 @@ impl Component for FileRevlogComponent {
|
|||
self.diff.focus(false);
|
||||
}
|
||||
} else if key == self.key_config.keys.enter {
|
||||
if let Some(id) = self.selected_commit() {
|
||||
self.hide();
|
||||
self.queue.push(
|
||||
InternalEvent::InspectCommit(id, None),
|
||||
);
|
||||
if let Some(commit_id) = self.selected_commit() {
|
||||
self.hide_stacked(true);
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::InspectCommit(
|
||||
InspectCommitOpen::new(commit_id),
|
||||
),
|
||||
));
|
||||
};
|
||||
} else if key == self.key_config.keys.blame {
|
||||
if let Some(file) = self.file_path.clone() {
|
||||
self.hide();
|
||||
self.queue.push(InternalEvent::BlameFile(
|
||||
file,
|
||||
self.selected_commit(),
|
||||
if let Some(open_request) =
|
||||
self.open_request.clone()
|
||||
{
|
||||
self.hide_stacked(true);
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::BlameFile(
|
||||
BlameFileOpen {
|
||||
file_path: open_request.file_path,
|
||||
commit_id: self.selected_commit(),
|
||||
selection: None,
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
} else if key == self.key_config.keys.move_up {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
use super::{
|
||||
command_pump, event_pump, visibility_blocking, CommandBlocking,
|
||||
CommandInfo, CommitDetailsComponent, Component, DiffComponent,
|
||||
DrawableComponent, EventState,
|
||||
DrawableComponent, EventState, FileTreeOpen,
|
||||
};
|
||||
use crate::{
|
||||
accessors,
|
||||
keys::SharedKeyConfig,
|
||||
queue::{InternalEvent, Queue},
|
||||
queue::{InternalEvent, Queue, StackablePopupOpen},
|
||||
strings,
|
||||
ui::style::SharedTheme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{
|
||||
sync::{diff::DiffOptions, CommitId, CommitTags, RepoPathRef},
|
||||
AsyncDiff, AsyncGitNotification, CommitFilesParams, DiffParams,
|
||||
DiffType,
|
||||
AsyncDiff, AsyncGitNotification, DiffParams, DiffType,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
|
|
@ -25,10 +24,38 @@ use tui::{
|
|||
Frame,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InspectCommitOpen {
|
||||
pub commit_id: CommitId,
|
||||
/// in case we wanna compare
|
||||
pub compare_id: Option<CommitId>,
|
||||
pub tags: Option<CommitTags>,
|
||||
}
|
||||
|
||||
impl InspectCommitOpen {
|
||||
pub const fn new(commit_id: CommitId) -> Self {
|
||||
Self {
|
||||
commit_id,
|
||||
compare_id: None,
|
||||
tags: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_tags(
|
||||
commit_id: CommitId,
|
||||
tags: Option<CommitTags>,
|
||||
) -> Self {
|
||||
Self {
|
||||
commit_id,
|
||||
compare_id: None,
|
||||
tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InspectCommitComponent {
|
||||
queue: Queue,
|
||||
commit_id: Option<CommitId>,
|
||||
tags: Option<CommitTags>,
|
||||
open_request: Option<InspectCommitOpen>,
|
||||
diff: DiffComponent,
|
||||
details: CommitDetailsComponent,
|
||||
git_diff: AsyncDiff,
|
||||
|
|
@ -121,12 +148,16 @@ impl Component for InspectCommitComponent {
|
|||
if event_pump(ev, self.components_mut().as_mut_slice())?
|
||||
.is_consumed()
|
||||
{
|
||||
if !self.details.is_visible() {
|
||||
self.hide_stacked(true);
|
||||
}
|
||||
|
||||
return Ok(EventState::Consumed);
|
||||
}
|
||||
|
||||
if let Event::Key(e) = ev {
|
||||
if e == self.key_config.keys.exit_popup {
|
||||
self.hide();
|
||||
self.hide_stacked(false);
|
||||
} else if e == self.key_config.keys.focus_right
|
||||
&& self.can_focus_diff()
|
||||
{
|
||||
|
|
@ -138,14 +169,20 @@ impl Component for InspectCommitComponent {
|
|||
self.details.focus(true);
|
||||
self.diff.focus(false);
|
||||
} else if e == self.key_config.keys.open_file_tree {
|
||||
if let Some(commit) = self.commit_id {
|
||||
self.queue.push(InternalEvent::OpenFileTree(
|
||||
commit,
|
||||
if let Some(commit) = self
|
||||
.open_request
|
||||
.as_ref()
|
||||
.map(|open| open.commit_id)
|
||||
{
|
||||
self.hide_stacked(true);
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::FileTree(
|
||||
FileTreeOpen::new(commit),
|
||||
),
|
||||
));
|
||||
self.hide();
|
||||
}
|
||||
} else if e == self.key_config.keys.focus_left {
|
||||
self.hide();
|
||||
self.hide_stacked(false);
|
||||
}
|
||||
|
||||
return Ok(EventState::Consumed);
|
||||
|
|
@ -198,8 +235,7 @@ impl InspectCommitComponent {
|
|||
key_config.clone(),
|
||||
true,
|
||||
),
|
||||
commit_id: None,
|
||||
tags: None,
|
||||
open_request: None,
|
||||
git_diff: AsyncDiff::new(repo.borrow().clone(), sender),
|
||||
visible: false,
|
||||
key_config,
|
||||
|
|
@ -207,13 +243,8 @@ impl InspectCommitComponent {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn open(
|
||||
&mut self,
|
||||
id: CommitId,
|
||||
tags: Option<CommitTags>,
|
||||
) -> Result<()> {
|
||||
self.commit_id = Some(id);
|
||||
self.tags = tags;
|
||||
pub fn open(&mut self, open: InspectCommitOpen) -> Result<()> {
|
||||
self.open_request = Some(open);
|
||||
self.show()?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -243,12 +274,14 @@ impl InspectCommitComponent {
|
|||
/// 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(request) = &self.open_request {
|
||||
if let Some(f) = self.details.files().selection_file()
|
||||
{
|
||||
let diff_params = DiffParams {
|
||||
path: f.path.clone(),
|
||||
diff_type: DiffType::Commit(id),
|
||||
diff_type: DiffType::Commit(
|
||||
request.commit_id,
|
||||
),
|
||||
options: DiffOptions::default(),
|
||||
};
|
||||
|
||||
|
|
@ -274,11 +307,14 @@ impl InspectCommitComponent {
|
|||
}
|
||||
|
||||
fn update(&mut self) -> Result<()> {
|
||||
self.details.set_commits(
|
||||
self.commit_id.map(CommitFilesParams::from),
|
||||
self.tags.clone(),
|
||||
)?;
|
||||
self.update_diff()?;
|
||||
if let Some(request) = &self.open_request {
|
||||
//TODO: pass as reference and only clone if details changed
|
||||
self.details.set_commits(
|
||||
Some(request.commit_id.into()),
|
||||
request.tags.clone(),
|
||||
)?;
|
||||
self.update_diff()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -286,4 +322,18 @@ impl InspectCommitComponent {
|
|||
fn can_focus_diff(&self) -> bool {
|
||||
self.details.files().selection_file().is_some()
|
||||
}
|
||||
|
||||
fn hide_stacked(&mut self, stack: bool) {
|
||||
self.hide();
|
||||
|
||||
if stack {
|
||||
if let Some(open_request) = self.open_request.take() {
|
||||
self.queue.push(InternalEvent::PopupStackPush(
|
||||
StackablePopupOpen::InspectCommit(open_request),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.queue.push(InternalEvent::PopupStackPop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ mod textinput;
|
|||
mod utils;
|
||||
|
||||
pub use self::status_tree::StatusTreeComponent;
|
||||
pub use blame_file::BlameFileComponent;
|
||||
pub use blame_file::{BlameFileComponent, BlameFileOpen};
|
||||
pub use branchlist::BranchListComponent;
|
||||
pub use changes::ChangesComponent;
|
||||
pub use command::{CommandInfo, CommandText};
|
||||
|
|
@ -46,9 +46,9 @@ pub use diff::DiffComponent;
|
|||
pub use externaleditor::ExternalEditorComponent;
|
||||
pub use fetch::FetchComponent;
|
||||
pub use file_find_popup::FileFindPopup;
|
||||
pub use file_revlog::FileRevlogComponent;
|
||||
pub use file_revlog::{FileRevOpen, FileRevlogComponent};
|
||||
pub use help::HelpComponent;
|
||||
pub use inspect_commit::InspectCommitComponent;
|
||||
pub use inspect_commit::{InspectCommitComponent, InspectCommitOpen};
|
||||
pub use msg::MsgComponent;
|
||||
pub use options_popup::{
|
||||
AppOption, OptionsPopupComponent, SharedOptions,
|
||||
|
|
@ -59,7 +59,7 @@ pub use push_tags::PushTagsComponent;
|
|||
pub use rename_branch::RenameBranchComponent;
|
||||
pub use reset::ConfirmComponent;
|
||||
pub use revision_files::RevisionFilesComponent;
|
||||
pub use revision_files_popup::RevisionFilesPopup;
|
||||
pub use revision_files_popup::{FileTreeOpen, RevisionFilesPopup};
|
||||
pub use stashmsg::StashMsgComponent;
|
||||
pub use syntax_text::SyntaxTextComponent;
|
||||
pub use tag_commit::TagCommitComponent;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use super::{
|
||||
utils::scroll_vertical::VerticalScroll, CommandBlocking,
|
||||
CommandInfo, Component, DrawableComponent, EventState,
|
||||
SyntaxTextComponent,
|
||||
utils::scroll_vertical::VerticalScroll, BlameFileOpen,
|
||||
CommandBlocking, CommandInfo, Component, DrawableComponent,
|
||||
EventState, FileRevOpen, SyntaxTextComponent,
|
||||
};
|
||||
use crate::{
|
||||
keys::SharedKeyConfig,
|
||||
queue::{InternalEvent, Queue},
|
||||
queue::{InternalEvent, Queue, StackablePopupOpen},
|
||||
strings::{self, order, symbol},
|
||||
ui::{self, common_nav, style::SharedTheme},
|
||||
AsyncAppNotification, AsyncNotification,
|
||||
|
|
@ -42,6 +42,7 @@ pub struct RevisionFilesComponent {
|
|||
current_file: SyntaxTextComponent,
|
||||
tree: FileTree,
|
||||
scroll: VerticalScroll,
|
||||
visible: bool,
|
||||
revision: Option<CommitId>,
|
||||
focus: Focus,
|
||||
key_config: SharedKeyConfig,
|
||||
|
|
@ -72,11 +73,14 @@ impl RevisionFilesComponent {
|
|||
focus: Focus::Tree,
|
||||
key_config,
|
||||
repo,
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn set_commit(&mut self, commit: CommitId) -> Result<()> {
|
||||
self.show()?;
|
||||
|
||||
let same_id =
|
||||
self.revision.map(|c| c == commit).unwrap_or_default();
|
||||
if !same_id {
|
||||
|
|
@ -92,6 +96,16 @@ impl RevisionFilesComponent {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
pub const fn revision(&self) -> Option<CommitId> {
|
||||
self.revision
|
||||
}
|
||||
|
||||
///
|
||||
pub const fn selection(&self) -> Option<usize> {
|
||||
self.tree.selection()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn update(&mut self, ev: AsyncNotification) {
|
||||
self.current_file.update(ev);
|
||||
|
|
@ -133,8 +147,13 @@ impl RevisionFilesComponent {
|
|||
|
||||
fn blame(&self) -> bool {
|
||||
self.selected_file_path().map_or(false, |path| {
|
||||
self.queue
|
||||
.push(InternalEvent::BlameFile(path, self.revision));
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::BlameFile(BlameFileOpen {
|
||||
file_path: path,
|
||||
commit_id: self.revision,
|
||||
selection: None,
|
||||
}),
|
||||
));
|
||||
|
||||
true
|
||||
})
|
||||
|
|
@ -142,7 +161,11 @@ impl RevisionFilesComponent {
|
|||
|
||||
fn file_history(&self) -> bool {
|
||||
self.selected_file_path().map_or(false, |path| {
|
||||
self.queue.push(InternalEvent::OpenFileRevlog(path));
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::FileRevlog(FileRevOpen::new(
|
||||
path,
|
||||
)),
|
||||
));
|
||||
|
||||
true
|
||||
})
|
||||
|
|
@ -278,6 +301,10 @@ impl Component for RevisionFilesComponent {
|
|||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
if !self.is_visible() && !force_all {
|
||||
return CommandBlocking::PassingOn;
|
||||
}
|
||||
|
||||
let is_tree_focused = matches!(self.focus, Focus::Tree);
|
||||
|
||||
if is_tree_focused || force_all {
|
||||
|
|
@ -316,6 +343,10 @@ impl Component for RevisionFilesComponent {
|
|||
&mut self,
|
||||
event: crossterm::event::Event,
|
||||
) -> Result<EventState> {
|
||||
if !self.is_visible() {
|
||||
return Ok(EventState::NotConsumed);
|
||||
}
|
||||
|
||||
if let Event::Key(key) = event {
|
||||
let is_tree_focused = matches!(self.focus, Focus::Tree);
|
||||
if is_tree_focused
|
||||
|
|
@ -371,6 +402,19 @@ impl Component for RevisionFilesComponent {
|
|||
|
||||
Ok(EventState::NotConsumed)
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false;
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: reuse for other tree usages
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use super::{
|
|||
};
|
||||
use crate::{
|
||||
keys::SharedKeyConfig,
|
||||
queue::Queue,
|
||||
queue::{InternalEvent, Queue, StackablePopupOpen},
|
||||
strings::{self},
|
||||
ui::style::SharedTheme,
|
||||
AsyncAppNotification, AsyncNotification,
|
||||
|
|
@ -18,10 +18,27 @@ use crossbeam_channel::Sender;
|
|||
use crossterm::event::Event;
|
||||
use tui::{backend::Backend, layout::Rect, widgets::Clear, Frame};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileTreeOpen {
|
||||
pub commit_id: CommitId,
|
||||
pub selection: Option<usize>,
|
||||
}
|
||||
|
||||
impl FileTreeOpen {
|
||||
pub const fn new(commit_id: CommitId) -> Self {
|
||||
Self {
|
||||
commit_id,
|
||||
selection: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RevisionFilesPopup {
|
||||
open_request: Option<FileTreeOpen>,
|
||||
visible: bool,
|
||||
key_config: SharedKeyConfig,
|
||||
files: RevisionFilesComponent,
|
||||
queue: Queue,
|
||||
}
|
||||
|
||||
impl RevisionFilesPopup {
|
||||
|
|
@ -43,12 +60,15 @@ impl RevisionFilesPopup {
|
|||
),
|
||||
visible: false,
|
||||
key_config,
|
||||
open_request: None,
|
||||
queue: queue.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn open(&mut self, commit: CommitId) -> Result<()> {
|
||||
self.files.set_commit(commit)?;
|
||||
pub fn open(&mut self, request: FileTreeOpen) -> Result<()> {
|
||||
self.files.set_commit(request.commit_id)?;
|
||||
self.open_request = Some(request);
|
||||
self.show()?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -67,6 +87,23 @@ impl RevisionFilesPopup {
|
|||
pub fn file_finder_update(&mut self, file: &Option<PathBuf>) {
|
||||
self.files.find_file(file);
|
||||
}
|
||||
|
||||
fn hide_stacked(&mut self, stack: bool) {
|
||||
self.hide();
|
||||
|
||||
if stack {
|
||||
if let Some(revision) = self.files.revision() {
|
||||
self.queue.push(InternalEvent::PopupStackPush(
|
||||
StackablePopupOpen::FileTree(FileTreeOpen {
|
||||
commit_id: revision,
|
||||
selection: self.files.selection(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.queue.push(InternalEvent::PopupStackPop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for RevisionFilesPopup {
|
||||
|
|
@ -114,11 +151,18 @@ impl Component for RevisionFilesPopup {
|
|||
if self.is_visible() {
|
||||
if let Event::Key(key) = &event {
|
||||
if *key == self.key_config.keys.exit_popup {
|
||||
self.hide();
|
||||
self.hide_stacked(false);
|
||||
}
|
||||
}
|
||||
|
||||
return self.files.event(event);
|
||||
let res = self.files.event(event)?;
|
||||
//Note: if this made the files hide we need to stack the popup
|
||||
if res == EventState::Consumed && !self.files.is_visible()
|
||||
{
|
||||
self.hide_stacked(true);
|
||||
}
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
Ok(EventState::NotConsumed)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ use super::{
|
|||
filetree::{FileTreeItem, FileTreeItemKind},
|
||||
statustree::{MoveSelection, StatusTree},
|
||||
},
|
||||
CommandBlocking, DrawableComponent,
|
||||
BlameFileOpen, CommandBlocking, DrawableComponent, FileRevOpen,
|
||||
};
|
||||
use crate::{
|
||||
components::{CommandInfo, Component, EventState},
|
||||
keys::SharedKeyConfig,
|
||||
queue::{InternalEvent, NeedsUpdate, Queue},
|
||||
queue::{InternalEvent, NeedsUpdate, Queue, StackablePopupOpen},
|
||||
strings::{self, order},
|
||||
ui,
|
||||
ui::style::SharedTheme,
|
||||
|
|
@ -22,6 +22,7 @@ use tui::{backend::Backend, layout::Rect, text::Span, Frame};
|
|||
//TODO: use new `filetreelist` crate
|
||||
|
||||
///
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct StatusTreeComponent {
|
||||
title: String,
|
||||
tree: StatusTree,
|
||||
|
|
@ -33,6 +34,7 @@ pub struct StatusTreeComponent {
|
|||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
scroll_top: Cell<usize>,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl StatusTreeComponent {
|
||||
|
|
@ -55,6 +57,7 @@ impl StatusTreeComponent {
|
|||
key_config,
|
||||
scroll_top: Cell::new(0),
|
||||
pending: true,
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -313,6 +316,10 @@ impl DrawableComponent for StatusTreeComponent {
|
|||
f: &mut Frame<B>,
|
||||
r: Rect,
|
||||
) -> Result<()> {
|
||||
if !self.is_visible() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.pending {
|
||||
let items = vec![Span::styled(
|
||||
Cow::from(strings::loading_text(&self.key_config)),
|
||||
|
|
@ -416,31 +423,35 @@ impl Component for StatusTreeComponent {
|
|||
if self.focused {
|
||||
if let Event::Key(e) = ev {
|
||||
return if e == self.key_config.keys.blame {
|
||||
match (&self.queue, self.selection_file()) {
|
||||
(Some(queue), Some(status_item)) => {
|
||||
//TODO: use correct revision here
|
||||
queue.push(InternalEvent::BlameFile(
|
||||
status_item.path,
|
||||
None,
|
||||
));
|
||||
|
||||
Ok(EventState::Consumed)
|
||||
}
|
||||
_ => Ok(EventState::NotConsumed),
|
||||
}
|
||||
} else if e == self.key_config.keys.file_history {
|
||||
match (&self.queue, self.selection_file()) {
|
||||
(Some(queue), Some(status_item)) => {
|
||||
queue.push(
|
||||
InternalEvent::OpenFileRevlog(
|
||||
status_item.path,
|
||||
if let Some(status_item) = self.selection_file() {
|
||||
self.hide();
|
||||
if let Some(queue) = &self.queue {
|
||||
queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::BlameFile(
|
||||
BlameFileOpen {
|
||||
file_path: status_item.path,
|
||||
commit_id: None,
|
||||
selection: None,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Ok(EventState::Consumed)
|
||||
));
|
||||
}
|
||||
_ => Ok(EventState::NotConsumed),
|
||||
}
|
||||
Ok(EventState::Consumed)
|
||||
} else if e == self.key_config.keys.file_history {
|
||||
if let Some(status_item) = self.selection_file() {
|
||||
self.hide();
|
||||
if let Some(queue) = &self.queue {
|
||||
queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::FileRevlog(
|
||||
FileRevOpen::new(
|
||||
status_item.path,
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(EventState::Consumed)
|
||||
} else if e == self.key_config.keys.move_down {
|
||||
Ok(self
|
||||
.move_selection(MoveSelection::Down)
|
||||
|
|
@ -481,6 +492,19 @@ impl Component for StatusTreeComponent {
|
|||
self.focused = focus;
|
||||
self.show_selection(focus);
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false;
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ mod components;
|
|||
mod input;
|
||||
mod keys;
|
||||
mod notify_mutex;
|
||||
mod popup_stack;
|
||||
mod profiler;
|
||||
mod queue;
|
||||
mod spinner;
|
||||
|
|
|
|||
16
src/popup_stack.rs
Normal file
16
src/popup_stack.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use crate::queue::StackablePopupOpen;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PopupStack {
|
||||
stack: Vec<StackablePopupOpen>,
|
||||
}
|
||||
|
||||
impl PopupStack {
|
||||
pub fn push(&mut self, popup: StackablePopupOpen) {
|
||||
self.stack.push(popup);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<StackablePopupOpen> {
|
||||
self.stack.pop()
|
||||
}
|
||||
}
|
||||
40
src/queue.rs
40
src/queue.rs
|
|
@ -1,6 +1,12 @@
|
|||
use crate::{components::AppOption, tabs::StashingOptions};
|
||||
use crate::{
|
||||
components::{
|
||||
AppOption, BlameFileOpen, FileRevOpen, FileTreeOpen,
|
||||
InspectCommitOpen,
|
||||
},
|
||||
tabs::StashingOptions,
|
||||
};
|
||||
use asyncgit::{
|
||||
sync::{diff::DiffLinePosition, CommitId, CommitTags, TreeFile},
|
||||
sync::{diff::DiffLinePosition, CommitId, TreeFile},
|
||||
PushType,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
|
|
@ -48,6 +54,20 @@ pub enum Action {
|
|||
AbortRevert,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StackablePopupOpen {
|
||||
///
|
||||
BlameFile(BlameFileOpen),
|
||||
///
|
||||
FileRevlog(FileRevOpen),
|
||||
///
|
||||
FileTree(FileTreeOpen),
|
||||
///
|
||||
InspectCommit(InspectCommitOpen),
|
||||
///
|
||||
CompareCommits(InspectCommitOpen),
|
||||
}
|
||||
|
||||
///
|
||||
pub enum InternalEvent {
|
||||
///
|
||||
|
|
@ -69,20 +89,12 @@ pub enum InternalEvent {
|
|||
///
|
||||
TabSwitchStatus,
|
||||
///
|
||||
InspectCommit(CommitId, Option<CommitTags>),
|
||||
///
|
||||
CompareCommits(CommitId, Option<CommitId>),
|
||||
///
|
||||
SelectCommitInRevlog(CommitId),
|
||||
///
|
||||
TagCommit(CommitId),
|
||||
///
|
||||
Tags,
|
||||
///
|
||||
BlameFile(String, Option<CommitId>),
|
||||
///
|
||||
OpenFileRevlog(String),
|
||||
///
|
||||
CreateBranch,
|
||||
///
|
||||
RenameBranch(String, String),
|
||||
|
|
@ -97,8 +109,6 @@ pub enum InternalEvent {
|
|||
///
|
||||
PushTags,
|
||||
///
|
||||
OpenFileTree(CommitId),
|
||||
///
|
||||
OptionSwitched(AppOption),
|
||||
///
|
||||
OpenFileFinder(Vec<TreeFile>),
|
||||
|
|
@ -106,6 +116,12 @@ pub enum InternalEvent {
|
|||
FileFinderChanged(Option<PathBuf>),
|
||||
///
|
||||
FetchRemotes,
|
||||
///
|
||||
OpenPopup(StackablePopupOpen),
|
||||
///
|
||||
PopupStackPop,
|
||||
///
|
||||
PopupStackPush(StackablePopupOpen),
|
||||
}
|
||||
|
||||
/// single threaded simple queue for components to communicate with each other
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ use crate::{
|
|||
components::{
|
||||
visibility_blocking, CommandBlocking, CommandInfo,
|
||||
CommitDetailsComponent, CommitList, Component,
|
||||
DrawableComponent, EventState,
|
||||
DrawableComponent, EventState, FileTreeOpen,
|
||||
InspectCommitOpen,
|
||||
},
|
||||
keys::SharedKeyConfig,
|
||||
queue::{InternalEvent, Queue},
|
||||
queue::{InternalEvent, Queue, StackablePopupOpen},
|
||||
strings, try_or_popup,
|
||||
ui::style::SharedTheme,
|
||||
};
|
||||
|
|
@ -199,6 +200,17 @@ impl Revlog {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inspect_commit(&self) {
|
||||
if let Some(commit_id) = self.selected_commit() {
|
||||
let tags = self.selected_commit_tags(&Some(commit_id));
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::InspectCommit(
|
||||
InspectCommitOpen::new_with_tags(commit_id, tags),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for Revlog {
|
||||
|
|
@ -260,20 +272,8 @@ impl Component for Revlog {
|
|||
} else if k == self.key_config.keys.focus_right
|
||||
&& self.commit_details.is_visible()
|
||||
{
|
||||
return self.selected_commit().map_or(
|
||||
Ok(EventState::NotConsumed),
|
||||
|id| {
|
||||
self.queue.push(
|
||||
InternalEvent::InspectCommit(
|
||||
id,
|
||||
self.selected_commit_tags(&Some(
|
||||
id,
|
||||
)),
|
||||
),
|
||||
);
|
||||
Ok(EventState::Consumed)
|
||||
},
|
||||
);
|
||||
self.inspect_commit();
|
||||
return Ok(EventState::Consumed);
|
||||
} else if k == self.key_config.keys.select_branch {
|
||||
self.queue.push(InternalEvent::SelectBranch);
|
||||
return Ok(EventState::Consumed);
|
||||
|
|
@ -291,7 +291,11 @@ impl Component for Revlog {
|
|||
Ok(EventState::NotConsumed),
|
||||
|id| {
|
||||
self.queue.push(
|
||||
InternalEvent::OpenFileTree(id),
|
||||
InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::FileTree(
|
||||
FileTreeOpen::new(id),
|
||||
),
|
||||
),
|
||||
);
|
||||
Ok(EventState::Consumed)
|
||||
},
|
||||
|
|
@ -304,22 +308,26 @@ impl Component for Revlog {
|
|||
{
|
||||
if self.list.marked_count() == 1 {
|
||||
// compare against head
|
||||
self.queue.push(
|
||||
InternalEvent::CompareCommits(
|
||||
self.list.marked()[0],
|
||||
None,
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::CompareCommits(
|
||||
InspectCommitOpen::new(
|
||||
self.list.marked()[0],
|
||||
),
|
||||
),
|
||||
);
|
||||
));
|
||||
return Ok(EventState::Consumed);
|
||||
} else if self.list.marked_count() == 2 {
|
||||
//compare two marked commits
|
||||
let marked = self.list.marked();
|
||||
self.queue.push(
|
||||
InternalEvent::CompareCommits(
|
||||
marked[0],
|
||||
Some(marked[1]),
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::CompareCommits(
|
||||
InspectCommitOpen {
|
||||
commit_id: marked[0],
|
||||
compare_id: Some(marked[1]),
|
||||
tags: None,
|
||||
},
|
||||
),
|
||||
);
|
||||
));
|
||||
return Ok(EventState::Consumed);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -269,6 +269,7 @@ impl Component for Stashing {
|
|||
self.options.stash_untracked =
|
||||
!config_untracked_files.include_none();
|
||||
|
||||
self.index.show()?;
|
||||
self.visible = true;
|
||||
self.update()?;
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ use crate::{
|
|||
components::{
|
||||
visibility_blocking, CommandBlocking, CommandInfo,
|
||||
CommitList, Component, DrawableComponent, EventState,
|
||||
InspectCommitOpen,
|
||||
},
|
||||
keys::SharedKeyConfig,
|
||||
queue::{Action, InternalEvent, Queue},
|
||||
queue::{Action, InternalEvent, Queue, StackablePopupOpen},
|
||||
strings,
|
||||
ui::style::SharedTheme,
|
||||
};
|
||||
|
|
@ -96,7 +97,11 @@ impl StashList {
|
|||
|
||||
fn inspect(&mut self) {
|
||||
if let Some(e) = self.list.selected_entry() {
|
||||
self.queue.push(InternalEvent::InspectCommit(e.id, None));
|
||||
self.queue.push(InternalEvent::OpenPopup(
|
||||
StackablePopupOpen::InspectCommit(
|
||||
InspectCommitOpen::new(e.id),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue