diff --git a/CHANGELOG.md b/CHANGELOG.md index b8ded4447d..7b16eb7c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * new command-line option to override the default log file path (`--logfile`) [[@acuteenvy](https://github.com/acuteenvy)] ([#2539](https://github.com/gitui-org/gitui/pull/2539)) * dx: `make check` checks Cargo.toml dependency ordering using `cargo sort` [[@naseschwarz](https://github.com/naseschwarz)] * add `use_selection_fg` to theme file to allow customizing selection foreground color [[@Upsylonbare](https://github.com/Upsylonbare)] ([#2515](https://github.com/gitui-org/gitui/pull/2515)) +* add `--file` cli flag to open the files tab with the given file already selected [[@laktak](https://github.com/laktak)] ([#2510](https://github.com/gitui-org/gitui/issues/2510)) ### Changed * improve error messages [[@acuteenvy](https://github.com/acuteenvy)] ([#2617](https://github.com/gitui-org/gitui/pull/2617)) diff --git a/src/app.rs b/src/app.rs index 479e2868cf..cdcdf04076 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,6 @@ use crate::{ accessors, + args::CliArgs, cmdbar::CommandBar, components::{ command_pump, event_pump, CommandInfo, Component, @@ -150,13 +151,14 @@ impl App { /// #[allow(clippy::too_many_lines)] pub fn new( - repo: RepoPathRef, + cliargs: CliArgs, sender_git: Sender, sender_app: Sender, input: Input, theme: Theme, key_config: KeyConfig, ) -> Result { + let repo = RefCell::new(cliargs.repo_path.clone()); log::trace!("open repo at: {:?}", &repo); let repo_path_text = @@ -172,7 +174,20 @@ impl App { sender_app, }; - let tab = env.options.borrow().current_tab(); + let mut select_file: Option = None; + let tab = if let Some(file) = cliargs.select_file { + // convert to relative git path + if let Ok(abs) = file.canonicalize() { + if let Ok(path) = abs.strip_prefix( + env.repo.borrow().gitpath().canonicalize()?, + ) { + select_file = Some(Path::new(".").join(path)); + } + } + 2 + } else { + env.options.borrow().current_tab() + }; let mut app = Self { input, @@ -217,7 +232,7 @@ impl App { status_tab: Status::new(&env), stashing_tab: Stashing::new(&env), stashlist_tab: StashList::new(&env), - files_tab: FilesTab::new(&env), + files_tab: FilesTab::new(&env, select_file), tab: 0, queue: env.queue, theme: env.theme, diff --git a/src/args.rs b/src/args.rs index a7d9d99540..afc59c3754 100644 --- a/src/args.rs +++ b/src/args.rs @@ -17,13 +17,16 @@ const LOG_FILE_FLAG_ID: &str = "logfile"; const LOGGING_FLAG_ID: &str = "logging"; const THEME_FLAG_ID: &str = "theme"; const WORKDIR_FLAG_ID: &str = "workdir"; +const FILE_FLAG_ID: &str = "file"; const GIT_DIR_FLAG_ID: &str = "directory"; const WATCHER_FLAG_ID: &str = "watcher"; const DEFAULT_THEME: &str = "theme.ron"; const DEFAULT_GIT_DIR: &str = "."; +#[derive(Clone)] pub struct CliArgs { pub theme: PathBuf, + pub select_file: Option, pub repo_path: RepoPath, pub notify_watcher: bool, } @@ -51,6 +54,10 @@ pub fn process_cmdline() -> Result { PathBuf::from, ); + let select_file = arg_matches + .get_one::(FILE_FLAG_ID) + .map(PathBuf::from); + let repo_path = if let Some(w) = workdir { RepoPath::Workdir { gitdir, workdir: w } } else { @@ -75,6 +82,7 @@ pub fn process_cmdline() -> Result { Ok(CliArgs { theme, + select_file, repo_path, notify_watcher, }) @@ -129,6 +137,13 @@ fn app() -> ClapApp { .long("bugreport") .action(clap::ArgAction::SetTrue), ) + .arg( + Arg::new(FILE_FLAG_ID) + .help("Select the file in the file tab") + .short('f') + .long("file") + .num_args(1), + ) .arg( Arg::new(GIT_DIR_FLAG_ID) .help("Set the git directory") diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index f3fec043d0..1e15ec086f 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -30,7 +30,10 @@ use ratatui::{ Frame, }; use std::{borrow::Cow, fmt::Write}; -use std::{collections::BTreeSet, path::Path}; +use std::{ + collections::BTreeSet, + path::{Path, PathBuf}, +}; use unicode_truncate::UnicodeTruncateStr; use unicode_width::UnicodeWidthStr; @@ -53,11 +56,15 @@ pub struct RevisionFilesComponent { revision: Option, focus: Focus, key_config: SharedKeyConfig, + select_file: Option, } impl RevisionFilesComponent { /// - pub fn new(env: &Environment) -> Self { + pub fn new( + env: &Environment, + select_file: Option, + ) -> Self { Self { queue: env.queue.clone(), tree: FileTree::default(), @@ -72,6 +79,7 @@ impl RevisionFilesComponent { focus: Focus::Tree, key_config: env.key_config.clone(), repo: env.repo.clone(), + select_file, visible: false, } } @@ -134,6 +142,12 @@ impl RevisionFilesComponent { self.tree.collapse_but_root(); self.files = Some(last); + + let select_file = self.select_file.clone(); + self.select_file = None; + if let Some(file) = select_file { + self.find_file(file.as_path()); + } } } else if let Some(rev) = &self.revision { self.request_files(rev.id); diff --git a/src/main.rs b/src/main.rs index 72165b083b..e9ae4de048 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,7 +79,10 @@ mod tabs; mod ui; mod watcher; -use crate::{app::App, args::process_cmdline}; +use crate::{ + app::App, + args::{process_cmdline, CliArgs}, +}; use anyhow::{anyhow, bail, Result}; use app::QuitState; use asyncgit::{ @@ -102,7 +105,6 @@ use scopeguard::defer; use scopetime::scope_time; use spinner::Spinner; use std::{ - cell::RefCell, io::{self, Stdout}, panic, path::Path, @@ -163,7 +165,7 @@ macro_rules! log_eprintln { fn main() -> Result<()> { let app_start = Instant::now(); - let cliargs = process_cmdline()?; + let mut cliargs = process_cmdline()?; asyncgit::register_tracing_logging(); ensure_valid_path(&cliargs.repo_path)?; @@ -180,8 +182,8 @@ fn main() -> Result<()> { set_panic_handler()?; - let mut repo_path = cliargs.repo_path; - let mut terminal = start_terminal(io::stdout(), &repo_path)?; + let mut terminal = + start_terminal(io::stdout(), &cliargs.repo_path)?; let input = Input::new(); let updater = if cliargs.notify_watcher { @@ -193,7 +195,7 @@ fn main() -> Result<()> { loop { let quit_state = run_app( app_start, - repo_path.clone(), + cliargs.clone(), theme.clone(), key_config.clone(), &input, @@ -203,7 +205,12 @@ fn main() -> Result<()> { match quit_state { QuitState::OpenSubmodule(p) => { - repo_path = p; + cliargs = CliArgs { + repo_path: p, + select_file: None, + theme: cliargs.theme, + notify_watcher: cliargs.notify_watcher, + } } _ => break, } @@ -214,7 +221,7 @@ fn main() -> Result<()> { fn run_app( app_start: Instant, - repo: RepoPath, + cliargs: CliArgs, theme: Theme, key_config: KeyConfig, input: &Input, @@ -228,8 +235,9 @@ fn run_app( let (rx_ticker, rx_watcher) = match updater { Updater::NotifyWatcher => { - let repo_watcher = - RepoWatcher::new(repo_work_dir(&repo)?.as_str()); + let repo_watcher = RepoWatcher::new( + repo_work_dir(&cliargs.repo_path)?.as_str(), + ); (never(), repo_watcher.receiver()) } @@ -239,7 +247,7 @@ fn run_app( let spinner_ticker = tick(SPINNER_INTERVAL); let mut app = App::new( - RefCell::new(repo), + cliargs, tx_git, tx_app, input.clone(), diff --git a/src/popups/revision_files.rs b/src/popups/revision_files.rs index 9fbe9e25de..51ee94cfa9 100644 --- a/src/popups/revision_files.rs +++ b/src/popups/revision_files.rs @@ -38,7 +38,7 @@ impl RevisionFilesPopup { /// pub fn new(env: &Environment) -> Self { Self { - files: RevisionFilesComponent::new(env), + files: RevisionFilesComponent::new(env, None), visible: false, key_config: env.key_config.clone(), open_request: None, diff --git a/src/tabs/files.rs b/src/tabs/files.rs index 79a071f25a..7b5fc8d156 100644 --- a/src/tabs/files.rs +++ b/src/tabs/files.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use crate::{ app::Environment, @@ -19,10 +19,13 @@ pub struct FilesTab { impl FilesTab { /// - pub fn new(env: &Environment) -> Self { + pub fn new( + env: &Environment, + select_file: Option, + ) -> Self { Self { visible: false, - files: RevisionFilesComponent::new(env), + files: RevisionFilesComponent::new(env, select_file), repo: env.repo.clone(), } }