From 47283f2be62d6d667352d9320358172b88be702a Mon Sep 17 00:00:00 2001 From: Christian Zangl Date: Thu, 23 Oct 2025 21:44:05 +0200 Subject: [PATCH 1/4] add cli flag to open files tab with selected file #2510 --- CHANGELOG.md | 1 + src/app.rs | 20 +++++++++++++++++++- src/args.rs | 14 ++++++++++++++ src/main.rs | 8 +++++++- src/queue.rs | 2 ++ src/tabs/files.rs | 4 ++++ 6 files changed, 47 insertions(+), 2 deletions(-) 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..54c51c3b14 100644 --- a/src/app.rs +++ b/src/app.rs @@ -155,6 +155,7 @@ impl App { sender_app: Sender, input: Input, theme: Theme, + select_file: Option, key_config: KeyConfig, ) -> Result { log::trace!("open repo at: {:?}", &repo); @@ -230,7 +231,21 @@ impl App { popup_stack: PopupStack::default(), }; - app.set_tab(tab)?; + if let Some(file) = select_file { + app.set_tab(2)?; + // convert to relative git path + if let Ok(abs) = file.canonicalize() { + let repo = + app.repo.borrow().gitpath().canonicalize()?; + if let Ok(path) = abs.strip_prefix(repo) { + app.queue.push(InternalEvent::SelectFile { + path: Path::new(".").join(path), + }); + } + } + } else { + app.set_tab(tab)?; + } Ok(app) } @@ -771,6 +786,9 @@ impl App { InternalEvent::SelectBranch => { self.select_branch_popup.open()?; } + InternalEvent::SelectFile { path } => { + self.files_tab.find_file(&path); + } InternalEvent::ViewSubmodules => { self.submodule_popup.open()?; } diff --git a/src/args.rs b/src/args.rs index a7d9d99540..1faabbd8fc 100644 --- a/src/args.rs +++ b/src/args.rs @@ -17,6 +17,7 @@ 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"; @@ -24,6 +25,7 @@ const DEFAULT_GIT_DIR: &str = "."; pub struct CliArgs { pub theme: PathBuf, + pub select_file: Option, pub repo_path: RepoPath, pub notify_watcher: bool, } @@ -51,6 +53,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 +81,7 @@ pub fn process_cmdline() -> Result { Ok(CliArgs { theme, + select_file, repo_path, notify_watcher, }) @@ -129,6 +136,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/main.rs b/src/main.rs index 72165b083b..7e06396c13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,7 +105,7 @@ use std::{ cell::RefCell, io::{self, Stdout}, panic, - path::Path, + path::{Path, PathBuf}, time::{Duration, Instant}, }; use ui::style::Theme; @@ -181,6 +181,7 @@ fn main() -> Result<()> { set_panic_handler()?; let mut repo_path = cliargs.repo_path; + let mut select_file = cliargs.select_file; let mut terminal = start_terminal(io::stdout(), &repo_path)?; let input = Input::new(); @@ -195,6 +196,7 @@ fn main() -> Result<()> { app_start, repo_path.clone(), theme.clone(), + select_file.clone(), key_config.clone(), &input, updater, @@ -204,6 +206,7 @@ fn main() -> Result<()> { match quit_state { QuitState::OpenSubmodule(p) => { repo_path = p; + select_file = None; } _ => break, } @@ -212,10 +215,12 @@ fn main() -> Result<()> { Ok(()) } +#[allow(clippy::too_many_arguments)] fn run_app( app_start: Instant, repo: RepoPath, theme: Theme, + select_file: Option, key_config: KeyConfig, input: &Input, updater: Updater, @@ -244,6 +249,7 @@ fn run_app( tx_app, input.clone(), theme, + select_file, key_config, )?; diff --git a/src/queue.rs b/src/queue.rs index 635fbc9e71..9b11d49a27 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -120,6 +120,8 @@ pub enum InternalEvent { /// SelectBranch, /// + SelectFile { path: PathBuf }, + /// OpenExternalEditor(Option), /// Push(String, PushType, bool, bool), diff --git a/src/tabs/files.rs b/src/tabs/files.rs index 79a071f25a..e2a0447e5a 100644 --- a/src/tabs/files.rs +++ b/src/tabs/files.rs @@ -58,6 +58,10 @@ impl FilesTab { pub fn file_finder_update(&mut self, file: &Path) { self.files.find_file(file); } + + pub fn find_file(&mut self, file: &Path) { + self.files.find_file(file); + } } impl DrawableComponent for FilesTab { From e8b278a83289eb4a8e1dd69d9c8b887c25728e4a Mon Sep 17 00:00:00 2001 From: Christian Zangl Date: Thu, 30 Oct 2025 09:37:48 +0100 Subject: [PATCH 2/4] add cli flag to open files tab with selected file #2510 --- src/app.rs | 7 ++++--- src/args.rs | 1 + src/main.rs | 37 ++++++++++++++++++++----------------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/app.rs b/src/app.rs index 54c51c3b14..98638013b7 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,14 +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, - select_file: Option, key_config: KeyConfig, ) -> Result { + let repo = RefCell::new(cliargs.repo_path.clone()); log::trace!("open repo at: {:?}", &repo); let repo_path_text = @@ -231,7 +232,7 @@ impl App { popup_stack: PopupStack::default(), }; - if let Some(file) = select_file { + if let Some(file) = cliargs.select_file { app.set_tab(2)?; // convert to relative git path if let Ok(abs) = file.canonicalize() { diff --git a/src/args.rs b/src/args.rs index 1faabbd8fc..afc59c3754 100644 --- a/src/args.rs +++ b/src/args.rs @@ -23,6 +23,7 @@ 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, diff --git a/src/main.rs b/src/main.rs index 7e06396c13..8efb17e63f 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,10 +105,9 @@ use scopeguard::defer; use scopetime::scope_time; use spinner::Spinner; use std::{ - cell::RefCell, io::{self, Stdout}, panic, - path::{Path, PathBuf}, + path::Path, time::{Duration, Instant}, }; use ui::style::Theme; @@ -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,9 +182,8 @@ fn main() -> Result<()> { set_panic_handler()?; - let mut repo_path = cliargs.repo_path; - let mut select_file = cliargs.select_file; - 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 { @@ -194,9 +195,8 @@ fn main() -> Result<()> { loop { let quit_state = run_app( app_start, - repo_path.clone(), + cliargs.clone(), theme.clone(), - select_file.clone(), key_config.clone(), &input, updater, @@ -205,8 +205,12 @@ fn main() -> Result<()> { match quit_state { QuitState::OpenSubmodule(p) => { - repo_path = p; - select_file = None; + cliargs = CliArgs { + repo_path: p, + select_file: None, + theme: cliargs.theme, + notify_watcher: cliargs.notify_watcher, + } } _ => break, } @@ -218,9 +222,8 @@ fn main() -> Result<()> { #[allow(clippy::too_many_arguments)] fn run_app( app_start: Instant, - repo: RepoPath, + cliargs: CliArgs, theme: Theme, - select_file: Option, key_config: KeyConfig, input: &Input, updater: Updater, @@ -233,8 +236,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()) } @@ -244,12 +248,11 @@ fn run_app( let spinner_ticker = tick(SPINNER_INTERVAL); let mut app = App::new( - RefCell::new(repo), + cliargs, tx_git, tx_app, input.clone(), theme, - select_file, key_config, )?; From f515fd40622cf3468a7e5a9942b3cacebc6a2bf5 Mon Sep 17 00:00:00 2001 From: Christian Zangl Date: Thu, 30 Oct 2025 10:12:09 +0100 Subject: [PATCH 3/4] add cli flag to open files tab with selected file #2510 --- src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 8efb17e63f..e9ae4de048 100644 --- a/src/main.rs +++ b/src/main.rs @@ -219,7 +219,6 @@ fn main() -> Result<()> { Ok(()) } -#[allow(clippy::too_many_arguments)] fn run_app( app_start: Instant, cliargs: CliArgs, From 4fb29cd33accfdb709f087a1a1f8c9ff38cd4005 Mon Sep 17 00:00:00 2001 From: Christian Zangl Date: Sat, 1 Nov 2025 18:23:43 +0100 Subject: [PATCH 4/4] add cli flag to open files tab with selected file #2510 --- src/app.rs | 36 ++++++++++++++------------------ src/components/revision_files.rs | 18 ++++++++++++++-- src/popups/revision_files.rs | 2 +- src/queue.rs | 2 -- src/tabs/files.rs | 13 ++++++------ 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/app.rs b/src/app.rs index 98638013b7..cdcdf04076 100644 --- a/src/app.rs +++ b/src/app.rs @@ -174,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, @@ -219,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, @@ -232,21 +245,7 @@ impl App { popup_stack: PopupStack::default(), }; - if let Some(file) = cliargs.select_file { - app.set_tab(2)?; - // convert to relative git path - if let Ok(abs) = file.canonicalize() { - let repo = - app.repo.borrow().gitpath().canonicalize()?; - if let Ok(path) = abs.strip_prefix(repo) { - app.queue.push(InternalEvent::SelectFile { - path: Path::new(".").join(path), - }); - } - } - } else { - app.set_tab(tab)?; - } + app.set_tab(tab)?; Ok(app) } @@ -787,9 +786,6 @@ impl App { InternalEvent::SelectBranch => { self.select_branch_popup.open()?; } - InternalEvent::SelectFile { path } => { - self.files_tab.find_file(&path); - } InternalEvent::ViewSubmodules => { self.submodule_popup.open()?; } 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/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/queue.rs b/src/queue.rs index 9b11d49a27..635fbc9e71 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -120,8 +120,6 @@ pub enum InternalEvent { /// SelectBranch, /// - SelectFile { path: PathBuf }, - /// OpenExternalEditor(Option), /// Push(String, PushType, bool, bool), diff --git a/src/tabs/files.rs b/src/tabs/files.rs index e2a0447e5a..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(), } } @@ -58,10 +61,6 @@ impl FilesTab { pub fn file_finder_update(&mut self, file: &Path) { self.files.find_file(file); } - - pub fn find_file(&mut self, file: &Path) { - self.files.find_file(file); - } } impl DrawableComponent for FilesTab {