mirror of
https://github.com/extrawurst/gitui.git
synced 2024-11-25 18:44:53 +03:00
PoC list submodules (#1090)
This commit is contained in:
parent
bcb565788e
commit
ef3ece552d
@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
**submodules view**
|
||||||
|
|
||||||
|
![submodules](assets/submodules.png)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* submodules support ([#1087](https://github.com/extrawurst/gitui/issues/1087))
|
||||||
|
|
||||||
## [0.21.0] - 2021-08-17
|
## [0.21.0] - 2021-08-17
|
||||||
|
|
||||||
**popup stacking**
|
**popup stacking**
|
||||||
|
2
Makefile
2
Makefile
@ -2,7 +2,7 @@
|
|||||||
.PHONY: debug build-release release-linux-musl test clippy clippy-pedantic install install-debug
|
.PHONY: debug build-release release-linux-musl test clippy clippy-pedantic install install-debug
|
||||||
|
|
||||||
ARGS=-l
|
ARGS=-l
|
||||||
# ARGS=-l -d ~/code/git-bare-test.git
|
# ARGS=-l -d ~/code/extern/pbrt-v4
|
||||||
# ARGS=-l -d ~/code/git-bare-test.git -w ~/code/git-bare-test
|
# ARGS=-l -d ~/code/git-bare-test.git -w ~/code/git-bare-test
|
||||||
|
|
||||||
profile:
|
profile:
|
||||||
|
BIN
assets/submodules.png
Normal file
BIN
assets/submodules.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 152 KiB |
@ -10,6 +10,12 @@ use unicode_truncate::UnicodeTruncateStr;
|
|||||||
)]
|
)]
|
||||||
pub struct CommitId(Oid);
|
pub struct CommitId(Oid);
|
||||||
|
|
||||||
|
impl Default for CommitId {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Oid::zero())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CommitId {
|
impl CommitId {
|
||||||
/// create new `CommitId`
|
/// create new `CommitId`
|
||||||
pub const fn new(id: Oid) -> Self {
|
pub const fn new(id: Oid) -> Self {
|
||||||
|
@ -27,6 +27,7 @@ mod staging;
|
|||||||
mod stash;
|
mod stash;
|
||||||
mod state;
|
mod state;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
mod submodules;
|
||||||
mod tags;
|
mod tags;
|
||||||
mod tree;
|
mod tree;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
@ -80,6 +81,9 @@ pub use stash::{
|
|||||||
};
|
};
|
||||||
pub use state::{repo_state, RepoState};
|
pub use state::{repo_state, RepoState};
|
||||||
pub use status::is_workdir_clean;
|
pub use status::is_workdir_clean;
|
||||||
|
pub use submodules::{
|
||||||
|
get_submodules, update_submodule, SubmoduleInfo, SubmoduleStatus,
|
||||||
|
};
|
||||||
pub use tags::{
|
pub use tags::{
|
||||||
delete_tag, get_tags, get_tags_with_metadata, CommitTags, Tag,
|
delete_tag, get_tags, get_tags_with_metadata, CommitTags, Tag,
|
||||||
TagWithMetadata, Tags,
|
TagWithMetadata, Tags,
|
||||||
|
84
asyncgit/src/sync/submodules.rs
Normal file
84
asyncgit/src/sync/submodules.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use git2::SubmoduleUpdateOptions;
|
||||||
|
use scopetime::scope_time;
|
||||||
|
|
||||||
|
use super::{repo, CommitId, RepoPath};
|
||||||
|
use crate::{error::Result, Error};
|
||||||
|
|
||||||
|
pub use git2::SubmoduleStatus;
|
||||||
|
|
||||||
|
///
|
||||||
|
pub struct SubmoduleInfo {
|
||||||
|
///
|
||||||
|
pub path: PathBuf,
|
||||||
|
///
|
||||||
|
pub url: Option<String>,
|
||||||
|
///
|
||||||
|
pub id: Option<CommitId>,
|
||||||
|
///
|
||||||
|
pub head_id: Option<CommitId>,
|
||||||
|
///
|
||||||
|
pub status: SubmoduleStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubmoduleInfo {
|
||||||
|
///
|
||||||
|
pub fn get_repo_path(
|
||||||
|
&self,
|
||||||
|
repo_path: &RepoPath,
|
||||||
|
) -> Result<RepoPath> {
|
||||||
|
let repo = repo(repo_path)?;
|
||||||
|
let wd = repo.workdir().ok_or(Error::NoWorkDir)?;
|
||||||
|
|
||||||
|
Ok(RepoPath::Path(wd.join(self.path.clone())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn get_submodules(
|
||||||
|
repo_path: &RepoPath,
|
||||||
|
) -> Result<Vec<SubmoduleInfo>> {
|
||||||
|
scope_time!("get_submodules");
|
||||||
|
|
||||||
|
let (r, repo2) = (repo(repo_path)?, repo(repo_path)?);
|
||||||
|
|
||||||
|
let res = r
|
||||||
|
.submodules()?
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
let status = repo2
|
||||||
|
.submodule_status(
|
||||||
|
s.name().unwrap_or_default(),
|
||||||
|
git2::SubmoduleIgnore::None,
|
||||||
|
)
|
||||||
|
.unwrap_or(SubmoduleStatus::empty());
|
||||||
|
|
||||||
|
SubmoduleInfo {
|
||||||
|
path: s.path().to_path_buf(),
|
||||||
|
id: s.workdir_id().map(CommitId::from),
|
||||||
|
head_id: s.head_id().map(CommitId::from),
|
||||||
|
url: s.url().map(String::from),
|
||||||
|
status,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn update_submodule(
|
||||||
|
repo_path: &RepoPath,
|
||||||
|
path: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let repo = repo(repo_path)?;
|
||||||
|
|
||||||
|
let mut submodule = repo.find_submodule(path)?;
|
||||||
|
|
||||||
|
let mut options = SubmoduleUpdateOptions::new();
|
||||||
|
|
||||||
|
submodule.update(true, Some(&mut options))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
14
src/app.rs
14
src/app.rs
@ -11,7 +11,8 @@ use crate::{
|
|||||||
MsgComponent, OptionsPopupComponent, PullComponent,
|
MsgComponent, OptionsPopupComponent, PullComponent,
|
||||||
PushComponent, PushTagsComponent, RenameBranchComponent,
|
PushComponent, PushTagsComponent, RenameBranchComponent,
|
||||||
RevisionFilesPopup, SharedOptions, StashMsgComponent,
|
RevisionFilesPopup, SharedOptions, StashMsgComponent,
|
||||||
TagCommitComponent, TagListComponent,
|
SubmodulesListComponent, TagCommitComponent,
|
||||||
|
TagListComponent,
|
||||||
},
|
},
|
||||||
input::{Input, InputEvent, InputState},
|
input::{Input, InputEvent, InputState},
|
||||||
keys::{key_match, KeyConfig, SharedKeyConfig},
|
keys::{key_match, KeyConfig, SharedKeyConfig},
|
||||||
@ -70,6 +71,7 @@ pub struct App {
|
|||||||
rename_branch_popup: RenameBranchComponent,
|
rename_branch_popup: RenameBranchComponent,
|
||||||
select_branch_popup: BranchListComponent,
|
select_branch_popup: BranchListComponent,
|
||||||
options_popup: OptionsPopupComponent,
|
options_popup: OptionsPopupComponent,
|
||||||
|
submodule_popup: SubmodulesListComponent,
|
||||||
tags_popup: TagListComponent,
|
tags_popup: TagListComponent,
|
||||||
cmdbar: RefCell<CommandBar>,
|
cmdbar: RefCell<CommandBar>,
|
||||||
tab: usize,
|
tab: usize,
|
||||||
@ -231,6 +233,11 @@ impl App {
|
|||||||
key_config.clone(),
|
key_config.clone(),
|
||||||
options.clone(),
|
options.clone(),
|
||||||
),
|
),
|
||||||
|
submodule_popup: SubmodulesListComponent::new(
|
||||||
|
repo.clone(),
|
||||||
|
theme.clone(),
|
||||||
|
key_config.clone(),
|
||||||
|
),
|
||||||
find_file_popup: FileFindPopup::new(
|
find_file_popup: FileFindPopup::new(
|
||||||
&queue,
|
&queue,
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
@ -543,6 +550,7 @@ impl App {
|
|||||||
rename_branch_popup,
|
rename_branch_popup,
|
||||||
select_branch_popup,
|
select_branch_popup,
|
||||||
revision_files_popup,
|
revision_files_popup,
|
||||||
|
submodule_popup,
|
||||||
tags_popup,
|
tags_popup,
|
||||||
options_popup,
|
options_popup,
|
||||||
help,
|
help,
|
||||||
@ -567,6 +575,7 @@ impl App {
|
|||||||
external_editor_popup,
|
external_editor_popup,
|
||||||
tag_commit_popup,
|
tag_commit_popup,
|
||||||
select_branch_popup,
|
select_branch_popup,
|
||||||
|
submodule_popup,
|
||||||
tags_popup,
|
tags_popup,
|
||||||
create_branch_popup,
|
create_branch_popup,
|
||||||
rename_branch_popup,
|
rename_branch_popup,
|
||||||
@ -775,6 +784,9 @@ impl App {
|
|||||||
InternalEvent::SelectBranch => {
|
InternalEvent::SelectBranch => {
|
||||||
self.select_branch_popup.open()?;
|
self.select_branch_popup.open()?;
|
||||||
}
|
}
|
||||||
|
InternalEvent::ViewSubmodules => {
|
||||||
|
self.submodule_popup.open()?;
|
||||||
|
}
|
||||||
InternalEvent::Tags => {
|
InternalEvent::Tags => {
|
||||||
self.tags_popup.open()?;
|
self.tags_popup.open()?;
|
||||||
}
|
}
|
||||||
|
@ -541,9 +541,9 @@ impl BranchListComponent {
|
|||||||
const HEAD_SYMBOL: char = '*';
|
const HEAD_SYMBOL: char = '*';
|
||||||
const EMPTY_SYMBOL: char = ' ';
|
const EMPTY_SYMBOL: char = ' ';
|
||||||
const THREE_DOTS: &str = "...";
|
const THREE_DOTS: &str = "...";
|
||||||
|
const THREE_DOTS_LENGTH: usize = THREE_DOTS.len(); // "..."
|
||||||
const COMMIT_HASH_LENGTH: usize = 8;
|
const COMMIT_HASH_LENGTH: usize = 8;
|
||||||
const IS_HEAD_STAR_LENGTH: usize = 3; // "* "
|
const IS_HEAD_STAR_LENGTH: usize = 3; // "* "
|
||||||
const THREE_DOTS_LENGTH: usize = THREE_DOTS.len(); // "..."
|
|
||||||
|
|
||||||
let branch_name_length: usize =
|
let branch_name_length: usize =
|
||||||
width_available as usize * 40 / 100;
|
width_available as usize * 40 / 100;
|
||||||
|
@ -26,6 +26,7 @@ mod revision_files;
|
|||||||
mod revision_files_popup;
|
mod revision_files_popup;
|
||||||
mod stashmsg;
|
mod stashmsg;
|
||||||
mod status_tree;
|
mod status_tree;
|
||||||
|
mod submodules;
|
||||||
mod syntax_text;
|
mod syntax_text;
|
||||||
mod tag_commit;
|
mod tag_commit;
|
||||||
mod taglist;
|
mod taglist;
|
||||||
@ -61,6 +62,7 @@ pub use reset::ConfirmComponent;
|
|||||||
pub use revision_files::RevisionFilesComponent;
|
pub use revision_files::RevisionFilesComponent;
|
||||||
pub use revision_files_popup::{FileTreeOpen, RevisionFilesPopup};
|
pub use revision_files_popup::{FileTreeOpen, RevisionFilesPopup};
|
||||||
pub use stashmsg::StashMsgComponent;
|
pub use stashmsg::StashMsgComponent;
|
||||||
|
pub use submodules::SubmodulesListComponent;
|
||||||
pub use syntax_text::SyntaxTextComponent;
|
pub use syntax_text::SyntaxTextComponent;
|
||||||
pub use tag_commit::TagCommitComponent;
|
pub use tag_commit::TagCommitComponent;
|
||||||
pub use taglist::TagListComponent;
|
pub use taglist::TagListComponent;
|
||||||
|
402
src/components/submodules.rs
Normal file
402
src/components/submodules.rs
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
use super::{
|
||||||
|
utils::scroll_vertical::VerticalScroll, visibility_blocking,
|
||||||
|
CommandBlocking, CommandInfo, Component, DrawableComponent,
|
||||||
|
EventState, ScrollType,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
keys::{key_match, SharedKeyConfig},
|
||||||
|
strings,
|
||||||
|
ui::{self, Size},
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use asyncgit::sync::{get_submodules, RepoPathRef, SubmoduleInfo};
|
||||||
|
use crossterm::event::Event;
|
||||||
|
use std::{cell::Cell, convert::TryInto};
|
||||||
|
use tui::{
|
||||||
|
backend::Backend,
|
||||||
|
layout::{
|
||||||
|
Alignment, Constraint, Direction, Layout, Margin, Rect,
|
||||||
|
},
|
||||||
|
text::{Span, Spans, Text},
|
||||||
|
widgets::{Block, BorderType, Borders, Clear, Paragraph},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
use ui::style::SharedTheme;
|
||||||
|
use unicode_truncate::UnicodeTruncateStr;
|
||||||
|
|
||||||
|
///
|
||||||
|
pub struct SubmodulesListComponent {
|
||||||
|
repo: RepoPathRef,
|
||||||
|
submodules: Vec<SubmoduleInfo>,
|
||||||
|
visible: bool,
|
||||||
|
current_height: Cell<u16>,
|
||||||
|
selection: u16,
|
||||||
|
scroll: VerticalScroll,
|
||||||
|
theme: SharedTheme,
|
||||||
|
key_config: SharedKeyConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawableComponent for SubmodulesListComponent {
|
||||||
|
fn draw<B: Backend>(
|
||||||
|
&self,
|
||||||
|
f: &mut Frame<B>,
|
||||||
|
rect: Rect,
|
||||||
|
) -> Result<()> {
|
||||||
|
if self.is_visible() {
|
||||||
|
const PERCENT_SIZE: Size = Size::new(80, 80);
|
||||||
|
const MIN_SIZE: Size = Size::new(60, 30);
|
||||||
|
|
||||||
|
let area = ui::centered_rect(
|
||||||
|
PERCENT_SIZE.width,
|
||||||
|
PERCENT_SIZE.height,
|
||||||
|
rect,
|
||||||
|
);
|
||||||
|
let area = ui::rect_inside(MIN_SIZE, rect.into(), area);
|
||||||
|
let area = area.intersection(rect);
|
||||||
|
|
||||||
|
f.render_widget(Clear, area);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Block::default()
|
||||||
|
.title(strings::POPUP_TITLE_SUBMODULES)
|
||||||
|
.border_type(BorderType::Thick)
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
area,
|
||||||
|
);
|
||||||
|
|
||||||
|
let area = area.inner(&Margin {
|
||||||
|
vertical: 1,
|
||||||
|
horizontal: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(
|
||||||
|
[Constraint::Min(40), Constraint::Length(40)]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
self.draw_list(f, chunks[0])?;
|
||||||
|
self.draw_info(f, chunks[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for SubmodulesListComponent {
|
||||||
|
fn commands(
|
||||||
|
&self,
|
||||||
|
out: &mut Vec<CommandInfo>,
|
||||||
|
force_all: bool,
|
||||||
|
) -> CommandBlocking {
|
||||||
|
if self.visible || force_all {
|
||||||
|
if !force_all {
|
||||||
|
out.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push(CommandInfo::new(
|
||||||
|
strings::commands::scroll(&self.key_config),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
|
||||||
|
out.push(CommandInfo::new(
|
||||||
|
strings::commands::close_popup(&self.key_config),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
visibility_blocking(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ev: &Event) -> Result<EventState> {
|
||||||
|
if !self.visible {
|
||||||
|
return Ok(EventState::NotConsumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Event::Key(e) = ev {
|
||||||
|
if key_match(e, self.key_config.keys.exit_popup) {
|
||||||
|
self.hide();
|
||||||
|
} else if key_match(e, self.key_config.keys.move_down) {
|
||||||
|
return self
|
||||||
|
.move_selection(ScrollType::Up)
|
||||||
|
.map(Into::into);
|
||||||
|
} else if key_match(e, self.key_config.keys.move_up) {
|
||||||
|
return self
|
||||||
|
.move_selection(ScrollType::Down)
|
||||||
|
.map(Into::into);
|
||||||
|
} else if key_match(e, self.key_config.keys.page_down) {
|
||||||
|
return self
|
||||||
|
.move_selection(ScrollType::PageDown)
|
||||||
|
.map(Into::into);
|
||||||
|
} else if key_match(e, self.key_config.keys.page_up) {
|
||||||
|
return self
|
||||||
|
.move_selection(ScrollType::PageUp)
|
||||||
|
.map(Into::into);
|
||||||
|
} else if key_match(e, self.key_config.keys.home) {
|
||||||
|
return self
|
||||||
|
.move_selection(ScrollType::Home)
|
||||||
|
.map(Into::into);
|
||||||
|
} else if key_match(e, self.key_config.keys.end) {
|
||||||
|
return self
|
||||||
|
.move_selection(ScrollType::End)
|
||||||
|
.map(Into::into);
|
||||||
|
} else if key_match(
|
||||||
|
e,
|
||||||
|
self.key_config.keys.cmd_bar_toggle,
|
||||||
|
) {
|
||||||
|
//do not consume if its the more key
|
||||||
|
return Ok(EventState::NotConsumed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EventState::Consumed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_visible(&self) -> bool {
|
||||||
|
self.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide(&mut self) {
|
||||||
|
self.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self) -> Result<()> {
|
||||||
|
self.visible = true;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubmodulesListComponent {
|
||||||
|
pub fn new(
|
||||||
|
repo: RepoPathRef,
|
||||||
|
theme: SharedTheme,
|
||||||
|
key_config: SharedKeyConfig,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
submodules: Vec::new(),
|
||||||
|
scroll: VerticalScroll::new(),
|
||||||
|
selection: 0,
|
||||||
|
visible: false,
|
||||||
|
theme,
|
||||||
|
key_config,
|
||||||
|
current_height: Cell::new(0),
|
||||||
|
repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn open(&mut self) -> Result<()> {
|
||||||
|
self.show()?;
|
||||||
|
self.update_submodules()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn update_submodules(&mut self) -> Result<()> {
|
||||||
|
if self.is_visible() {
|
||||||
|
self.submodules = get_submodules(&self.repo.borrow())?;
|
||||||
|
|
||||||
|
self.set_selection(self.selection)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_entry(&self) -> Option<&SubmoduleInfo> {
|
||||||
|
self.submodules.get(self.selection as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: dedup this almost identical with BranchListComponent
|
||||||
|
fn move_selection(&mut self, scroll: ScrollType) -> Result<bool> {
|
||||||
|
let new_selection = match scroll {
|
||||||
|
ScrollType::Up => self.selection.saturating_add(1),
|
||||||
|
ScrollType::Down => self.selection.saturating_sub(1),
|
||||||
|
ScrollType::PageDown => self
|
||||||
|
.selection
|
||||||
|
.saturating_add(self.current_height.get()),
|
||||||
|
ScrollType::PageUp => self
|
||||||
|
.selection
|
||||||
|
.saturating_sub(self.current_height.get()),
|
||||||
|
ScrollType::Home => 0,
|
||||||
|
ScrollType::End => {
|
||||||
|
let count: u16 = self.submodules.len().try_into()?;
|
||||||
|
count.saturating_sub(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_selection(new_selection)?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selection(&mut self, selection: u16) -> Result<()> {
|
||||||
|
let num_branches: u16 = self.submodules.len().try_into()?;
|
||||||
|
let num_branches = num_branches.saturating_sub(1);
|
||||||
|
|
||||||
|
let selection = if selection > num_branches {
|
||||||
|
num_branches
|
||||||
|
} else {
|
||||||
|
selection
|
||||||
|
};
|
||||||
|
|
||||||
|
self.selection = selection;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_text(
|
||||||
|
&self,
|
||||||
|
theme: &SharedTheme,
|
||||||
|
width_available: u16,
|
||||||
|
height: usize,
|
||||||
|
) -> Text {
|
||||||
|
const THREE_DOTS: &str = "...";
|
||||||
|
const THREE_DOTS_LENGTH: usize = THREE_DOTS.len(); // "..."
|
||||||
|
const COMMIT_HASH_LENGTH: usize = 8;
|
||||||
|
|
||||||
|
let mut txt = Vec::with_capacity(3);
|
||||||
|
|
||||||
|
let name_length: usize = (width_available as usize)
|
||||||
|
.saturating_sub(COMMIT_HASH_LENGTH)
|
||||||
|
.saturating_sub(THREE_DOTS_LENGTH);
|
||||||
|
|
||||||
|
for (i, submodule) in self
|
||||||
|
.submodules
|
||||||
|
.iter()
|
||||||
|
.skip(self.scroll.get_top())
|
||||||
|
.take(height)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let mut module_path = submodule
|
||||||
|
.path
|
||||||
|
.as_os_str()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if module_path.len() > name_length {
|
||||||
|
module_path.unicode_truncate(
|
||||||
|
name_length.saturating_sub(THREE_DOTS_LENGTH),
|
||||||
|
);
|
||||||
|
module_path += THREE_DOTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selected = (self.selection as usize
|
||||||
|
- self.scroll.get_top())
|
||||||
|
== i;
|
||||||
|
|
||||||
|
let span_hash = Span::styled(
|
||||||
|
format!(
|
||||||
|
"{} ",
|
||||||
|
submodule
|
||||||
|
.head_id
|
||||||
|
.unwrap_or_default()
|
||||||
|
.get_short_string()
|
||||||
|
),
|
||||||
|
theme.commit_hash(selected),
|
||||||
|
);
|
||||||
|
|
||||||
|
let span_name = Span::styled(
|
||||||
|
format!("{:w$} ", module_path, w = name_length),
|
||||||
|
theme.text(true, selected),
|
||||||
|
);
|
||||||
|
|
||||||
|
txt.push(Spans::from(vec![span_name, span_hash]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Text::from(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_info_text(&self, theme: &SharedTheme) -> Text {
|
||||||
|
self.selected_entry().map_or_else(
|
||||||
|
Text::default,
|
||||||
|
|submodule| {
|
||||||
|
let span_title_path =
|
||||||
|
Span::styled("Path:", theme.text(false, false));
|
||||||
|
let span_path = Span::styled(
|
||||||
|
submodule.path.to_string_lossy(),
|
||||||
|
theme.text(true, false),
|
||||||
|
);
|
||||||
|
|
||||||
|
let span_title_commit =
|
||||||
|
Span::styled("Commit:", theme.text(false, false));
|
||||||
|
let span_commit = Span::styled(
|
||||||
|
submodule.id.unwrap_or_default().to_string(),
|
||||||
|
theme.commit_hash(false),
|
||||||
|
);
|
||||||
|
|
||||||
|
let span_title_url =
|
||||||
|
Span::styled("Url:", theme.text(false, false));
|
||||||
|
let span_url = Span::styled(
|
||||||
|
submodule.url.clone().unwrap_or_default(),
|
||||||
|
theme.text(true, false),
|
||||||
|
);
|
||||||
|
|
||||||
|
let span_title_status =
|
||||||
|
Span::styled("Status:", theme.text(false, false));
|
||||||
|
let span_status = Span::styled(
|
||||||
|
format!("{:?}", submodule.status),
|
||||||
|
theme.text(true, false),
|
||||||
|
);
|
||||||
|
|
||||||
|
Text::from(vec![
|
||||||
|
Spans::from(vec![span_title_path]),
|
||||||
|
Spans::from(vec![span_path]),
|
||||||
|
Spans::from(vec![]),
|
||||||
|
Spans::from(vec![span_title_commit]),
|
||||||
|
Spans::from(vec![span_commit]),
|
||||||
|
Spans::from(vec![]),
|
||||||
|
Spans::from(vec![span_title_url]),
|
||||||
|
Spans::from(vec![span_url]),
|
||||||
|
Spans::from(vec![]),
|
||||||
|
Spans::from(vec![span_title_status]),
|
||||||
|
Spans::from(vec![span_status]),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_list<B: Backend>(
|
||||||
|
&self,
|
||||||
|
f: &mut Frame<B>,
|
||||||
|
r: Rect,
|
||||||
|
) -> Result<()> {
|
||||||
|
let height_in_lines = r.height as usize;
|
||||||
|
self.current_height.set(height_in_lines.try_into()?);
|
||||||
|
|
||||||
|
self.scroll.update(
|
||||||
|
self.selection as usize,
|
||||||
|
self.submodules.len(),
|
||||||
|
height_in_lines,
|
||||||
|
);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(self.get_text(
|
||||||
|
&self.theme,
|
||||||
|
r.width,
|
||||||
|
height_in_lines,
|
||||||
|
))
|
||||||
|
.alignment(Alignment::Left),
|
||||||
|
r,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut r = r;
|
||||||
|
r.height += 2;
|
||||||
|
r.y = r.y.saturating_sub(1);
|
||||||
|
|
||||||
|
self.scroll.draw(f, r, &self.theme);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_info<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(self.get_info_text(&self.theme))
|
||||||
|
.alignment(Alignment::Left),
|
||||||
|
r,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -107,6 +107,7 @@ pub struct KeysList {
|
|||||||
pub undo_commit: GituiKeyEvent,
|
pub undo_commit: GituiKeyEvent,
|
||||||
pub stage_unstage_item: GituiKeyEvent,
|
pub stage_unstage_item: GituiKeyEvent,
|
||||||
pub tag_annotate: GituiKeyEvent,
|
pub tag_annotate: GituiKeyEvent,
|
||||||
|
pub view_submodules: GituiKeyEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
@ -185,6 +186,8 @@ impl Default for KeysList {
|
|||||||
file_find: GituiKeyEvent::new(KeyCode::Char('f'), KeyModifiers::empty()),
|
file_find: GituiKeyEvent::new(KeyCode::Char('f'), KeyModifiers::empty()),
|
||||||
stage_unstage_item: GituiKeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
|
stage_unstage_item: GituiKeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
|
||||||
tag_annotate: GituiKeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL),
|
tag_annotate: GituiKeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL),
|
||||||
|
view_submodules: GituiKeyEvent::new(KeyCode::Char('S'), KeyModifiers::SHIFT),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ pub struct KeysListFile {
|
|||||||
pub undo_commit: Option<GituiKeyEvent>,
|
pub undo_commit: Option<GituiKeyEvent>,
|
||||||
pub stage_unstage_item: Option<GituiKeyEvent>,
|
pub stage_unstage_item: Option<GituiKeyEvent>,
|
||||||
pub tag_annotate: Option<GituiKeyEvent>,
|
pub tag_annotate: Option<GituiKeyEvent>,
|
||||||
|
pub view_submodules: Option<GituiKeyEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeysListFile {
|
impl KeysListFile {
|
||||||
@ -166,6 +167,7 @@ impl KeysListFile {
|
|||||||
undo_commit: self.undo_commit.unwrap_or(default.undo_commit),
|
undo_commit: self.undo_commit.unwrap_or(default.undo_commit),
|
||||||
stage_unstage_item: self.stage_unstage_item.unwrap_or(default.stage_unstage_item),
|
stage_unstage_item: self.stage_unstage_item.unwrap_or(default.stage_unstage_item),
|
||||||
tag_annotate: self.tag_annotate.unwrap_or(default.tag_annotate),
|
tag_annotate: self.tag_annotate.unwrap_or(default.tag_annotate),
|
||||||
|
view_submodules: self.view_submodules.unwrap_or(default.view_submodules),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,8 @@ pub enum InternalEvent {
|
|||||||
PopupStackPop,
|
PopupStackPop,
|
||||||
///
|
///
|
||||||
PopupStackPush(StackablePopupOpen),
|
PopupStackPush(StackablePopupOpen),
|
||||||
|
///
|
||||||
|
ViewSubmodules,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// single threaded simple queue for components to communicate with each other
|
/// single threaded simple queue for components to communicate with each other
|
||||||
|
@ -23,6 +23,8 @@ pub static PUSH_TAGS_STATES_FETCHING: &str = "fetching";
|
|||||||
pub static PUSH_TAGS_STATES_PUSHING: &str = "pushing";
|
pub static PUSH_TAGS_STATES_PUSHING: &str = "pushing";
|
||||||
pub static PUSH_TAGS_STATES_DONE: &str = "done";
|
pub static PUSH_TAGS_STATES_DONE: &str = "done";
|
||||||
|
|
||||||
|
pub static POPUP_TITLE_SUBMODULES: &str = "Submodules";
|
||||||
|
|
||||||
pub mod symbol {
|
pub mod symbol {
|
||||||
pub const WHITESPACE: &str = "\u{00B7}"; //·
|
pub const WHITESPACE: &str = "\u{00B7}"; //·
|
||||||
pub const CHECKMARK: &str = "\u{2713}"; //✓
|
pub const CHECKMARK: &str = "\u{2713}"; //✓
|
||||||
@ -700,6 +702,19 @@ pub mod commands {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn view_submodules(
|
||||||
|
key_config: &SharedKeyConfig,
|
||||||
|
) -> CommandText {
|
||||||
|
CommandText::new(
|
||||||
|
format!(
|
||||||
|
"Submodules [{}]",
|
||||||
|
key_config.get_hint(key_config.keys.view_submodules),
|
||||||
|
),
|
||||||
|
"open submodule view",
|
||||||
|
CMD_GROUP_GENERAL,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn continue_rebase(
|
pub fn continue_rebase(
|
||||||
key_config: &SharedKeyConfig,
|
key_config: &SharedKeyConfig,
|
||||||
) -> CommandText {
|
) -> CommandText {
|
||||||
|
@ -781,6 +781,12 @@ impl Component for Status {
|
|||||||
true,
|
true,
|
||||||
self.pending_revert() || force_all,
|
self.pending_revert() || force_all,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
out.push(CommandInfo::new(
|
||||||
|
strings::commands::view_submodules(&self.key_config),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -936,6 +942,12 @@ impl Component for Status {
|
|||||||
NeedsUpdate::ALL,
|
NeedsUpdate::ALL,
|
||||||
));
|
));
|
||||||
Ok(EventState::Consumed)
|
Ok(EventState::Consumed)
|
||||||
|
} else if key_match(
|
||||||
|
k,
|
||||||
|
self.key_config.keys.view_submodules,
|
||||||
|
) {
|
||||||
|
self.queue.push(InternalEvent::ViewSubmodules);
|
||||||
|
Ok(EventState::Consumed)
|
||||||
} else {
|
} else {
|
||||||
Ok(EventState::NotConsumed)
|
Ok(EventState::NotConsumed)
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user