From 986d34a5acd520fbec91386675bec8013affc6bd Mon Sep 17 00:00:00 2001 From: extrawurst <776816+extrawurst@users.noreply.github.com> Date: Wed, 31 Aug 2022 10:51:08 +0200 Subject: [PATCH] support opening submodule (#1298) --- Cargo.lock | 20 +++++ README.md | 1 + asyncgit/Cargo.toml | 1 + asyncgit/src/error.rs | 9 +- asyncgit/src/sync/mod.rs | 17 +++- asyncgit/src/sync/repository.rs | 2 +- asyncgit/src/sync/submodules.rs | 155 ++++++++++++++++++++++++++++---- src/app.rs | 37 ++++++-- src/components/submodules.rs | 110 +++++++++++++++++++++-- src/input.rs | 1 + src/keys/key_config.rs | 2 +- src/keys/key_list.rs | 4 +- src/keys/key_list_file.rs | 1 + src/keys/symbols.rs | 2 +- src/main.rs | 46 ++++++++-- src/queue.rs | 2 + src/strings.rs | 27 ++++++ src/ui/style.rs | 6 +- 18 files changed, 392 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12d9652c..213f8cfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,7 @@ version = "0.21.0" dependencies = [ "crossbeam-channel", "easy-cast", + "env_logger", "git2", "invalidstring", "log", @@ -422,6 +423,19 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fancy-regex" version = "0.7.1" @@ -692,6 +706,12 @@ dependencies = [ "libc", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "iana-time-zone" version = "0.1.46" diff --git a/README.md b/README.md index f0a7a032..93740ef3 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ - Browse commit log, diff committed changes - Scalable terminal UI layout - Async git API for fluid control +- Submodule support ## 2. Motivation [Top ▲](#table-of-contents) diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml index 95ec983b..b2ebd66f 100644 --- a/asyncgit/Cargo.toml +++ b/asyncgit/Cargo.toml @@ -28,6 +28,7 @@ unicode-truncate = "0.2.0" url = "2.2" [dev-dependencies] +env_logger = "0.9" invalidstring = { path = "../invalidstring", version = "0.1" } pretty_assertions = "1.3" serial_test = "0.9" diff --git a/asyncgit/src/error.rs b/asyncgit/src/error.rs index e733284d..5cbed38b 100644 --- a/asyncgit/src/error.rs +++ b/asyncgit/src/error.rs @@ -1,6 +1,9 @@ #![allow(renamed_and_removed_lints, clippy::unknown_clippy_lints)] -use std::{num::TryFromIntError, string::FromUtf8Error}; +use std::{ + num::TryFromIntError, path::StripPrefixError, + string::FromUtf8Error, +}; use thiserror::Error; /// @@ -50,6 +53,10 @@ pub enum Error { #[error("git error:{0}")] Git(#[from] git2::Error), + /// + #[error("strip prefix error: {0}")] + StripPrefix(#[from] StripPrefixError), + /// #[error("utf8 error:{0}")] Utf8Conversion(#[from] FromUtf8Error), diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 53778a8e..7a72b69f 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -82,7 +82,8 @@ pub use stash::{ pub use state::{repo_state, RepoState}; pub use status::is_workdir_clean; pub use submodules::{ - get_submodules, update_submodule, SubmoduleInfo, SubmoduleStatus, + get_submodules, submodule_parent_info, update_submodule, + SubmoduleInfo, SubmoduleParentInfo, SubmoduleStatus, }; pub use tags::{ delete_tag, get_tags, get_tags_with_metadata, CommitTags, Tag, @@ -209,6 +210,8 @@ mod tests { /// pub fn repo_init_empty() -> Result<(TempDir, Repository)> { + init_log(); + sandbox_config_files(); let td = TempDir::new()?; @@ -223,6 +226,8 @@ mod tests { /// pub fn repo_init() -> Result<(TempDir, Repository)> { + init_log(); + sandbox_config_files(); let td = TempDir::new()?; @@ -266,8 +271,18 @@ mod tests { Ok((td, repo)) } + // init log + fn init_log() { + let _ = env_logger::builder() + .is_test(true) + .filter_level(log::LevelFilter::Trace) + .try_init(); + } + /// Same as repo_init, but the repo is a bare repo (--bare) pub fn repo_init_bare() -> Result<(TempDir, Repository)> { + init_log(); + let tmp_repo_dir = TempDir::new()?; let bare_repo = Repository::init_bare(tmp_repo_dir.path())?; Ok((tmp_repo_dir, bare_repo)) diff --git a/asyncgit/src/sync/repository.rs b/asyncgit/src/sync/repository.rs index 1fad1206..24620d4c 100644 --- a/asyncgit/src/sync/repository.rs +++ b/asyncgit/src/sync/repository.rs @@ -11,7 +11,7 @@ use crate::error::Result; pub type RepoPathRef = RefCell; /// -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum RepoPath { /// Path(PathBuf), diff --git a/asyncgit/src/sync/submodules.rs b/asyncgit/src/sync/submodules.rs index f81f9573..b32a67d8 100644 --- a/asyncgit/src/sync/submodules.rs +++ b/asyncgit/src/sync/submodules.rs @@ -1,15 +1,24 @@ -use std::path::PathBuf; +//TODO: +// #![allow(unused_variables, dead_code)] -use git2::SubmoduleUpdateOptions; +use std::path::{Path, PathBuf}; + +use git2::{ + Repository, RepositoryOpenFlags, Submodule, + SubmoduleUpdateOptions, +}; use scopetime::scope_time; use super::{repo, CommitId, RepoPath}; -use crate::{error::Result, Error}; +use crate::{error::Result, sync::utils::work_dir, Error}; pub use git2::SubmoduleStatus; /// +#[derive(Debug)] pub struct SubmoduleInfo { + /// + pub name: String, /// pub path: PathBuf, /// @@ -22,6 +31,17 @@ pub struct SubmoduleInfo { pub status: SubmoduleStatus, } +/// +#[derive(Debug)] +pub struct SubmoduleParentInfo { + /// where to find parent repo + pub parent_gitpath: PathBuf, + /// where to find submodule git path + pub submodule_gitpath: PathBuf, + /// `submodule_info` from perspective of parent repo + pub submodule_info: SubmoduleInfo, +} + impl SubmoduleInfo { /// pub fn get_repo_path( @@ -35,6 +55,24 @@ impl SubmoduleInfo { } } +fn submodule_to_info(s: &Submodule, r: &Repository) -> SubmoduleInfo { + let status = r + .submodule_status( + s.name().unwrap_or_default(), + git2::SubmoduleIgnore::None, + ) + .unwrap_or(SubmoduleStatus::empty()); + + SubmoduleInfo { + name: s.name().unwrap_or_default().into(), + 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, + } +} + /// pub fn get_submodules( repo_path: &RepoPath, @@ -46,22 +84,7 @@ pub fn get_submodules( 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, - } - }) + .map(|s| submodule_to_info(s, &repo2)) .collect(); Ok(res) @@ -82,3 +105,97 @@ pub fn update_submodule( Ok(()) } + +/// query whether `repo_path` points to a repo that is part of a parent git which contains it as a submodule +pub fn submodule_parent_info( + repo_path: &RepoPath, +) -> Result> { + scope_time!("submodule_parent_info"); + + let repo = repo(repo_path)?; + let repo_wd = work_dir(&repo)?.to_path_buf(); + + log::trace!("[sub] repo_wd: {:?}", repo_wd); + log::trace!("[sub] repo_path: {:?}", repo.path()); + + if let Some(parent_path) = repo_wd.parent() { + log::trace!("[sub] parent_path: {:?}", parent_path); + + if let Ok(parent) = Repository::open_ext( + parent_path, + RepositoryOpenFlags::empty(), + Vec::<&Path>::new(), + ) { + let parent_wd = work_dir(&parent)?.to_path_buf(); + log::trace!("[sub] parent_wd: {:?}", parent_wd); + + let submodule_name = repo_wd + .strip_prefix(parent_wd)? + .to_string_lossy() + .to_string(); + + log::trace!("[sub] submodule_name: {:?}", submodule_name); + + if let Ok(submodule) = + parent.find_submodule(&submodule_name) + { + return Ok(Some(SubmoduleParentInfo { + parent_gitpath: parent.path().to_path_buf(), + submodule_gitpath: repo.path().to_path_buf(), + submodule_info: submodule_to_info( + &submodule, &parent, + ), + })); + } + } + } + + Ok(None) +} + +#[cfg(test)] +mod tests { + use super::get_submodules; + use crate::sync::{ + submodules::submodule_parent_info, tests::repo_init, RepoPath, + }; + use git2::Repository; + use pretty_assertions::assert_eq; + use std::path::Path; + + #[test] + fn test_smoke() { + let (dir, _r) = repo_init().unwrap(); + + { + let r = Repository::open(dir.path()).unwrap(); + let mut s = r + .submodule( + //TODO: use local git + "https://github.com/extrawurst/brewdump.git", + Path::new("foo/bar"), + false, + ) + .unwrap(); + + let _sub_r = s.clone(None).unwrap(); + s.add_finalize().unwrap(); + } + + let repo_p = RepoPath::Path(dir.into_path()); + let subs = get_submodules(&repo_p).unwrap(); + + assert_eq!(subs.len(), 1); + assert_eq!(&subs[0].name, "foo/bar"); + + let info = submodule_parent_info( + &subs[0].get_repo_path(&repo_p).unwrap(), + ) + .unwrap() + .unwrap(); + + dbg!(&info); + + assert_eq!(&info.submodule_info.name, "foo/bar"); + } +} diff --git a/src/app.rs b/src/app.rs index c24b04b0..e3cc7dc2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,7 +28,7 @@ use crate::{ }; use anyhow::{bail, Result}; use asyncgit::{ - sync::{self, RepoPathRef}, + sync::{self, utils::repo_work_dir, RepoPath, RepoPathRef}, AsyncGitNotification, PushType, }; use crossbeam_channel::Sender; @@ -46,10 +46,17 @@ use tui::{ Frame, }; +#[derive(Clone)] +pub enum QuitState { + None, + Close, + OpenSubmodule(RepoPath), +} + /// the main app type pub struct App { repo: RepoPathRef, - do_quit: bool, + do_quit: QuitState, help: HelpComponent, msg: MsgComponent, reset: ConfirmComponent, @@ -103,6 +110,8 @@ impl App { theme: Theme, key_config: KeyConfig, ) -> Self { + log::trace!("open repo at: {:?}", repo); + let queue = Queue::new(); let theme = Rc::new(theme); let key_config = Rc::new(key_config); @@ -235,6 +244,7 @@ impl App { ), submodule_popup: SubmodulesListComponent::new( repo.clone(), + &queue, theme.clone(), key_config.clone(), ), @@ -243,7 +253,7 @@ impl App { theme.clone(), key_config.clone(), ), - do_quit: false, + do_quit: QuitState::None, cmdbar: RefCell::new(CommandBar::new( theme.clone(), key_config.clone(), @@ -493,7 +503,13 @@ impl App { /// pub fn is_quit(&self) -> bool { - self.do_quit || self.input.is_aborted() + !matches!(self.do_quit, QuitState::None) + || self.input.is_aborted() + } + + /// + pub fn quit_state(&self) -> QuitState { + self.do_quit.clone() } /// @@ -597,7 +613,7 @@ impl App { } if let Event::Key(e) = ev { if key_match(e, self.key_config.keys.quit) { - self.do_quit = true; + self.do_quit = QuitState::Close; return true; } } @@ -607,7 +623,7 @@ impl App { fn check_hard_exit(&mut self, ev: &Event) -> bool { if let Event::Key(e) = ev { if key_match(e, self.key_config.keys.exit) { - self.do_quit = true; + self.do_quit = QuitState::Close; return true; } } @@ -878,6 +894,15 @@ impl App { flags .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); } + InternalEvent::OpenRepo { path } => { + let submodule_repo_path = RepoPath::Path( + Path::new(&repo_work_dir(&self.repo.borrow())?) + .join(path), + ); + //TODO: validate this is a valid repo first, so we can show proper error otherwise + self.do_quit = + QuitState::OpenSubmodule(submodule_repo_path); + } }; Ok(flags) diff --git a/src/components/submodules.rs b/src/components/submodules.rs index 017028da..58eb0e44 100644 --- a/src/components/submodules.rs +++ b/src/components/submodules.rs @@ -5,11 +5,15 @@ use super::{ }; use crate::{ keys::{key_match, SharedKeyConfig}, + queue::{InternalEvent, Queue}, strings, ui::{self, Size}, }; use anyhow::Result; -use asyncgit::sync::{get_submodules, RepoPathRef, SubmoduleInfo}; +use asyncgit::sync::{ + get_submodules, repo_dir, submodule_parent_info, RepoPathRef, + SubmoduleInfo, SubmoduleParentInfo, +}; use crossterm::event::Event; use std::{cell::Cell, convert::TryInto}; use tui::{ @@ -18,7 +22,7 @@ use tui::{ Alignment, Constraint, Direction, Layout, Margin, Rect, }, text::{Span, Spans, Text}, - widgets::{Block, BorderType, Borders, Clear, Paragraph}, + widgets::{Block, Borders, Clear, Paragraph}, Frame, }; use ui::style::SharedTheme; @@ -27,7 +31,10 @@ use unicode_truncate::UnicodeTruncateStr; /// pub struct SubmodulesListComponent { repo: RepoPathRef, + repo_path: String, + queue: Queue, submodules: Vec, + submodule_parent: Option, visible: bool, current_height: Cell, selection: u16, @@ -59,7 +66,6 @@ impl DrawableComponent for SubmodulesListComponent { f.render_widget( Block::default() .title(strings::POPUP_TITLE_SUBMODULES) - .border_type(BorderType::Thick) .borders(Borders::ALL), area, ); @@ -69,16 +75,25 @@ impl DrawableComponent for SubmodulesListComponent { horizontal: 1, }); + let chunks_vertical = Layout::default() + .direction(Direction::Vertical) + .constraints( + [Constraint::Min(1), Constraint::Length(5)] + .as_ref(), + ) + .split(area); + let chunks = Layout::default() .direction(Direction::Horizontal) .constraints( [Constraint::Min(40), Constraint::Length(40)] .as_ref(), ) - .split(area); + .split(chunks_vertical[0]); self.draw_list(f, chunks[0])?; self.draw_info(f, chunks[1]); + self.draw_local_info(f, chunks_vertical[1]); } Ok(()) @@ -107,6 +122,20 @@ impl Component for SubmodulesListComponent { true, true, )); + + out.push(CommandInfo::new( + strings::commands::open_submodule(&self.key_config), + self.is_valid_selection(), + true, + )); + + out.push(CommandInfo::new( + strings::commands::open_submodule_parent( + &self.key_config, + ), + self.submodule_parent.is_some(), + true, + )); } visibility_blocking(self) } @@ -143,6 +172,21 @@ impl Component for SubmodulesListComponent { return self .move_selection(ScrollType::End) .map(Into::into); + } else if key_match(e, self.key_config.keys.enter) { + if let Some(submodule) = self.selected_entry() { + self.queue.push(InternalEvent::OpenRepo { + path: submodule.path.clone(), + }); + } + } else if key_match( + e, + self.key_config.keys.view_submodule_parent, + ) { + if let Some(parent) = &self.submodule_parent { + self.queue.push(InternalEvent::OpenRepo { + path: parent.parent_gitpath.clone(), + }); + } } else if key_match( e, self.key_config.keys.cmd_bar_toggle, @@ -173,18 +217,22 @@ impl Component for SubmodulesListComponent { impl SubmodulesListComponent { pub fn new( repo: RepoPathRef, + queue: &Queue, theme: SharedTheme, key_config: SharedKeyConfig, ) -> Self { Self { submodules: Vec::new(), + submodule_parent: None, scroll: VerticalScroll::new(), + queue: queue.clone(), selection: 0, visible: false, theme, key_config, current_height: Cell::new(0), repo, + repo_path: String::new(), } } @@ -201,6 +249,13 @@ impl SubmodulesListComponent { if self.is_visible() { self.submodules = get_submodules(&self.repo.borrow())?; + self.submodule_parent = + submodule_parent_info(&self.repo.borrow())?; + + self.repo_path = repo_dir(&self.repo.borrow()) + .map(|e| e.to_string_lossy().to_string()) + .unwrap_or_default(); + self.set_selection(self.selection)?; } Ok(()) @@ -210,6 +265,10 @@ impl SubmodulesListComponent { self.submodules.get(self.selection as usize) } + fn is_valid_selection(&self) -> bool { + self.selected_entry().is_some() + } + //TODO: dedup this almost identical with BranchListComponent fn move_selection(&mut self, scroll: ScrollType) -> Result { let new_selection = match scroll { @@ -234,11 +293,11 @@ impl SubmodulesListComponent { } 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 num_entriess: u16 = self.submodules.len().try_into()?; + let num_entriess = num_entriess.saturating_sub(1); - let selection = if selection > num_branches { - num_branches + let selection = if selection > num_entriess { + num_entriess } else { selection }; @@ -359,6 +418,32 @@ impl SubmodulesListComponent { ) } + fn get_local_info_text(&self, theme: &SharedTheme) -> Text { + let mut spans = vec![ + Spans::from(vec![Span::styled( + "Current:", + theme.text(false, false), + )]), + Spans::from(vec![Span::styled( + self.repo_path.to_string(), + theme.text(true, false), + )]), + Spans::from(vec![Span::styled( + "Parent:", + theme.text(false, false), + )]), + ]; + + if let Some(parent_info) = &self.submodule_parent { + spans.push(Spans::from(vec![Span::styled( + parent_info.parent_gitpath.to_string_lossy(), + theme.text(true, false), + )])); + } + + Text::from(spans) + } + fn draw_list( &self, f: &mut Frame, @@ -399,4 +484,13 @@ impl SubmodulesListComponent { r, ); } + + fn draw_local_info(&self, f: &mut Frame, r: Rect) { + f.render_widget( + Paragraph::new(self.get_local_info_text(&self.theme)) + .block(Block::default().borders(Borders::TOP)) + .alignment(Alignment::Left), + r, + ); + } } diff --git a/src/input.rs b/src/input.rs index a3bd169c..c42f6256 100644 --- a/src/input.rs +++ b/src/input.rs @@ -28,6 +28,7 @@ pub enum InputEvent { } /// +#[derive(Clone)] pub struct Input { desired_state: Arc>, current_state: Arc, diff --git a/src/keys/key_config.rs b/src/keys/key_config.rs index 71d3e03b..6016e52f 100644 --- a/src/keys/key_config.rs +++ b/src/keys/key_config.rs @@ -11,7 +11,7 @@ use super::{ pub type SharedKeyConfig = Rc; -#[derive(Default)] +#[derive(Default, Clone)] pub struct KeyConfig { pub keys: KeysList, symbols: KeySymbols, diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs index cc0f8282..c28a0ced 100644 --- a/src/keys/key_list.rs +++ b/src/keys/key_list.rs @@ -34,6 +34,7 @@ impl From<&GituiKeyEvent> for KeyEvent { } } +#[derive(Clone)] pub struct KeysList { pub tab_status: GituiKeyEvent, pub tab_log: GituiKeyEvent, @@ -108,6 +109,7 @@ pub struct KeysList { pub stage_unstage_item: GituiKeyEvent, pub tag_annotate: GituiKeyEvent, pub view_submodules: GituiKeyEvent, + pub view_submodule_parent: GituiKeyEvent, } #[rustfmt::skip] @@ -187,7 +189,7 @@ impl Default for KeysList { stage_unstage_item: GituiKeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), tag_annotate: GituiKeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL), view_submodules: GituiKeyEvent::new(KeyCode::Char('S'), KeyModifiers::SHIFT), - + view_submodule_parent: GituiKeyEvent::new(KeyCode::Char('p'), KeyModifiers::empty()), } } } diff --git a/src/keys/key_list_file.rs b/src/keys/key_list_file.rs index 0213f7ad..189c43d1 100644 --- a/src/keys/key_list_file.rs +++ b/src/keys/key_list_file.rs @@ -168,6 +168,7 @@ impl KeysListFile { stage_unstage_item: self.stage_unstage_item.unwrap_or(default.stage_unstage_item), tag_annotate: self.tag_annotate.unwrap_or(default.tag_annotate), view_submodules: self.view_submodules.unwrap_or(default.view_submodules), + view_submodule_parent: self.view_submodules.unwrap_or(default.view_submodule_parent), } } } diff --git a/src/keys/symbols.rs b/src/keys/symbols.rs index a70f04ee..8bdb7b50 100644 --- a/src/keys/symbols.rs +++ b/src/keys/symbols.rs @@ -3,7 +3,7 @@ use std::{fs::File, io::Read, path::PathBuf}; use anyhow::Result; use serde::{Deserialize, Serialize}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct KeySymbols { pub enter: String, pub left: String, diff --git a/src/main.rs b/src/main.rs index 50e66801..b9395be4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,7 @@ mod version; use crate::{app::App, args::process_cmdline}; use anyhow::{bail, Result}; +use app::QuitState; use asyncgit::{sync::RepoPath, AsyncGitNotification}; use backtrace::Backtrace; use crossbeam_channel::{tick, unbounded, Receiver, Select}; @@ -114,7 +115,7 @@ fn main() -> Result<()> { let key_config = KeyConfig::init() .map_err(|e| eprintln!("KeyConfig loading error: {}", e)) .unwrap_or_default(); - let theme = Theme::init(cliargs.theme) + let theme = Theme::init(&cliargs.theme) .map_err(|e| eprintln!("Theme loading error: {}", e)) .unwrap_or_default(); @@ -126,21 +127,48 @@ fn main() -> Result<()> { set_panic_handlers()?; let mut terminal = start_terminal(io::stdout())?; + let mut repo_path = cliargs.repo_path; + let input = Input::new(); + loop { + let quit_state = run_app( + repo_path.clone(), + theme, + key_config.clone(), + &input, + &mut terminal, + )?; + + match quit_state { + QuitState::OpenSubmodule(p) => { + repo_path = p; + } + _ => break, + } + } + + Ok(()) +} + +fn run_app( + repo: RepoPath, + theme: Theme, + key_config: KeyConfig, + input: &Input, + terminal: &mut Terminal>, +) -> Result { let (tx_git, rx_git) = unbounded(); let (tx_app, rx_app) = unbounded(); - let input = Input::new(); - let rx_input = input.receiver(); let ticker = tick(TICK_INTERVAL); let spinner_ticker = tick(SPINNER_INTERVAL); let mut app = App::new( - RefCell::new(cliargs.repo_path), + RefCell::new(repo), &tx_git, &tx_app, - input, + input.clone(), theme, key_config, ); @@ -165,7 +193,7 @@ fn main() -> Result<()> { { if let QueueEvent::SpinnerUpdate = event { spinner.update(); - spinner.draw(&mut terminal)?; + spinner.draw(terminal)?; continue; } @@ -194,10 +222,10 @@ fn main() -> Result<()> { QueueEvent::SpinnerUpdate => unreachable!(), } - draw(&mut terminal, &app)?; + draw(terminal, &app)?; spinner.set_state(app.any_work_pending()); - spinner.draw(&mut terminal)?; + spinner.draw(terminal)?; if app.is_quit() { break; @@ -205,7 +233,7 @@ fn main() -> Result<()> { } } - Ok(()) + Ok(app.quit_state()) } fn setup_terminal() -> Result<()> { diff --git a/src/queue.rs b/src/queue.rs index 13952d5c..dd033bed 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -124,6 +124,8 @@ pub enum InternalEvent { PopupStackPush(StackablePopupOpen), /// ViewSubmodules, + /// + OpenRepo { path: PathBuf }, } /// single threaded simple queue for components to communicate with each other diff --git a/src/strings.rs b/src/strings.rs index 231124ca..d781a995 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -716,6 +716,33 @@ pub mod commands { ) } + pub fn open_submodule( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Open [{}]", + key_config.get_hint(key_config.keys.enter), + ), + "open submodule", + CMD_GROUP_GENERAL, + ) + } + + pub fn open_submodule_parent( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Open Parent [{}]", + key_config + .get_hint(key_config.keys.view_submodule_parent), + ), + "open submodule parent repo", + CMD_GROUP_GENERAL, + ) + } + pub fn continue_rebase( key_config: &SharedKeyConfig, ) -> CommandText { diff --git a/src/ui/style.rs b/src/ui/style.rs index f59f1f41..3a33948f 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -15,7 +15,7 @@ use tui::style::{Color, Modifier, Style}; pub type SharedTheme = Rc; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct Theme { selected_tab: Color, #[serde(with = "Color")] @@ -279,7 +279,7 @@ impl Theme { } // This will only be called when theme.ron doesn't already exists - fn save(&self, theme_file: PathBuf) -> Result<()> { + fn save(&self, theme_file: &PathBuf) -> Result<()> { let mut file = File::create(theme_file)?; let data = to_string_pretty(self, PrettyConfig::default())?; file.write_all(data.as_bytes())?; @@ -293,7 +293,7 @@ impl Theme { Ok(from_bytes(&buffer)?) } - pub fn init(file: PathBuf) -> Result { + pub fn init(file: &PathBuf) -> Result { if file.exists() { match Self::read_file(file.clone()) { Err(e) => {