mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-21 16:01:04 +00:00
@@ -12,6 +12,7 @@
|
||||
- Relicensed Herdr from AGPL-3.0-or-later to Apache-2.0.
|
||||
|
||||
### Fixed
|
||||
- Pane applications now receive semantic light/dark query responses and live Mode 2031 updates when the host appearance changes. (#714)
|
||||
- Worktree CLI help and docs no longer advertise the redundant `--json` flag; worktree commands remain JSON-only and continue accepting the flag for compatibility. (#2171)
|
||||
- OpenCode 2 preview panes now appear as OpenCode agents and use the existing OpenCode status detection. (#2169)
|
||||
- Pane text copied through VS Code Remote Tunnels now reaches the viewing machine's clipboard instead of overwriting the remote host clipboard. (#2015)
|
||||
|
||||
@@ -3704,6 +3704,7 @@ mod tests {
|
||||
live_cwd.clone(),
|
||||
0,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin),
|
||||
&crate::pane::PaneLaunchEnv::default(),
|
||||
events,
|
||||
|
||||
@@ -239,6 +239,7 @@ impl App {
|
||||
cwd,
|
||||
self.state.pane_scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
self.state.host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&self.state.default_shell, self.state.shell_mode),
|
||||
&launch_env,
|
||||
self.event_tx.clone(),
|
||||
|
||||
@@ -545,6 +545,7 @@ impl App {
|
||||
cwd,
|
||||
self.state.pane_scrollback_limit_bytes,
|
||||
self.state.host_terminal_theme,
|
||||
self.state.host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&self.state.default_shell, self.state.shell_mode),
|
||||
&launch_env,
|
||||
self.event_tx.clone(),
|
||||
@@ -1712,6 +1713,7 @@ mod tests {
|
||||
live_cwd.clone(),
|
||||
0,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin),
|
||||
&crate::pane::PaneLaunchEnv::default(),
|
||||
events,
|
||||
@@ -1804,6 +1806,7 @@ mod tests {
|
||||
live_cwd.clone(),
|
||||
0,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin),
|
||||
&crate::pane::PaneLaunchEnv::default(),
|
||||
events,
|
||||
|
||||
@@ -93,6 +93,7 @@ impl App {
|
||||
let default_shell = self.state.default_shell.clone();
|
||||
let scrollback_limit_bytes = self.state.pane_scrollback_limit_bytes;
|
||||
let host_terminal_theme = self.state.host_terminal_theme;
|
||||
let host_terminal_appearance = self.state.host_terminal_appearance;
|
||||
let extra_env = match super::env::normalize_launch_env(root_leaf.env.clone()) {
|
||||
Ok(env) => env,
|
||||
Err((code, message)) => return encode_error(id, &code, message),
|
||||
@@ -115,6 +116,7 @@ impl App {
|
||||
extra_env,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
)
|
||||
} else {
|
||||
ws.create_tab(
|
||||
@@ -123,6 +125,7 @@ impl App {
|
||||
first_cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&default_shell, self.state.shell_mode),
|
||||
extra_env,
|
||||
)
|
||||
@@ -397,6 +400,7 @@ impl App {
|
||||
let default_shell = self.state.default_shell.clone();
|
||||
let scrollback_limit_bytes = self.state.pane_scrollback_limit_bytes;
|
||||
let host_terminal_theme = self.state.host_terminal_theme;
|
||||
let host_terminal_appearance = self.state.host_terminal_appearance;
|
||||
let cwd = pane
|
||||
.cwd
|
||||
.as_ref()
|
||||
@@ -425,6 +429,7 @@ impl App {
|
||||
extra_env,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
@@ -437,6 +442,7 @@ impl App {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&default_shell, self.state.shell_mode),
|
||||
extra_env,
|
||||
false,
|
||||
|
||||
@@ -57,6 +57,7 @@ impl App {
|
||||
let default_shell = self.state.default_shell.clone();
|
||||
let scrollback_limit_bytes = self.state.pane_scrollback_limit_bytes;
|
||||
let host_terminal_theme = self.state.host_terminal_theme;
|
||||
let host_terminal_appearance = self.state.host_terminal_appearance;
|
||||
let previous_focus = self.state.current_pane_focus_target();
|
||||
let Some(ws) = self.state.workspaces.get_mut(ws_idx) else {
|
||||
return encode_error(id, "pane_not_found", "pane not found");
|
||||
@@ -76,6 +77,7 @@ impl App {
|
||||
split_cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
extra_env,
|
||||
params.focus,
|
||||
@@ -88,6 +90,7 @@ impl App {
|
||||
split_cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
extra_env,
|
||||
params.focus,
|
||||
|
||||
@@ -126,6 +126,7 @@ impl App {
|
||||
extra_env,
|
||||
self.state.pane_scrollback_limit_bytes,
|
||||
self.state.host_terminal_theme,
|
||||
self.state.host_terminal_appearance,
|
||||
params.focus || placement == PluginPanePlacement::Zoomed,
|
||||
);
|
||||
let (tab_idx, new_pane) = match result {
|
||||
@@ -202,6 +203,7 @@ impl App {
|
||||
extra_env,
|
||||
self.state.pane_scrollback_limit_bytes,
|
||||
self.state.host_terminal_theme,
|
||||
self.state.host_terminal_appearance,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(err) => return encode_error(id, "plugin_pane_open_failed", err.to_string()),
|
||||
|
||||
@@ -69,6 +69,7 @@ impl App {
|
||||
let default_shell = self.state.default_shell.clone();
|
||||
let scrollback_limit_bytes = self.state.pane_scrollback_limit_bytes;
|
||||
let host_terminal_theme = self.state.host_terminal_theme;
|
||||
let host_terminal_appearance = self.state.host_terminal_appearance;
|
||||
let extra_env = match super::env::normalize_launch_env(env) {
|
||||
Ok(env) => env,
|
||||
Err((code, message)) => return encode_error(id, &code, message),
|
||||
@@ -85,6 +86,7 @@ impl App {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&default_shell, self.state.shell_mode),
|
||||
extra_env,
|
||||
)
|
||||
|
||||
@@ -196,6 +196,7 @@ impl App {
|
||||
initial_cwd,
|
||||
self.state.pane_scrollback_limit_bytes,
|
||||
self.state.host_terminal_theme,
|
||||
self.state.host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&self.state.default_shell, self.state.shell_mode),
|
||||
Vec::new(),
|
||||
)?;
|
||||
@@ -249,6 +250,7 @@ impl App {
|
||||
cols,
|
||||
self.state.pane_scrollback_limit_bytes,
|
||||
self.state.host_terminal_theme,
|
||||
self.state.host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&self.state.default_shell, self.state.shell_mode),
|
||||
self.event_tx.clone(),
|
||||
self.render_notify.clone(),
|
||||
|
||||
@@ -772,6 +772,7 @@ impl AppState {
|
||||
cwd,
|
||||
self.pane_scrollback_limit_bytes,
|
||||
self.host_terminal_theme,
|
||||
self.host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&self.default_shell, self.shell_mode),
|
||||
Vec::new(),
|
||||
) {
|
||||
|
||||
@@ -2945,6 +2945,7 @@ mod tests {
|
||||
80,
|
||||
app.state.pane_scrollback_limit_bytes,
|
||||
app.state.host_terminal_theme,
|
||||
app.state.host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&app.state.default_shell, app.state.shell_mode),
|
||||
app.event_tx.clone(),
|
||||
app.render_notify.clone(),
|
||||
@@ -3373,6 +3374,7 @@ mod tests {
|
||||
mouse_protocol_encoding: crate::input::MouseProtocolEncoding::Sgr,
|
||||
mouse_alternate_scroll: true,
|
||||
modify_other_keys: false,
|
||||
color_scheme_reporting: false,
|
||||
};
|
||||
|
||||
assert_eq!(wheel_routing(input_state), WheelRouting::MouseReport);
|
||||
@@ -4049,6 +4051,7 @@ mod tests {
|
||||
mouse_protocol_encoding: crate::input::MouseProtocolEncoding::Default,
|
||||
mouse_alternate_scroll: true,
|
||||
modify_other_keys: false,
|
||||
color_scheme_reporting: false,
|
||||
};
|
||||
|
||||
assert_eq!(wheel_routing(input_state), WheelRouting::AlternateScroll);
|
||||
@@ -4065,6 +4068,7 @@ mod tests {
|
||||
mouse_protocol_encoding: crate::input::MouseProtocolEncoding::Default,
|
||||
mouse_alternate_scroll: true,
|
||||
modify_other_keys: false,
|
||||
color_scheme_reporting: false,
|
||||
};
|
||||
|
||||
assert_eq!(wheel_routing(input_state), WheelRouting::HostScroll);
|
||||
|
||||
@@ -1005,6 +1005,7 @@ impl App {
|
||||
env,
|
||||
self.state.pane_scrollback_limit_bytes,
|
||||
self.state.host_terminal_theme,
|
||||
self.state.host_terminal_appearance,
|
||||
)?;
|
||||
let new_pane_id = new_pane.pane_id;
|
||||
self.terminal_runtimes
|
||||
@@ -1091,6 +1092,7 @@ impl App {
|
||||
extra_env,
|
||||
self.state.pane_scrollback_limit_bytes,
|
||||
self.state.host_terminal_theme,
|
||||
self.state.host_terminal_appearance,
|
||||
true,
|
||||
);
|
||||
let (tab_idx, new_pane) = match result {
|
||||
@@ -3282,6 +3284,7 @@ navigate_pane_down = "ctrl+j"
|
||||
80,
|
||||
app.state.pane_scrollback_limit_bytes,
|
||||
app.state.host_terminal_theme,
|
||||
app.state.host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&app.state.default_shell, app.state.shell_mode),
|
||||
app.event_tx.clone(),
|
||||
app.render_notify.clone(),
|
||||
|
||||
@@ -425,6 +425,7 @@ mod tests {
|
||||
80,
|
||||
app.state.pane_scrollback_limit_bytes,
|
||||
app.state.host_terminal_theme,
|
||||
app.state.host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new(&app.state.default_shell, app.state.shell_mode),
|
||||
app.event_tx.clone(),
|
||||
app.render_notify.clone(),
|
||||
|
||||
@@ -71,6 +71,7 @@ impl App {
|
||||
crate::pane::AgentDetection::Disabled,
|
||||
app.state.pane_scrollback_limit_bytes,
|
||||
app.state.host_terminal_theme,
|
||||
app.state.host_terminal_appearance,
|
||||
app.event_tx.clone(),
|
||||
app.render_notify.clone(),
|
||||
app.render_dirty.clone(),
|
||||
@@ -102,6 +103,7 @@ impl App {
|
||||
crate::pane::AgentDetection::Disabled,
|
||||
app.state.pane_scrollback_limit_bytes,
|
||||
app.state.host_terminal_theme,
|
||||
app.state.host_terminal_appearance,
|
||||
app.event_tx.clone(),
|
||||
app.render_notify.clone(),
|
||||
app.render_dirty.clone(),
|
||||
|
||||
@@ -50,6 +50,7 @@ impl App {
|
||||
}
|
||||
self.state.host_terminal_appearance = Some(appearance);
|
||||
self.state.host_terminal_appearance_explicit = explicit;
|
||||
self.apply_host_terminal_appearance_to_panes();
|
||||
self.refresh_effective_app_theme()
|
||||
}
|
||||
|
||||
@@ -65,6 +66,7 @@ impl App {
|
||||
}
|
||||
self.state.host_terminal_appearance = appearance;
|
||||
self.state.host_terminal_appearance_explicit = explicit;
|
||||
self.apply_host_terminal_appearance_to_panes();
|
||||
self.refresh_effective_app_theme()
|
||||
}
|
||||
|
||||
@@ -95,6 +97,12 @@ impl App {
|
||||
true
|
||||
}
|
||||
|
||||
fn apply_host_terminal_appearance_to_panes(&self) {
|
||||
for runtime in self.terminal_runtimes.values() {
|
||||
runtime.apply_host_terminal_appearance(self.state.host_terminal_appearance);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_host_terminal_theme_to_panes(&self) {
|
||||
for runtime in self.terminal_runtimes.values() {
|
||||
runtime.apply_host_terminal_theme(self.state.host_terminal_theme);
|
||||
|
||||
@@ -119,6 +119,21 @@ impl FocusEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ColorScheme {
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl ColorScheme {
|
||||
fn as_raw(self) -> ffi::GhosttyColorScheme {
|
||||
match self {
|
||||
Self::Light => ffi::GhosttyColorScheme_GHOSTTY_COLOR_SCHEME_LIGHT,
|
||||
Self::Dark => ffi::GhosttyColorScheme_GHOSTTY_COLOR_SCHEME_DARK,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const MOD_SHIFT: u16 = ffi::GHOSTTY_MODS_SHIFT as u16;
|
||||
pub const MOD_CTRL: u16 = ffi::GHOSTTY_MODS_CTRL as u16;
|
||||
pub const MOD_ALT: u16 = ffi::GHOSTTY_MODS_ALT as u16;
|
||||
@@ -163,6 +178,7 @@ pub const MODE_MOUSE_SGR_PIXELS: u16 = 1016;
|
||||
pub const MODE_BRACKETED_PASTE: u16 = 2004;
|
||||
pub const MODE_SYNCHRONIZED_OUTPUT: u16 = 2026;
|
||||
pub const MODE_GRAPHEME_CLUSTER: u16 = 2027;
|
||||
pub const MODE_COLOR_SCHEME_REPORT: u16 = 2031;
|
||||
// These are documented in vendor/libghostty-vt/include/ghostty/vt/terminal.h,
|
||||
// but the generated bindings do not currently expose named constants for them.
|
||||
const TERMINAL_DATA_COLOR_FOREGROUND: ffi::GhosttyTerminalData = 18;
|
||||
@@ -457,6 +473,25 @@ struct TerminalCallbackState {
|
||||
pwd_changes: Vec<Vec<u8>>,
|
||||
clipboard_writes: Vec<Vec<u8>>,
|
||||
size_report: ffi::GhosttySizeReportSize,
|
||||
color_scheme: Option<ColorScheme>,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn color_scheme_trampoline(
|
||||
_terminal: ffi::GhosttyTerminal,
|
||||
userdata: *mut c_void,
|
||||
out_scheme: *mut ffi::GhosttyColorScheme,
|
||||
) -> bool {
|
||||
if userdata.is_null() || out_scheme.is_null() {
|
||||
return false;
|
||||
}
|
||||
let state = unsafe { &*userdata.cast::<TerminalCallbackState>() };
|
||||
let Some(color_scheme) = state.color_scheme else {
|
||||
return false;
|
||||
};
|
||||
unsafe {
|
||||
out_scheme.write(color_scheme.as_raw());
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
unsafe extern "C" fn size_trampoline(
|
||||
@@ -791,6 +826,12 @@ impl Terminal {
|
||||
(clipboard_write_trampoline as *const ()).cast(),
|
||||
)
|
||||
.into_result()?;
|
||||
ffi::ghostty_terminal_set(
|
||||
terminal.raw,
|
||||
ffi::GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_COLOR_SCHEME,
|
||||
(color_scheme_trampoline as *const ()).cast(),
|
||||
)
|
||||
.into_result()?;
|
||||
ffi::ghostty_terminal_set(
|
||||
terminal.raw,
|
||||
ffi::GhosttyTerminalOption_GHOSTTY_TERMINAL_OPT_GLYPH_PROTOCOL,
|
||||
@@ -913,6 +954,10 @@ impl Terminal {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_color_scheme(&mut self, color_scheme: Option<ColorScheme>) -> Option<ColorScheme> {
|
||||
mem::replace(&mut self.callback_state.color_scheme, color_scheme)
|
||||
}
|
||||
|
||||
pub fn take_pwd_changes(&mut self) -> Vec<Vec<u8>> {
|
||||
mem::take(&mut self.callback_state.pwd_changes)
|
||||
}
|
||||
|
||||
+47
-1
@@ -1127,6 +1127,18 @@ impl PaneRuntimeIo {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_terminal_response(&self, response: impl FnOnce() -> Option<Bytes>) {
|
||||
match self {
|
||||
PaneRuntimeIo::Actor(actor) => actor.write_terminal_response(response),
|
||||
#[cfg(test)]
|
||||
PaneRuntimeIo::TestChannel { sender, .. } => {
|
||||
if let Some(bytes) = response() {
|
||||
let _ = sender.try_send(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_bytes_after(&self, bytes: Bytes, delay: std::time::Duration) {
|
||||
match self {
|
||||
PaneRuntimeIo::Actor(actor) => {
|
||||
@@ -1610,6 +1622,16 @@ impl PaneRuntime {
|
||||
self.terminal.apply_host_terminal_theme(theme);
|
||||
}
|
||||
|
||||
pub fn apply_host_terminal_appearance(
|
||||
&self,
|
||||
appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) {
|
||||
self.io
|
||||
.write_terminal_response(|| self.terminal.apply_host_terminal_appearance(appearance));
|
||||
}
|
||||
|
||||
// Runtime construction threads PTY geometry, host context, launch policy, and render hooks.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn spawn(
|
||||
pane_id: PaneId,
|
||||
rows: u16,
|
||||
@@ -1617,6 +1639,7 @@ impl PaneRuntime {
|
||||
cwd: std::path::PathBuf,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: PaneShellConfig<'_>,
|
||||
launch_env: &PaneLaunchEnv,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
@@ -1630,6 +1653,7 @@ impl PaneRuntime {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
launch_env,
|
||||
None,
|
||||
@@ -1648,6 +1672,7 @@ impl PaneRuntime {
|
||||
cwd: std::path::PathBuf,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: PaneShellConfig<'_>,
|
||||
launch_env: &PaneLaunchEnv,
|
||||
initial_history_ansi: Option<&str>,
|
||||
@@ -1667,6 +1692,7 @@ impl PaneRuntime {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
events,
|
||||
render_notify,
|
||||
render_dirty,
|
||||
@@ -1693,6 +1719,7 @@ impl PaneRuntime {
|
||||
agent_detection: AgentDetection,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -1707,6 +1734,7 @@ impl PaneRuntime {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
events,
|
||||
render_notify,
|
||||
render_dirty,
|
||||
@@ -1729,6 +1757,7 @@ impl PaneRuntime {
|
||||
agent_detection: AgentDetection,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -1752,6 +1781,7 @@ impl PaneRuntime {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
events,
|
||||
render_notify,
|
||||
render_dirty,
|
||||
@@ -1767,6 +1797,7 @@ impl PaneRuntime {
|
||||
import: crate::handoff_runtime::ImportedHandoffRuntime,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -1803,6 +1834,7 @@ impl PaneRuntime {
|
||||
}
|
||||
let pane_terminal = GhosttyPaneTerminal::new(terminal, response_tx.clone())?;
|
||||
pane_terminal.apply_host_terminal_theme(host_terminal_theme);
|
||||
let _ = pane_terminal.apply_host_terminal_appearance(host_terminal_appearance);
|
||||
pane_terminal.seed_terminal_title(terminal_title);
|
||||
if let Some(input_state) = input_state {
|
||||
pane_terminal.seed_handoff_input_state(input_state);
|
||||
@@ -1916,6 +1948,7 @@ impl PaneRuntime {
|
||||
cols: u16,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -1936,6 +1969,7 @@ impl PaneRuntime {
|
||||
}
|
||||
let pane_terminal = GhosttyPaneTerminal::new(terminal, response_tx.clone())?;
|
||||
pane_terminal.apply_host_terminal_theme(host_terminal_theme);
|
||||
let _ = pane_terminal.apply_host_terminal_appearance(host_terminal_appearance);
|
||||
pane_terminal.set_windows_powershell_prompt_cwd_reporting(
|
||||
initial_state.windows_powershell_prompt_cwd_reporting,
|
||||
);
|
||||
@@ -3393,7 +3427,7 @@ mod tests {
|
||||
let runtime = PaneRuntime::test_with_screen_bytes(
|
||||
80,
|
||||
24,
|
||||
b"\x1b[>5u\x1b[>4;2m\x1b[?1h\x1b[?2004h\x1b[?1004h\x1b[?1002h\x1b[?1006h",
|
||||
b"\x1b[>5u\x1b[>4;2m\x1b[?1h\x1b[?2004h\x1b[?1004h\x1b[?1002h\x1b[?1006h\x1b[?2031h",
|
||||
);
|
||||
|
||||
runtime.test_process_pty_bytes("\x1b]2;✳ 修复🙂标题\x1b\\".as_bytes());
|
||||
@@ -3414,6 +3448,7 @@ mod tests {
|
||||
mouse_protocol_encoding: crate::input::MouseProtocolEncoding::Sgr,
|
||||
mouse_alternate_scroll: true,
|
||||
modify_other_keys: true,
|
||||
color_scheme_reporting: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -3508,6 +3543,17 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn subscribed_idle_child_receives_color_scheme_transition() {
|
||||
let (runtime, mut rx) = PaneRuntime::test_with_channel(80, 24);
|
||||
runtime.apply_host_terminal_appearance(Some(crate::terminal_theme::HostAppearance::Dark));
|
||||
runtime.test_process_pty_bytes(b"\x1b[?2031h");
|
||||
|
||||
runtime.apply_host_terminal_appearance(Some(crate::terminal_theme::HostAppearance::Light));
|
||||
|
||||
assert_eq!(rx.recv().await, Some(Bytes::from_static(b"\x1b[?997;2n")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foreground_shell_reports_process_exit_before_clearing_agent() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -120,6 +120,8 @@ pub struct InputState {
|
||||
pub mouse_alternate_scroll: bool,
|
||||
#[serde(default)]
|
||||
pub modify_other_keys: bool,
|
||||
#[serde(default)]
|
||||
pub color_scheme_reporting: bool,
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
@@ -458,6 +460,13 @@ impl PaneTerminal {
|
||||
self.ghostty.apply_host_terminal_theme(theme);
|
||||
}
|
||||
|
||||
pub fn apply_host_terminal_appearance(
|
||||
&self,
|
||||
appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) -> Option<Bytes> {
|
||||
self.ghostty.apply_host_terminal_appearance(appearance)
|
||||
}
|
||||
|
||||
pub fn has_transient_default_color_override(&self) -> bool {
|
||||
self.ghostty.has_transient_default_color_override()
|
||||
}
|
||||
@@ -988,6 +997,32 @@ impl GhosttyPaneTerminal {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_host_terminal_appearance(
|
||||
&self,
|
||||
appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) -> Option<Bytes> {
|
||||
let mut core = self.core.lock().ok()?;
|
||||
let color_scheme = appearance.map(|appearance| match appearance {
|
||||
crate::terminal_theme::HostAppearance::Dark => crate::ghostty::ColorScheme::Dark,
|
||||
crate::terminal_theme::HostAppearance::Light => crate::ghostty::ColorScheme::Light,
|
||||
});
|
||||
let previous = core.terminal.set_color_scheme(color_scheme);
|
||||
|
||||
let transitioned = matches!(
|
||||
(previous, color_scheme),
|
||||
(Some(previous), Some(current)) if previous != current
|
||||
);
|
||||
if !transitioned
|
||||
|| !core
|
||||
.terminal
|
||||
.mode_get(crate::ghostty::MODE_COLOR_SCHEME_REPORT)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
appearance.map(|appearance| Bytes::from_static(appearance.color_scheme_report()))
|
||||
}
|
||||
|
||||
pub fn has_transient_default_color_override(&self) -> bool {
|
||||
self.core
|
||||
.lock()
|
||||
@@ -1331,6 +1366,10 @@ impl GhosttyPaneTerminal {
|
||||
crate::ghostty::MODE_MOUSE_ALTERNATE_SCROLL,
|
||||
input_state.mouse_alternate_scroll,
|
||||
);
|
||||
let _ = core.terminal.mode_set(
|
||||
crate::ghostty::MODE_COLOR_SCHEME_REPORT,
|
||||
input_state.color_scheme_reporting,
|
||||
);
|
||||
|
||||
for mode in [
|
||||
MODE_MOUSE_X10,
|
||||
@@ -1585,6 +1624,10 @@ impl GhosttyPaneTerminal {
|
||||
.keyboard_state_ansi()
|
||||
.ok()
|
||||
.is_some_and(|ansi| !ansi.is_empty()),
|
||||
color_scheme_reporting: core
|
||||
.terminal
|
||||
.mode_get(crate::ghostty::MODE_COLOR_SCHEME_REPORT)
|
||||
.ok()?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3907,6 +3950,7 @@ mod tests {
|
||||
mouse_protocol_encoding: crate::input::MouseProtocolEncoding::Sgr,
|
||||
mouse_alternate_scroll: true,
|
||||
modify_other_keys: true,
|
||||
color_scheme_reporting: true,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
@@ -3920,6 +3964,7 @@ mod tests {
|
||||
mouse_protocol_encoding: crate::input::MouseProtocolEncoding::Sgr,
|
||||
mouse_alternate_scroll: true,
|
||||
modify_other_keys: true,
|
||||
color_scheme_reporting: true,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -5009,6 +5054,45 @@ mod tests {
|
||||
assert!(rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_scheme_queries_and_live_updates_follow_terminal_mode() {
|
||||
let (tx, mut rx) = mpsc::channel(4);
|
||||
let terminal = crate::ghostty::Terminal::new(20, 5, 0).unwrap();
|
||||
let pane = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap();
|
||||
let pane_id = PaneId::from_raw(1);
|
||||
|
||||
assert!(pane
|
||||
.apply_host_terminal_appearance(Some(crate::terminal_theme::HostAppearance::Dark))
|
||||
.is_none());
|
||||
let query = pane.process_pty_bytes(pane_id, 0, b"\x1b[?996n", &tx);
|
||||
assert_eq!(
|
||||
query.terminal_responses,
|
||||
vec![Bytes::from_static(b"\x1b[?997;1n")]
|
||||
);
|
||||
|
||||
pane.process_pty_bytes(pane_id, 0, b"\x1b[?2031h", &tx);
|
||||
assert!(pane
|
||||
.apply_host_terminal_appearance(Some(crate::terminal_theme::HostAppearance::Dark))
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
pane.apply_host_terminal_appearance(Some(crate::terminal_theme::HostAppearance::Light)),
|
||||
Some(Bytes::from_static(b"\x1b[?997;2n"))
|
||||
);
|
||||
|
||||
assert!(pane.apply_host_terminal_appearance(None).is_none());
|
||||
let unknown_query = pane.process_pty_bytes(pane_id, 0, b"\x1b[?996n", &tx);
|
||||
assert!(unknown_query.terminal_responses.is_empty());
|
||||
assert!(pane
|
||||
.apply_host_terminal_appearance(Some(crate::terminal_theme::HostAppearance::Dark))
|
||||
.is_none());
|
||||
|
||||
pane.process_pty_bytes(pane_id, 0, b"\x1bc", &tx);
|
||||
assert!(pane
|
||||
.apply_host_terminal_appearance(Some(crate::terminal_theme::HostAppearance::Light))
|
||||
.is_none());
|
||||
assert!(rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_pty_bytes_returns_xtgettcap_truecolor_query_responses_without_queuing_input() {
|
||||
let (tx, mut rx) = mpsc::channel(8);
|
||||
|
||||
@@ -582,6 +582,7 @@ fn restore_tab(
|
||||
},
|
||||
runtime_context.scrollback_limit_bytes,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
runtime_context.events.clone(),
|
||||
runtime_context.render_notify.clone(),
|
||||
runtime_context.render_dirty.clone(),
|
||||
@@ -594,6 +595,7 @@ fn restore_tab(
|
||||
cwd.clone(),
|
||||
runtime_context.scrollback_limit_bytes,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
runtime_context.shell_config,
|
||||
&launch_env,
|
||||
startup.initial_history_ansi,
|
||||
@@ -612,6 +614,7 @@ fn restore_tab(
|
||||
cwd.clone(),
|
||||
runtime_context.scrollback_limit_bytes,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
runtime_context.shell_config,
|
||||
&launch_env,
|
||||
startup.initial_history_ansi,
|
||||
|
||||
+47
-26
@@ -52,6 +52,8 @@ mod windows {
|
||||
pub(crate) struct PtyIoActorHandle {
|
||||
data_tx: mpsc::Sender<Bytes>,
|
||||
control_tx: std_mpsc::Sender<PtyIoControlCommand>,
|
||||
write_tx: std_mpsc::Sender<Bytes>,
|
||||
response_order: Arc<Mutex<()>>,
|
||||
accepting: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
@@ -84,6 +86,16 @@ mod windows {
|
||||
self.data_tx.try_send(bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn write_terminal_response(&self, response: impl FnOnce() -> Option<Bytes>) {
|
||||
let _order = self
|
||||
.response_order
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
if let Some(bytes) = response().filter(|bytes| !bytes.is_empty()) {
|
||||
let _ = self.write_tx.send(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resize(
|
||||
&self,
|
||||
rows: u16,
|
||||
@@ -128,39 +140,55 @@ mod windows {
|
||||
let mut reader = master
|
||||
.try_clone_reader()
|
||||
.map_err(|err| std::io::Error::other(err.to_string()))?;
|
||||
let writer = master
|
||||
let mut writer = master
|
||||
.take_writer()
|
||||
.map_err(|err| std::io::Error::other(err.to_string()))?;
|
||||
let writer = Arc::new(Mutex::new(writer));
|
||||
let (data_tx, mut data_rx) = mpsc::channel::<Bytes>(1024);
|
||||
let (control_tx, control_rx) = std_mpsc::channel::<PtyIoControlCommand>();
|
||||
let (write_tx, write_rx) = std_mpsc::channel::<Bytes>();
|
||||
let response_order = Arc::new(Mutex::new(()));
|
||||
let accepting = Arc::new(Mutex::new(!initially_quiesced));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for bytes in write_rx {
|
||||
if writer.write_all(&bytes).is_err() || writer.flush().is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug!(pane_id, "windows pty writer thread exiting");
|
||||
});
|
||||
|
||||
{
|
||||
let writer = Arc::clone(&writer);
|
||||
let write_tx = write_tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
while let Some(bytes) = data_rx.blocking_recv() {
|
||||
if write_all_locked(&writer, &bytes).is_err() {
|
||||
if write_tx.send(bytes).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug!(pane_id, "windows pty writer thread exiting");
|
||||
debug!(pane_id, "windows pty input thread exiting");
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let writer = Arc::clone(&writer);
|
||||
let write_tx = write_tx.clone();
|
||||
let response_order = Arc::clone(&response_order);
|
||||
std::thread::spawn(move || {
|
||||
let mut buf = [0u8; 8192];
|
||||
loop {
|
||||
match reader.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
let _order = response_order
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let result = on_read(&buf[..n]);
|
||||
for response in result.terminal_responses {
|
||||
if write_all_locked(&writer, &response).is_err() {
|
||||
break;
|
||||
}
|
||||
if result
|
||||
.terminal_responses
|
||||
.into_iter()
|
||||
.any(|response| write_tx.send(response).is_err())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -177,7 +205,7 @@ mod windows {
|
||||
}
|
||||
|
||||
{
|
||||
let writer = Arc::clone(&writer);
|
||||
let write_tx = write_tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
for command in control_rx {
|
||||
match command {
|
||||
@@ -191,10 +219,12 @@ mod windows {
|
||||
}) {
|
||||
warn!(pane_id, err = %err, "windows pty resize failed");
|
||||
}
|
||||
for response in request.terminal_responses {
|
||||
if write_all_locked(&writer, &response).is_err() {
|
||||
break;
|
||||
}
|
||||
if request
|
||||
.terminal_responses
|
||||
.into_iter()
|
||||
.any(|response| write_tx.send(response).is_err())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
PtyIoControlCommand::Shutdown => break,
|
||||
@@ -207,22 +237,13 @@ mod windows {
|
||||
Ok(PtyIoActorHandle {
|
||||
data_tx,
|
||||
control_tx,
|
||||
write_tx,
|
||||
response_order,
|
||||
accepting,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn write_all_locked(
|
||||
writer: &Arc<Mutex<Box<dyn Write + Send>>>,
|
||||
bytes: &[u8],
|
||||
) -> std::io::Result<()> {
|
||||
let mut writer = writer
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
writer.write_all(bytes)?;
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn _assert_duration_send(_: Duration) {}
|
||||
}
|
||||
|
||||
+135
-3
@@ -60,6 +60,7 @@ struct PtyResizeRequest {
|
||||
struct SharedPtyControls {
|
||||
resize: Option<PtyResizeRequest>,
|
||||
nudge: Option<PtyResize>,
|
||||
terminal_responses: Vec<Bytes>,
|
||||
}
|
||||
|
||||
pub(crate) struct PtyIoActorConfig {
|
||||
@@ -90,6 +91,7 @@ pub(crate) struct PtyIoActorHandle {
|
||||
wake: fd::WakeWriter,
|
||||
user_writes: Arc<Mutex<UserWriteGate>>,
|
||||
controls: Arc<Mutex<SharedPtyControls>>,
|
||||
response_order: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -157,6 +159,24 @@ impl PtyIoActorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_terminal_response(&self, response: impl FnOnce() -> Option<Bytes>) {
|
||||
let _order = self
|
||||
.response_order
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let Some(bytes) = response() else {
|
||||
return;
|
||||
};
|
||||
if !bytes.is_empty() {
|
||||
self.controls
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
.terminal_responses
|
||||
.push(bytes);
|
||||
self.wake_actor();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resize(
|
||||
&self,
|
||||
rows: u16,
|
||||
@@ -349,12 +369,14 @@ impl PtyIoActor {
|
||||
accepting: !config.initially_quiesced,
|
||||
}));
|
||||
let controls = Arc::new(Mutex::new(SharedPtyControls::default()));
|
||||
let response_order = Arc::new(Mutex::new(()));
|
||||
let handle = PtyIoActorHandle {
|
||||
data_tx,
|
||||
control_tx,
|
||||
wake: wake_pipe.writer,
|
||||
user_writes,
|
||||
controls: Arc::clone(&controls),
|
||||
response_order: Arc::clone(&response_order),
|
||||
};
|
||||
|
||||
let mut runner = PtyIoActorRunner {
|
||||
@@ -371,6 +393,7 @@ impl PtyIoActor {
|
||||
current_write_offset: 0,
|
||||
wake_read_fd: wake_pipe.read_fd,
|
||||
controls,
|
||||
response_order,
|
||||
on_read: config.on_read,
|
||||
on_reader_exit: config.on_reader_exit,
|
||||
poll_observer,
|
||||
@@ -402,6 +425,7 @@ struct PtyIoActorRunner {
|
||||
current_write_offset: usize,
|
||||
wake_read_fd: OwnedFd,
|
||||
controls: Arc<Mutex<SharedPtyControls>>,
|
||||
response_order: Arc<Mutex<()>>,
|
||||
on_read: ReadCallback,
|
||||
on_reader_exit: Option<ReaderExitCallback>,
|
||||
poll_observer: Option<std_mpsc::Sender<()>>,
|
||||
@@ -625,12 +649,16 @@ impl PtyIoActorRunner {
|
||||
}
|
||||
|
||||
fn apply_pending_controls(&mut self) {
|
||||
let (resize, nudge) = {
|
||||
let (resize, nudge, terminal_responses) = {
|
||||
let mut controls = self
|
||||
.controls
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
(controls.resize.take(), controls.nudge.take())
|
||||
(
|
||||
controls.resize.take(),
|
||||
controls.nudge.take(),
|
||||
std::mem::take(&mut controls.terminal_responses),
|
||||
)
|
||||
};
|
||||
if self.state == ActorState::Released {
|
||||
return;
|
||||
@@ -642,6 +670,7 @@ impl PtyIoActorRunner {
|
||||
if let Some(nudge) = nudge {
|
||||
self.nudge(nudge);
|
||||
}
|
||||
self.enqueue_terminal_responses(terminal_responses);
|
||||
}
|
||||
|
||||
fn read_once(&mut self) -> bool {
|
||||
@@ -655,8 +684,25 @@ impl PtyIoActorRunner {
|
||||
false
|
||||
}
|
||||
Ok(n) => {
|
||||
let response_order = Arc::clone(&self.response_order);
|
||||
let _order = response_order
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let result = (self.on_read)(&buf[..n]);
|
||||
self.enqueue_terminal_responses(result.terminal_responses);
|
||||
self.controls
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
.terminal_responses
|
||||
.extend(result.terminal_responses);
|
||||
drop(_order);
|
||||
let terminal_responses = std::mem::take(
|
||||
&mut self
|
||||
.controls
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
.terminal_responses,
|
||||
);
|
||||
self.enqueue_terminal_responses(terminal_responses);
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -769,6 +815,7 @@ mod tests {
|
||||
io::{Read, Write},
|
||||
os::fd::{AsRawFd, FromRawFd, IntoRawFd},
|
||||
os::unix::net::UnixStream,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
fn test_wake_pair() -> (fd::WakeWriter, OwnedFd) {
|
||||
@@ -834,6 +881,7 @@ mod tests {
|
||||
current_write_offset: 0,
|
||||
wake_read_fd: wake_pipe.read_fd,
|
||||
controls: Arc::new(Mutex::new(SharedPtyControls::default())),
|
||||
response_order: Arc::new(Mutex::new(())),
|
||||
on_read: Box::new(|_| PtyReadResult::empty()),
|
||||
on_reader_exit: None,
|
||||
poll_observer: None,
|
||||
@@ -1099,11 +1147,13 @@ mod tests {
|
||||
wake,
|
||||
user_writes: Arc::new(Mutex::new(UserWriteGate { accepting: true })),
|
||||
controls: Arc::clone(&controls),
|
||||
response_order: Arc::new(Mutex::new(())),
|
||||
};
|
||||
|
||||
handle.resize(20, 80, 8, 16, vec![Bytes::from_static(b"old")]);
|
||||
handle.resize(40, 120, 9, 18, vec![Bytes::from_static(b"new")]);
|
||||
handle.nudge_child_redraw_after_handoff(41, 121, 10, 20);
|
||||
handle.write_terminal_response(|| Some(Bytes::from_static(b"response")));
|
||||
|
||||
let controls = controls.lock().expect("controls lock");
|
||||
assert_eq!(
|
||||
@@ -1127,6 +1177,84 @@ mod tests {
|
||||
cell_height_px: 20,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
controls.terminal_responses,
|
||||
vec![Bytes::from_static(b"response")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appearance_transition_report_precedes_query_of_new_scheme() {
|
||||
let (actor_socket, mut peer) = UnixStream::pair().expect("socket pair");
|
||||
actor_socket
|
||||
.set_nonblocking(true)
|
||||
.expect("actor socket nonblocking");
|
||||
let owned = unsafe { OwnedFd::from_raw_fd(actor_socket.into_raw_fd()) };
|
||||
let (data_tx, data_rx) = mpsc::channel(ACTOR_COMMAND_BUFFER);
|
||||
let (control_tx, control_rx) = std_mpsc::channel();
|
||||
let wake_pipe = fd::create_wake_pipe().expect("wake pipe");
|
||||
let controls = Arc::new(Mutex::new(SharedPtyControls::default()));
|
||||
let response_order = Arc::new(Mutex::new(()));
|
||||
let light = Arc::new(AtomicBool::new(false));
|
||||
let query_light = Arc::clone(&light);
|
||||
let runner = PtyIoActorRunner {
|
||||
pane_id: 1,
|
||||
file: std::fs::File::from(owned),
|
||||
data_rx,
|
||||
control_rx,
|
||||
state: ActorState::Running,
|
||||
pending_writes: VecDeque::new(),
|
||||
current_write_offset: 0,
|
||||
wake_read_fd: wake_pipe.read_fd,
|
||||
controls: Arc::clone(&controls),
|
||||
response_order: Arc::clone(&response_order),
|
||||
on_read: Box::new(move |_| PtyReadResult {
|
||||
terminal_responses: vec![if query_light.load(Ordering::Acquire) {
|
||||
Bytes::from_static(b"query-light")
|
||||
} else {
|
||||
Bytes::from_static(b"query-dark")
|
||||
}],
|
||||
}),
|
||||
on_reader_exit: None,
|
||||
poll_observer: None,
|
||||
};
|
||||
let handle = PtyIoActorHandle {
|
||||
data_tx,
|
||||
control_tx,
|
||||
wake: wake_pipe.writer,
|
||||
user_writes: Arc::new(Mutex::new(UserWriteGate { accepting: true })),
|
||||
controls,
|
||||
response_order,
|
||||
};
|
||||
let (changed_tx, changed_rx) = std_mpsc::channel();
|
||||
let (continue_tx, continue_rx) = std_mpsc::channel();
|
||||
|
||||
let appearance = std::thread::spawn(move || {
|
||||
handle.write_terminal_response(|| {
|
||||
light.store(true, Ordering::Release);
|
||||
changed_tx.send(()).expect("notify appearance change");
|
||||
continue_rx.recv().expect("continue appearance report");
|
||||
Some(Bytes::from_static(b"live-light"))
|
||||
});
|
||||
});
|
||||
changed_rx.recv().expect("appearance changed");
|
||||
peer.write_all(b"query").expect("write query");
|
||||
let reader = std::thread::spawn(move || {
|
||||
let mut runner = runner;
|
||||
assert!(runner.read_once());
|
||||
runner
|
||||
});
|
||||
continue_tx.send(()).expect("release appearance report");
|
||||
appearance.join().expect("appearance thread joins");
|
||||
let runner = reader.join().expect("reader thread joins");
|
||||
|
||||
assert_eq!(
|
||||
runner.pending_writes,
|
||||
VecDeque::from([
|
||||
Bytes::from_static(b"live-light"),
|
||||
Bytes::from_static(b"query-light"),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1159,6 +1287,7 @@ mod tests {
|
||||
wake,
|
||||
user_writes: Arc::new(Mutex::new(UserWriteGate { accepting: true })),
|
||||
controls: Arc::new(Mutex::new(SharedPtyControls::default())),
|
||||
response_order: Arc::new(Mutex::new(())),
|
||||
};
|
||||
|
||||
let write = tokio::spawn(async move {
|
||||
@@ -1204,6 +1333,7 @@ mod tests {
|
||||
wake,
|
||||
user_writes: Arc::new(Mutex::new(UserWriteGate { accepting: true })),
|
||||
controls: Arc::new(Mutex::new(SharedPtyControls::default())),
|
||||
response_order: Arc::new(Mutex::new(())),
|
||||
};
|
||||
let write_handle = handle.clone();
|
||||
let write = tokio::spawn(async move {
|
||||
@@ -1258,6 +1388,7 @@ mod tests {
|
||||
wake,
|
||||
user_writes: Arc::new(Mutex::new(UserWriteGate { accepting: true })),
|
||||
controls: Arc::new(Mutex::new(SharedPtyControls::default())),
|
||||
response_order: Arc::new(Mutex::new(())),
|
||||
};
|
||||
|
||||
let handoff = std::thread::spawn(move || handle.begin_handoff(Duration::from_secs(1)));
|
||||
@@ -1302,6 +1433,7 @@ mod tests {
|
||||
current_write_offset: 0,
|
||||
wake_read_fd: fd::create_wake_pipe().expect("wake pipe").read_fd,
|
||||
controls: Arc::new(Mutex::new(SharedPtyControls::default())),
|
||||
response_order: Arc::new(Mutex::new(())),
|
||||
on_read: Box::new(|_| PtyReadResult::empty()),
|
||||
on_reader_exit: None,
|
||||
poll_observer: None,
|
||||
|
||||
@@ -120,6 +120,7 @@ mod tests {
|
||||
live_cwd.clone(),
|
||||
0,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin),
|
||||
&crate::pane::PaneLaunchEnv::default(),
|
||||
events,
|
||||
|
||||
@@ -64,6 +64,7 @@ impl TerminalRuntime {
|
||||
import: crate::handoff_runtime::ImportedHandoffRuntime,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -72,6 +73,7 @@ impl TerminalRuntime {
|
||||
import,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
events,
|
||||
render_notify,
|
||||
render_dirty,
|
||||
@@ -79,6 +81,8 @@ impl TerminalRuntime {
|
||||
.map(Self)
|
||||
}
|
||||
|
||||
// Wrapper mirrors pane runtime construction arguments.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn spawn(
|
||||
pane_id: PaneId,
|
||||
rows: u16,
|
||||
@@ -86,6 +90,7 @@ impl TerminalRuntime {
|
||||
cwd: std::path::PathBuf,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
launch_env: &crate::pane::PaneLaunchEnv,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
@@ -99,6 +104,7 @@ impl TerminalRuntime {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
launch_env,
|
||||
events,
|
||||
@@ -117,6 +123,7 @@ impl TerminalRuntime {
|
||||
cwd: std::path::PathBuf,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
launch_env: &crate::pane::PaneLaunchEnv,
|
||||
initial_history_ansi: Option<&str>,
|
||||
@@ -131,6 +138,7 @@ impl TerminalRuntime {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
launch_env,
|
||||
initial_history_ansi,
|
||||
@@ -153,6 +161,7 @@ impl TerminalRuntime {
|
||||
agent_detection: crate::pane::AgentDetection,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -167,6 +176,7 @@ impl TerminalRuntime {
|
||||
agent_detection,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
events,
|
||||
render_notify,
|
||||
render_dirty,
|
||||
@@ -186,6 +196,7 @@ impl TerminalRuntime {
|
||||
agent_detection: crate::pane::AgentDetection,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -200,6 +211,7 @@ impl TerminalRuntime {
|
||||
agent_detection,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
events,
|
||||
render_notify,
|
||||
render_dirty,
|
||||
@@ -211,6 +223,13 @@ impl TerminalRuntime {
|
||||
self.0.apply_host_terminal_theme(theme);
|
||||
}
|
||||
|
||||
pub fn apply_host_terminal_appearance(
|
||||
&self,
|
||||
appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) {
|
||||
self.0.apply_host_terminal_appearance(appearance);
|
||||
}
|
||||
|
||||
pub fn begin_graceful_release(&self, agent: crate::detect::Agent) {
|
||||
self.0.begin_graceful_release(agent);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,15 @@ pub enum HostAppearance {
|
||||
Light,
|
||||
}
|
||||
|
||||
impl HostAppearance {
|
||||
pub const fn color_scheme_report(self) -> &'static [u8] {
|
||||
match self {
|
||||
Self::Dark => b"\x1b[?997;1n",
|
||||
Self::Light => b"\x1b[?997;2n",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RgbColor {
|
||||
pub fn inferred_appearance(self) -> HostAppearance {
|
||||
let luminance = u32::from(self.r) * 299 + u32::from(self.g) * 587 + u32::from(self.b) * 114;
|
||||
|
||||
@@ -827,6 +827,7 @@ mod tests {
|
||||
live_cwd,
|
||||
0,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin),
|
||||
&crate::pane::PaneLaunchEnv::default(),
|
||||
events,
|
||||
|
||||
@@ -1473,6 +1473,7 @@ mod tests {
|
||||
live_cwd.clone(),
|
||||
0,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin),
|
||||
&crate::pane::PaneLaunchEnv::default(),
|
||||
events,
|
||||
|
||||
@@ -2247,6 +2247,7 @@ rows = [[{ token = "git_status", fg = "#123456" }]]
|
||||
live_cwd.clone(),
|
||||
0,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin),
|
||||
&crate::pane::PaneLaunchEnv::default(),
|
||||
events,
|
||||
|
||||
@@ -276,6 +276,7 @@ impl Workspace {
|
||||
cols: u16,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
@@ -287,6 +288,7 @@ impl Workspace {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
events,
|
||||
render_notify,
|
||||
@@ -302,6 +304,7 @@ impl Workspace {
|
||||
cols: u16,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
@@ -314,6 +317,7 @@ impl Workspace {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
events,
|
||||
render_notify,
|
||||
@@ -332,6 +336,7 @@ impl Workspace {
|
||||
argv: &[String],
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -343,6 +348,7 @@ impl Workspace {
|
||||
argv,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
events,
|
||||
render_notify,
|
||||
render_dirty,
|
||||
@@ -358,6 +364,7 @@ impl Workspace {
|
||||
argv: &[String],
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
render_dirty: Arc<RenderSignal>,
|
||||
@@ -369,6 +376,7 @@ impl Workspace {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new("", crate::config::ShellModeConfig::NonLogin),
|
||||
events,
|
||||
render_notify,
|
||||
@@ -385,6 +393,7 @@ impl Workspace {
|
||||
cols: u16,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
@@ -407,6 +416,7 @@ impl Workspace {
|
||||
argv,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
&launch_env,
|
||||
events,
|
||||
render_notify,
|
||||
@@ -420,6 +430,7 @@ impl Workspace {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
&launch_env,
|
||||
events,
|
||||
@@ -501,6 +512,7 @@ impl Workspace {
|
||||
cwd: PathBuf,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
extra_env: Vec<(String, String)>,
|
||||
) -> std::io::Result<(usize, TerminalState, TerminalRuntime)> {
|
||||
@@ -510,6 +522,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
None,
|
||||
extra_env,
|
||||
@@ -525,6 +538,7 @@ impl Workspace {
|
||||
extra_env: Vec<(String, String)>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) -> std::io::Result<(usize, TerminalState, TerminalRuntime)> {
|
||||
self.create_tab_with_runtime(
|
||||
rows,
|
||||
@@ -532,6 +546,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new("", crate::config::ShellModeConfig::NonLogin),
|
||||
Some(argv),
|
||||
extra_env,
|
||||
@@ -545,6 +560,7 @@ impl Workspace {
|
||||
cwd: PathBuf,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
argv: Option<&[String]>,
|
||||
extra_env: Vec<(String, String)>,
|
||||
@@ -575,6 +591,7 @@ impl Workspace {
|
||||
argv,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
&launch_env,
|
||||
events,
|
||||
render_notify,
|
||||
@@ -588,6 +605,7 @@ impl Workspace {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
&launch_env,
|
||||
events,
|
||||
@@ -655,6 +673,7 @@ impl Workspace {
|
||||
cwd: Option<PathBuf>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
extra_env: Vec<(String, String)>,
|
||||
) -> std::io::Result<crate::workspace::tab::NewPane> {
|
||||
@@ -674,6 +693,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
&launch_env,
|
||||
)?;
|
||||
@@ -692,6 +712,7 @@ impl Workspace {
|
||||
extra_env: Vec<(String, String)>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) -> std::io::Result<crate::workspace::tab::NewPane> {
|
||||
let pane_number = self.next_public_pane_number;
|
||||
let tab_number = self
|
||||
@@ -711,11 +732,14 @@ impl Workspace {
|
||||
&launch_env,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
)?;
|
||||
self.register_new_pane_with_number(new_pane.pane_id, pane_number);
|
||||
Ok(new_pane)
|
||||
}
|
||||
|
||||
// Workspace split routing carries pane identity, geometry, host context, and focus policy.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn split_pane(
|
||||
&mut self,
|
||||
pane_id: PaneId,
|
||||
@@ -725,6 +749,7 @@ impl Workspace {
|
||||
cwd: Option<PathBuf>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
extra_env: Vec<(String, String)>,
|
||||
focus_new_pane: bool,
|
||||
@@ -738,6 +763,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
extra_env,
|
||||
focus_new_pane,
|
||||
@@ -756,6 +782,7 @@ impl Workspace {
|
||||
cwd: Option<PathBuf>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
extra_env: Vec<(String, String)>,
|
||||
focus_new_pane: bool,
|
||||
@@ -769,6 +796,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
extra_env,
|
||||
focus_new_pane,
|
||||
@@ -788,6 +816,7 @@ impl Workspace {
|
||||
extra_env: Vec<(String, String)>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
focus_new_pane: bool,
|
||||
) -> Option<std::io::Result<(usize, crate::workspace::tab::NewPane)>> {
|
||||
self.split_pane_with_runtime(
|
||||
@@ -799,6 +828,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new("", crate::config::ShellModeConfig::NonLogin),
|
||||
extra_env,
|
||||
focus_new_pane,
|
||||
@@ -819,6 +849,7 @@ impl Workspace {
|
||||
extra_env: Vec<(String, String)>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
focus_new_pane: bool,
|
||||
) -> Option<std::io::Result<(usize, crate::workspace::tab::NewPane)>> {
|
||||
self.split_pane_with_runtime(
|
||||
@@ -830,6 +861,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new("", crate::config::ShellModeConfig::NonLogin),
|
||||
extra_env,
|
||||
focus_new_pane,
|
||||
@@ -848,6 +880,7 @@ impl Workspace {
|
||||
cwd: Option<PathBuf>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
extra_env: Vec<(String, String)>,
|
||||
focus_new_pane: bool,
|
||||
@@ -872,6 +905,7 @@ impl Workspace {
|
||||
&launch_env,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
),
|
||||
None => tab.split_focused_argv_command(
|
||||
direction,
|
||||
@@ -882,6 +916,7 @@ impl Workspace {
|
||||
&launch_env,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
@@ -894,6 +929,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
&launch_env,
|
||||
),
|
||||
@@ -904,6 +940,7 @@ impl Workspace {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
&launch_env,
|
||||
),
|
||||
@@ -1663,6 +1700,7 @@ mod tests {
|
||||
&argv,
|
||||
1024,
|
||||
crate::terminal_theme::TerminalTheme::default(),
|
||||
None,
|
||||
events,
|
||||
render_notify,
|
||||
render_dirty,
|
||||
|
||||
@@ -52,6 +52,8 @@ pub struct Tab {
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
// Tab construction threads pane runtime geometry, host context, and render hooks.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
number: usize,
|
||||
initial_cwd: PathBuf,
|
||||
@@ -59,6 +61,7 @@ impl Tab {
|
||||
cols: u16,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
launch_env: &PaneLaunchEnv,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
@@ -72,6 +75,7 @@ impl Tab {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
launch_env,
|
||||
events,
|
||||
@@ -81,6 +85,8 @@ impl Tab {
|
||||
)
|
||||
}
|
||||
|
||||
// Command tab construction mirrors the shell tab runtime arguments.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_argv_command(
|
||||
number: usize,
|
||||
initial_cwd: PathBuf,
|
||||
@@ -89,6 +95,7 @@ impl Tab {
|
||||
argv: &[String],
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
launch_env: &PaneLaunchEnv,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
render_notify: Arc<Notify>,
|
||||
@@ -101,6 +108,7 @@ impl Tab {
|
||||
cols,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new("", crate::config::ShellModeConfig::NonLogin),
|
||||
launch_env,
|
||||
events,
|
||||
@@ -118,6 +126,7 @@ impl Tab {
|
||||
cols: u16,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
launch_env: &PaneLaunchEnv,
|
||||
events: mpsc::Sender<AppEvent>,
|
||||
@@ -137,6 +146,7 @@ impl Tab {
|
||||
crate::pane::AgentDetection::Enabled,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
events.clone(),
|
||||
render_notify.clone(),
|
||||
render_dirty.clone(),
|
||||
@@ -149,6 +159,7 @@ impl Tab {
|
||||
initial_cwd.clone(),
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
launch_env,
|
||||
events.clone(),
|
||||
@@ -202,6 +213,7 @@ impl Tab {
|
||||
cwd: Option<PathBuf>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
launch_env: &PaneLaunchEnv,
|
||||
) -> std::io::Result<NewPane> {
|
||||
@@ -213,6 +225,7 @@ impl Tab {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
launch_env,
|
||||
None,
|
||||
@@ -228,6 +241,7 @@ impl Tab {
|
||||
cwd: Option<PathBuf>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
launch_env: &PaneLaunchEnv,
|
||||
) -> std::io::Result<NewPane> {
|
||||
@@ -239,6 +253,7 @@ impl Tab {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
launch_env,
|
||||
None,
|
||||
@@ -255,6 +270,7 @@ impl Tab {
|
||||
launch_env: &PaneLaunchEnv,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) -> std::io::Result<NewPane> {
|
||||
self.split_focused_with_runtime(
|
||||
direction,
|
||||
@@ -264,6 +280,7 @@ impl Tab {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new("", crate::config::ShellModeConfig::NonLogin),
|
||||
launch_env,
|
||||
Some(SplitCommand::Shell {
|
||||
@@ -283,6 +300,7 @@ impl Tab {
|
||||
launch_env: &PaneLaunchEnv,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) -> std::io::Result<NewPane> {
|
||||
self.split_focused_with_runtime(
|
||||
direction,
|
||||
@@ -292,6 +310,7 @@ impl Tab {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new("", crate::config::ShellModeConfig::NonLogin),
|
||||
launch_env,
|
||||
Some(SplitCommand::Argv { argv, launch_env }),
|
||||
@@ -309,6 +328,7 @@ impl Tab {
|
||||
launch_env: &PaneLaunchEnv,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
) -> std::io::Result<NewPane> {
|
||||
self.split_focused_with_runtime(
|
||||
direction,
|
||||
@@ -318,12 +338,15 @@ impl Tab {
|
||||
cwd,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
crate::pane::PaneShellConfig::new("", crate::config::ShellModeConfig::NonLogin),
|
||||
launch_env,
|
||||
Some(SplitCommand::Argv { argv, launch_env }),
|
||||
)
|
||||
}
|
||||
|
||||
// Split construction threads geometry, host context, launch policy, and command state.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn split_focused_with_runtime(
|
||||
&mut self,
|
||||
direction: Direction,
|
||||
@@ -333,6 +356,7 @@ impl Tab {
|
||||
cwd: Option<PathBuf>,
|
||||
scrollback_limit_bytes: usize,
|
||||
host_terminal_theme: crate::terminal_theme::TerminalTheme,
|
||||
host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>,
|
||||
shell_config: crate::pane::PaneShellConfig<'_>,
|
||||
launch_env: &PaneLaunchEnv,
|
||||
command: Option<SplitCommand<'_>>,
|
||||
@@ -363,6 +387,7 @@ impl Tab {
|
||||
crate::pane::AgentDetection::Enabled,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
self.events.clone(),
|
||||
self.render_notify.clone(),
|
||||
self.render_dirty.clone(),
|
||||
@@ -377,6 +402,7 @@ impl Tab {
|
||||
crate::pane::AgentDetection::Enabled,
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
self.events.clone(),
|
||||
self.render_notify.clone(),
|
||||
self.render_dirty.clone(),
|
||||
@@ -388,6 +414,7 @@ impl Tab {
|
||||
actual_cwd.clone(),
|
||||
scrollback_limit_bytes,
|
||||
host_terminal_theme,
|
||||
host_terminal_appearance,
|
||||
shell_config,
|
||||
launch_env,
|
||||
self.events.clone(),
|
||||
|
||||
Reference in New Issue
Block a user