fix: macOS local shells no longer run lsof synchronously on every Enter
The working-directory capture (used to restart a local shell where it left off) ran /usr/sbin/lsof inside write(), so each Enter in a macOS local tab waited for a subprocess. On macOS the lookup now runs on a background thread, triggered by the shell's next output after a line is entered, so it sees the directory after the command; a failed lookup keeps the last known directory. Linux keeps the cheap /proc readlink (F10). Not exercised on macOS in this environment.
This commit is contained in:
@@ -7,6 +7,7 @@ use portable_pty::{CommandBuilder, MasterPty, PtySize, native_pty_system};
|
|||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
pub fn default_shell() -> String {
|
pub fn default_shell() -> String {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
@@ -27,13 +28,23 @@ pub struct LocalShell {
|
|||||||
shell: String,
|
shell: String,
|
||||||
cb: Callbacks,
|
cb: Callbacks,
|
||||||
state: Mutex<(Option<Running>, PathBuf, u16, u16, bool)>, // running, cwd, cols, rows, closed
|
state: Mutex<(Option<Running>, PathBuf, u16, u16, bool)>, // running, cwd, cols, rows, closed
|
||||||
|
/// A line was entered since the last cwd lookup. On macOS the lookup shells out to lsof, too
|
||||||
|
/// slow for the write path, so it runs off-thread once the shell next prints something.
|
||||||
|
cwd_stale: AtomicBool,
|
||||||
|
capturing: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalShell {
|
impl LocalShell {
|
||||||
pub fn new(shell: &str, start_dir: Option<PathBuf>, cols: u16, rows: u16, cb: Callbacks) -> Arc<LocalShell> {
|
pub fn new(shell: &str, start_dir: Option<PathBuf>, cols: u16, rows: u16, cb: Callbacks) -> Arc<LocalShell> {
|
||||||
let shell = if shell.is_empty() { default_shell() } else { shell.to_string() };
|
let shell = if shell.is_empty() { default_shell() } else { shell.to_string() };
|
||||||
let dir = start_dir.filter(|d| d.is_dir()).or_else(home_dir).unwrap_or_else(|| PathBuf::from("/"));
|
let dir = start_dir.filter(|d| d.is_dir()).or_else(home_dir).unwrap_or_else(|| PathBuf::from("/"));
|
||||||
Arc::new(LocalShell { shell, cb, state: Mutex::new((None, dir, cols.max(1), rows.max(1), false)) })
|
Arc::new(LocalShell {
|
||||||
|
shell,
|
||||||
|
cb,
|
||||||
|
state: Mutex::new((None, dir, cols.max(1), rows.max(1), false)),
|
||||||
|
cwd_stale: Default::default(),
|
||||||
|
capturing: Default::default(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(self: &Arc<Self>) -> Result<()> {
|
pub fn start(self: &Arc<Self>) -> Result<()> {
|
||||||
@@ -70,7 +81,12 @@ impl LocalShell {
|
|||||||
loop {
|
loop {
|
||||||
match reader.read(&mut buf) {
|
match reader.read(&mut buf) {
|
||||||
Ok(0) | Err(_) => break,
|
Ok(0) | Err(_) => break,
|
||||||
Ok(n) => (me.cb.on_data)(&buf[..n]),
|
Ok(n) => {
|
||||||
|
(me.cb.on_data)(&buf[..n]);
|
||||||
|
if me.cwd_stale.swap(false, Ordering::SeqCst) {
|
||||||
|
me.capture_cwd_in_background();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
@@ -134,11 +150,30 @@ impl LocalShell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn capture_cwd(&self) {
|
fn capture_cwd(&self) {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
self.cwd_stale.store(true, Ordering::SeqCst);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let pid = self.state.lock().0.as_ref().and_then(|r| r.pid);
|
let pid = self.state.lock().0.as_ref().and_then(|r| r.pid);
|
||||||
if let Some(dir) = pid.and_then(process_cwd).filter(|d| d.is_dir()) {
|
if let Some(dir) = pid.and_then(process_cwd).filter(|d| d.is_dir()) {
|
||||||
self.state.lock().1 = dir;
|
self.state.lock().1 = dir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn capture_cwd_in_background(self: &Arc<Self>) {
|
||||||
|
let Some(pid) = self.state.lock().0.as_ref().and_then(|r| r.pid) else { return };
|
||||||
|
if self.capturing.swap(true, Ordering::SeqCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let me = self.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
// A failed lookup (the shell already exited) keeps the last known directory.
|
||||||
|
if let Some(dir) = process_cwd(pid).filter(|d| d.is_dir()) {
|
||||||
|
me.state.lock().1 = dir;
|
||||||
|
}
|
||||||
|
me.capturing.store(false, Ordering::SeqCst);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn home_dir() -> Option<PathBuf> {
|
fn home_dir() -> Option<PathBuf> {
|
||||||
|
|||||||
Reference in New Issue
Block a user