From 75714dedee7c1a9c16f43acc836b7b5b28ca2557 Mon Sep 17 00:00:00 2001 From: Nicolas Danelon Date: Fri, 6 Mar 2026 13:53:46 +0100 Subject: [PATCH] New tab opens on current directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch add a new feature, now tab behavior now inherits the active tab’s current directory on Linux controlled by a settings toggle, and default is off --- i18n/en/cosmic_term.ftl | 2 ++ src/config.rs | 3 ++ src/main.rs | 70 ++++++++++++++++++++++++++++++++--------- src/terminal.rs | 21 ++++++++++++- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/i18n/en/cosmic_term.ftl b/i18n/en/cosmic_term.ftl index 659021d..e2e459f 100644 --- a/i18n/en/cosmic_term.ftl +++ b/i18n/en/cosmic_term.ftl @@ -63,6 +63,8 @@ focus-follow-mouse = Typing focus follows mouse advanced = Advanced show-headerbar = Show header show-header-description = Reveal the header from the right-click menu +tab-new-inherit-working-directory = New tabs use current directory +tab-new-inherit-working-directory-description = Open new tabs in the active tab's working directory ### Keyboard shortcuts add-another-keybinding = Add another keybinding diff --git a/src/config.rs b/src/config.rs index 39e6568..49eb079 100644 --- a/src/config.rs +++ b/src/config.rs @@ -235,6 +235,8 @@ pub struct Config { pub syntax_theme_dark: String, pub syntax_theme_light: String, pub focus_follow_mouse: bool, + #[serde(default)] + pub tab_new_inherit_working_directory: bool, pub default_profile: Option, #[serde(default)] pub shortcuts_custom: Shortcuts, @@ -249,6 +251,7 @@ impl Default for Config { color_schemes_light: BTreeMap::new(), dim_font_weight: Weight::NORMAL.0, focus_follow_mouse: false, + tab_new_inherit_working_directory: false, font_name: "Noto Sans Mono".to_string(), font_size: 14, font_size_zoom_step_mul_100: 100, diff --git a/src/main.rs b/src/main.rs index f394359..c64c926 100644 --- a/src/main.rs +++ b/src/main.rs @@ -431,6 +431,7 @@ pub enum Message { ShowHeaderBar(bool), SyntaxTheme(ColorSchemeKind, usize), SystemThemeChange, + TabNewInheritWorkingDirectory(bool), TabActivate(segmented_button::Entity), TabActivateJump(usize), TabClose(Option), @@ -1483,11 +1484,21 @@ impl App { .toggler(self.config.focus_follow_mouse, Message::FocusFollowMouse), ); - let advanced_section = widget::settings::section().title(fl!("advanced")).add( - widget::settings::item::builder(fl!("show-headerbar")) - .description(fl!("show-header-description")) - .toggler(self.config.show_headerbar, Message::ShowHeaderBar), - ); + let advanced_section = widget::settings::section() + .title(fl!("advanced")) + .add( + widget::settings::item::builder(fl!("show-headerbar")) + .description(fl!("show-header-description")) + .toggler(self.config.show_headerbar, Message::ShowHeaderBar), + ) + .add( + widget::settings::item::builder(fl!("tab-new-inherit-working-directory")) + .description(fl!("tab-new-inherit-working-directory-description")) + .toggler( + self.config.tab_new_inherit_working_directory, + Message::TabNewInheritWorkingDirectory, + ), + ); widget::settings::view_column(vec![ appearance_section.into(), @@ -1501,11 +1512,23 @@ impl App { self.config.default_profile } + fn active_terminal_working_directory(&self) -> Option { + let tab_model = self.pane_model.active()?; + let entity = tab_model.active(); + let terminal = tab_model.data::>(entity)?; + let terminal = terminal.lock().unwrap(); + terminal.working_directory() + } + fn create_and_focus_new_terminal( &mut self, pane: pane_grid::Pane, profile_id_opt: Option, + inherit_working_directory: bool, ) -> Task { + let inherited_working_directory = inherit_working_directory + .then(|| self.active_terminal_working_directory()) + .flatten(); self.pane_model.set_focus(pane); match &self.term_event_tx_opt { Some(term_event_tx) => { @@ -1542,12 +1565,13 @@ impl App { } return None; }), - working_directory: startup_options.working_directory.or_else( - || { + working_directory: startup_options + .working_directory + .or_else(|| inherited_working_directory.clone()) + .or_else(|| { (!profile.working_directory.is_empty()) .then(|| profile.working_directory.clone().into()) - }, - ), + }), drain_on_exit: startup_options.drain_on_exit || profile.drain_on_exit, ..startup_options @@ -1559,7 +1583,11 @@ impl App { }; (options, tab_title_override) } else { - (self.startup_options.take().unwrap_or_default(), None) + let mut options = self.startup_options.take().unwrap_or_default(); + if options.working_directory.is_none() { + options.working_directory = inherited_working_directory.clone(); + } + (options, None) }; let entity = tab_model @@ -2541,7 +2569,7 @@ impl Application for App { if let Some((pane, _)) = result { self.terminal_ids.insert(pane, widget::Id::unique()); let command = - self.create_and_focus_new_terminal(pane, self.get_default_profile()); + self.create_and_focus_new_terminal(pane, self.get_default_profile(), false); self.pane_model.panes_created += 1; return command; } @@ -2653,8 +2681,11 @@ impl Application for App { return self.save_profiles(); } Message::ProfileOpen(profile_id) => { - return self - .create_and_focus_new_terminal(self.pane_model.focused(), Some(profile_id)); + return self.create_and_focus_new_terminal( + self.pane_model.focused(), + Some(profile_id), + false, + ); } Message::ProfileRemove(profile_id) => { // Reset matching terminals to default profile @@ -2720,6 +2751,12 @@ impl Application for App { return self.update_config(); } } + Message::TabNewInheritWorkingDirectory(tab_new_inherit_working_directory) => { + config_set!( + tab_new_inherit_working_directory, + tab_new_inherit_working_directory + ); + } Message::UseBrightBold(use_bright_bold) => { if use_bright_bold != self.config.use_bright_bold { config_set!(use_bright_bold, use_bright_bold); @@ -2863,10 +2900,15 @@ impl Application for App { return self.create_and_focus_new_terminal( self.pane_model.focused(), self.get_default_profile(), + self.config.tab_new_inherit_working_directory, ); } Message::TabNewNoProfile => { - return self.create_and_focus_new_terminal(self.pane_model.focused(), None); + return self.create_and_focus_new_terminal( + self.pane_model.focused(), + None, + self.config.tab_new_inherit_working_directory, + ); } Message::TabNext => { if let Some(tab_model) = self.pane_model.active() { diff --git a/src/terminal.rs b/src/terminal.rs index a12471c..699cf1a 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -28,7 +28,8 @@ use indexmap::IndexSet; use std::{ borrow::Cow, collections::HashMap, - io, mem, + fs, io, mem, + path::PathBuf, sync::{ Arc, Mutex, Weak, atomic::{AtomicU32, Ordering}, @@ -257,6 +258,7 @@ pub struct Terminal { notifier: Notifier, search_regex_opt: Option, search_value: String, + shell_pid: Option, size: Size, use_bright_bold: bool, zoom_adj: i8, @@ -328,6 +330,10 @@ impl Terminal { let window_id = 0; let pty = tty::new(&options, size.into(), window_id)?; + #[cfg(not(windows))] + let shell_pid = Some(pty.child().id()); + #[cfg(windows)] + let shell_pid = pty.child_watcher().pid().map(|pid| pid.get()); let pty_event_loop = EventLoop::new(term.clone(), event_proxy, pty, options.drain_on_exit, false)?; @@ -352,6 +358,7 @@ impl Terminal { profile_id_opt, search_regex_opt: None, search_value: String::new(), + shell_pid, size, tab_title_override, term, @@ -407,6 +414,18 @@ impl Terminal { self.notifier.notify(input); } + pub fn working_directory(&self) -> Option { + #[cfg(not(windows))] + { + let shell_pid = self.shell_pid?; + fs::read_link(format!("/proc/{shell_pid}/cwd")).ok() + } + #[cfg(windows)] + { + None + } + } + pub fn input_scroll>>(&self, input: I) { self.input_no_scroll(input); self.scroll(TerminalScroll::Bottom);