Skip to content

Commit dc000a2

Browse files
feat: Open new terms in focused term's CWD
Closes: #251 This patch implements an optional (but enabled by default) feature for opening new terminals using the focused terminal's working directory. The code to retrieve the CWD is largely based on Alacritty's implementation of the same feature. I added Rustix as a new direct dependency for the working directory logic. Both libc and Rustix are transitive dependencies of COSMIC Term. I opted for Rustix over libc to avoid an `unsafe` block as well as for its stronger type guarantees. References: * https://github.com/alacritty/alacritty/blob/6bd1674bd80e73df0d41e4342ad4e34bb7d04f84/alacritty/src/daemon.rs#L85-L108
1 parent 64e95eb commit dc000a2

File tree

6 files changed

+92
-8
lines changed

6 files changed

+92
-8
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ron = "0.8"
2323
serde = { version = "1", features = ["serde_derive"] }
2424
shlex = "1"
2525
tokio = { version = "1", features = ["sync"] }
26+
rustix = { version = "0.38", features = ["termios"] }
2627
# Internationalization
2728
i18n-embed = { version = "0.14", features = [
2829
"fluent-system",

i18n/en/cosmic_term.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ focus-follow-mouse = Typing focus follows mouse
6060
advanced = Advanced
6161
show-headerbar = Show header
6262
show-header-description = Reveal the header from the right-click menu.
63+
open-in-cwd = Use parent CWD
64+
open-in-cwd-description = Start new terms using the focused tab's working directory.
6365
6466
# Find
6567
find-placeholder = Find...

src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ pub struct Config {
229229
pub font_stretch: u16,
230230
pub font_size_zoom_step_mul_100: u16,
231231
pub opacity: u8,
232+
/// Open new terminal with the current working directory of the focused term
233+
pub open_in_cwd: bool,
232234
pub profiles: BTreeMap<ProfileId, Profile>,
233235
pub show_headerbar: bool,
234236
pub use_bright_bold: bool,
@@ -253,6 +255,7 @@ impl Default for Config {
253255
font_stretch: Stretch::Normal.to_number(),
254256
font_weight: Weight::NORMAL.0,
255257
opacity: 100,
258+
open_in_cwd: true,
256259
profiles: BTreeMap::new(),
257260
show_headerbar: true,
258261
syntax_theme_dark: COSMIC_THEME_DARK.to_string(),

src/main.rs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ pub enum Message {
338338
ProfileSyntaxTheme(ProfileId, ColorSchemeKind, usize),
339339
ProfileTabTitle(ProfileId, String),
340340
SelectAll(Option<segmented_button::Entity>),
341+
SetOpenInCWD(bool),
341342
ShowAdvancedFontSettings(bool),
342343
ShowHeaderBar(bool),
343344
SyntaxTheme(ColorSchemeKind, usize),
@@ -1199,11 +1200,17 @@ impl App {
11991200
.toggler(self.config.focus_follow_mouse, Message::FocusFollowMouse),
12001201
);
12011202

1202-
let advanced_section = widget::settings::view_section(fl!("advanced")).add(
1203-
widget::settings::item::builder(fl!("show-headerbar"))
1204-
.description(fl!("show-header-description"))
1205-
.toggler(self.config.show_headerbar, Message::ShowHeaderBar),
1206-
);
1203+
let advanced_section = widget::settings::view_section(fl!("advanced"))
1204+
.add(
1205+
widget::settings::item::builder(fl!("show-headerbar"))
1206+
.description(fl!("show-header-description"))
1207+
.toggler(self.config.show_headerbar, Message::ShowHeaderBar),
1208+
)
1209+
.add(
1210+
widget::settings::item::builder(fl!("open-in-cwd"))
1211+
.description(fl!("open-in-cwd-description"))
1212+
.toggler(self.config.open_in_cwd, Message::SetOpenInCWD),
1213+
);
12071214

12081215
widget::settings::view_column(vec![
12091216
appearance_section.into(),
@@ -1241,6 +1248,22 @@ impl App {
12411248
Some(colors) => {
12421249
let current_pane = self.pane_model.focus;
12431250
if let Some(tab_model) = self.pane_model.active_mut() {
1251+
// Current working directory of the selected tab/terminal
1252+
#[cfg(not(windows))]
1253+
let cwd = self
1254+
.config
1255+
.open_in_cwd
1256+
.then(|| {
1257+
tab_model.active_data::<Mutex<Terminal>>().and_then(
1258+
|terminal| {
1259+
terminal.lock().unwrap().current_working_directory()
1260+
},
1261+
)
1262+
})
1263+
.flatten();
1264+
#[cfg(windows)]
1265+
let cwd: Option<std::path::PathBuf> = None;
1266+
12441267
// Use the profile options, startup options, or defaults
12451268
let (options, tab_title_override) = match profile_id_opt
12461269
.and_then(|profile_id| self.config.profiles.get(&profile_id))
@@ -1253,8 +1276,8 @@ impl App {
12531276
shell = Some(tty::Shell::new(command, args));
12541277
}
12551278
}
1256-
let working_directory = (!profile.working_directory.is_empty())
1257-
.then(|| profile.working_directory.clone().into());
1279+
let working_directory = cwd
1280+
.or_else(|| Some(profile.working_directory.clone().into()));
12581281

12591282
let options = tty::Options {
12601283
shell,
@@ -1269,7 +1292,12 @@ impl App {
12691292
};
12701293
(options, tab_title_override)
12711294
}
1272-
None => (self.startup_options.take().unwrap_or_default(), None),
1295+
None => {
1296+
let mut options =
1297+
self.startup_options.take().unwrap_or_default();
1298+
options.working_directory = cwd;
1299+
(options, None)
1300+
}
12731301
};
12741302
let entity = tab_model
12751303
.insert()
@@ -2211,6 +2239,12 @@ impl Application for App {
22112239
}
22122240
return self.update_focus();
22132241
}
2242+
Message::SetOpenInCWD(open_in_cwd) => {
2243+
if open_in_cwd != self.config.open_in_cwd {
2244+
self.config.open_in_cwd = open_in_cwd;
2245+
return self.update_config();
2246+
}
2247+
}
22142248
Message::ShowHeaderBar(show_headerbar) => {
22152249
if show_headerbar != self.config.show_headerbar {
22162250
config_set!(show_headerbar, show_headerbar);

src/terminal.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ use cosmic_text::{
2525
Weight, Wrap,
2626
};
2727
use indexmap::IndexSet;
28+
#[cfg(not(windows))]
29+
use rustix::fd::AsFd;
2830
use std::{
2931
borrow::Cow,
3032
collections::HashMap,
@@ -35,6 +37,8 @@ use std::{
3537
},
3638
time::Instant,
3739
};
40+
#[cfg(not(windows))]
41+
use std::{fs, path::PathBuf};
3842
use tokio::sync::mpsc;
3943

4044
pub use alacritty_terminal::grid::Scroll as TerminalScroll;
@@ -214,6 +218,10 @@ pub struct Terminal {
214218
size: Size,
215219
use_bright_bold: bool,
216220
zoom_adj: i8,
221+
#[cfg(not(windows))]
222+
master_fd: Option<rustix::fd::OwnedFd>,
223+
#[cfg(not(windows))]
224+
shell_pid: rustix::process::Pid,
217225
}
218226

219227
impl Terminal {
@@ -283,6 +291,11 @@ impl Terminal {
283291
let window_id = 0;
284292
let pty = tty::new(&options, size.into(), window_id)?;
285293

294+
#[cfg(not(windows))]
295+
let master_fd = pty.file().as_fd().try_clone_to_owned().ok();
296+
#[cfg(not(windows))]
297+
let shell_pid = rustix::process::Pid::from_child(pty.child());
298+
286299
let pty_event_loop = EventLoop::new(term.clone(), event_proxy, pty, options.hold, false)?;
287300
let notifier = Notifier(pty_event_loop.channel());
288301
let _pty_join_handle = pty_event_loop.spawn();
@@ -306,6 +319,10 @@ impl Terminal {
306319
term,
307320
use_bright_bold,
308321
zoom_adj: Default::default(),
322+
#[cfg(not(windows))]
323+
master_fd,
324+
#[cfg(not(windows))]
325+
shell_pid,
309326
})
310327
}
311328

@@ -924,6 +941,32 @@ impl Terminal {
924941
);
925942
}
926943
}
944+
945+
/// Current working directory
946+
#[cfg(not(windows))]
947+
pub fn current_working_directory(&self) -> Option<PathBuf> {
948+
// Largely based off of Alacritty
949+
// https://github.com/alacritty/alacritty/blob/6bd1674bd80e73df0d41e4342ad4e34bb7d04f84/alacritty/src/daemon.rs#L85-L108
950+
let pid = self
951+
.master_fd
952+
.as_ref()
953+
.and_then(|pid| rustix::termios::tcgetpgrp(pid).ok())
954+
.or(Some(self.shell_pid))?;
955+
956+
#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
957+
let link_path = format!("/proc/{}/cwd", pid.as_raw_nonzero());
958+
#[cfg(target_os = "freebsd")]
959+
let link_path = format!("/compat/linux/proc/{}/cwd", pid.as_raw_nonzero());
960+
961+
#[cfg(not(target_os = "macos"))]
962+
let cwd = fs::read_link(link_path).ok();
963+
964+
// TODO: macOS support
965+
#[cfg(target_os = "macos")]
966+
let cwd = None;
967+
968+
cwd
969+
}
927970
}
928971

929972
impl Drop for Terminal {

0 commit comments

Comments
 (0)