Skip to content

Commit

Permalink
feat: Open new terms in focused term's CWD
Browse files Browse the repository at this point in the history
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
  • Loading branch information
joshuamegnauth54 committed Sep 5, 2024
1 parent a71f0dc commit 43fbe1b
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ron = "0.8"
serde = { version = "1", features = ["serde_derive"] }
shlex = "1"
tokio = { version = "1", features = ["sync"] }
rustix = { version = "0.38", features = ["termios"] }
# Internationalization
i18n-embed = { version = "0.14", features = [
"fluent-system",
Expand Down
2 changes: 2 additions & 0 deletions i18n/en/cosmic_term.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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.
open-in-cwd = Use parent CWD
open-in-cwd-description = Start new terms using the focused tab's working directory.
# Find
find-placeholder = Find...
Expand Down
3 changes: 3 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ pub struct Config {
pub font_stretch: u16,
pub font_size_zoom_step_mul_100: u16,
pub opacity: u8,
/// Open new terminal with the current working directory of the focused term
pub open_in_cwd: bool,
pub profiles: BTreeMap<ProfileId, Profile>,
pub show_headerbar: bool,
pub use_bright_bold: bool,
Expand All @@ -253,6 +255,7 @@ impl Default for Config {
font_stretch: Stretch::Normal.to_number(),
font_weight: Weight::NORMAL.0,
opacity: 100,
open_in_cwd: true,
profiles: BTreeMap::new(),
show_headerbar: true,
syntax_theme_dark: COSMIC_THEME_DARK.to_string(),
Expand Down
50 changes: 42 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ pub enum Message {
ProfileSyntaxTheme(ProfileId, ColorSchemeKind, usize),
ProfileTabTitle(ProfileId, String),
SelectAll(Option<segmented_button::Entity>),
SetOpenInCWD(bool),
ShowAdvancedFontSettings(bool),
ShowHeaderBar(bool),
SyntaxTheme(ColorSchemeKind, usize),
Expand Down Expand Up @@ -1196,11 +1197,17 @@ impl App {
.toggler(self.config.focus_follow_mouse, Message::FocusFollowMouse),
);

let advanced_section = widget::settings::view_section(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::view_section(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!("open-in-cwd"))
.description(fl!("open-in-cwd-description"))
.toggler(self.config.open_in_cwd, Message::SetOpenInCWD),
);

widget::settings::view_column(vec![
appearance_section.into(),
Expand Down Expand Up @@ -1238,6 +1245,22 @@ impl App {
Some(colors) => {
let current_pane = self.pane_model.focus;
if let Some(tab_model) = self.pane_model.active_mut() {
// Current working directory of the selected tab/terminal
#[cfg(not(windows))]
let cwd = self
.config
.open_in_cwd
.then(|| {
tab_model.active_data::<Mutex<Terminal>>().and_then(
|terminal| {
terminal.lock().unwrap().current_working_directory()
},
)
})
.flatten();
#[cfg(windows)]
let cwd: Option<std::path::PathBuf> = None;

// Use the profile options, startup options, or defaults
let (options, tab_title_override) = match profile_id_opt
.and_then(|profile_id| self.config.profiles.get(&profile_id))
Expand All @@ -1250,8 +1273,8 @@ impl App {
shell = Some(tty::Shell::new(command, args));
}
}
let working_directory = (!profile.working_directory.is_empty())
.then(|| profile.working_directory.clone().into());
let working_directory = cwd
.or_else(|| Some(profile.working_directory.clone().into()));

let options = tty::Options {
shell,
Expand All @@ -1266,7 +1289,12 @@ impl App {
};
(options, tab_title_override)
}
None => (self.startup_options.take().unwrap_or_default(), None),
None => {
let mut options =
self.startup_options.take().unwrap_or_default();
options.working_directory = cwd;
(options, None)
}
};
let entity = tab_model
.insert()
Expand Down Expand Up @@ -2198,6 +2226,12 @@ impl Application for App {
}
return self.update_focus();
}
Message::SetOpenInCWD(open_in_cwd) => {
if open_in_cwd != self.config.open_in_cwd {
self.config.open_in_cwd = open_in_cwd;
return self.update_config();
}
}
Message::ShowHeaderBar(show_headerbar) => {
if show_headerbar != self.config.show_headerbar {
config_set!(show_headerbar, show_headerbar);
Expand Down
43 changes: 43 additions & 0 deletions src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use cosmic_text::{
Weight, Wrap,
};
use indexmap::IndexSet;
#[cfg(not(windows))]
use rustix::fd::AsFd;
use std::{
borrow::Cow,
collections::HashMap,
Expand All @@ -35,6 +37,8 @@ use std::{
},
time::Instant,
};
#[cfg(not(windows))]
use std::{fs, path::PathBuf};
use tokio::sync::mpsc;

pub use alacritty_terminal::grid::Scroll as TerminalScroll;
Expand Down Expand Up @@ -214,6 +218,10 @@ pub struct Terminal {
size: Size,
use_bright_bold: bool,
zoom_adj: i8,
#[cfg(not(windows))]
master_fd: Option<rustix::fd::OwnedFd>,
#[cfg(not(windows))]
shell_pid: rustix::process::Pid,
}

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

#[cfg(not(windows))]
let master_fd = pty.file().as_fd().try_clone_to_owned().ok();
#[cfg(not(windows))]
let shell_pid = rustix::process::Pid::from_child(pty.child());

let pty_event_loop = EventLoop::new(term.clone(), event_proxy, pty, options.hold, false)?;
let notifier = Notifier(pty_event_loop.channel());
let _pty_join_handle = pty_event_loop.spawn();
Expand All @@ -306,6 +319,10 @@ impl Terminal {
term,
use_bright_bold,
zoom_adj: Default::default(),
#[cfg(not(windows))]
master_fd,
#[cfg(not(windows))]
shell_pid,
})
}

Expand Down Expand Up @@ -924,6 +941,32 @@ impl Terminal {
);
}
}

/// Current working directory
#[cfg(not(windows))]
pub fn current_working_directory(&self) -> Option<PathBuf> {
// Largely based off of Alacritty
// https://github.com/alacritty/alacritty/blob/6bd1674bd80e73df0d41e4342ad4e34bb7d04f84/alacritty/src/daemon.rs#L85-L108
let pid = self
.master_fd
.as_ref()
.and_then(|pid| rustix::termios::tcgetpgrp(pid).ok())
.or(Some(self.shell_pid))?;

#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
let link_path = format!("/proc/{}/cwd", pid.as_raw_nonzero());
#[cfg(target_os = "freebsd")]
let link_path = format!("/compat/linux/proc/{}/cwd", pid.as_raw_nonzero());

#[cfg(not(target_os = "macos"))]
let cwd = fs::read_link(link_path).ok();

// TODO: macOS support
#[cfg(target_os = "macos")]
let cwd = None;

cwd
}
}

impl Drop for Terminal {
Expand Down

0 comments on commit 43fbe1b

Please sign in to comment.