Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions rust/limux-host-linux/src/layout_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ pub enum TabContentState {
cwd: Option<String>,
#[serde(default)]
agent: Option<RestorableAgentState>,
#[serde(default)]
font_size: Option<f32>,
},
Browser {
#[serde(default)]
Expand Down Expand Up @@ -260,6 +262,7 @@ impl TabState {
content: TabContentState::Terminal {
cwd: cwd.map(|value| value.to_string()),
agent: None,
font_size: None,
},
}
}
Expand Down Expand Up @@ -1379,6 +1382,7 @@ mod tests {
launch_command: None,
restore_on_startup: true,
}),
font_size: None,
},
}],
});
Expand Down Expand Up @@ -1474,6 +1478,7 @@ mod tests {
}),
restore_on_startup: true,
}),
font_size: None,
},
};

Expand All @@ -1498,6 +1503,52 @@ mod tests {
}
}

#[test]
fn terminal_tab_state_round_trips_font_size_override() {
let tab = TabState {
id: "tab-a".to_string(),
custom_name: None,
pinned: false,
content: TabContentState::Terminal {
cwd: Some("/tmp/project".to_string()),
agent: None,
font_size: Some(17.5),
},
};

let raw = serde_json::to_string(&tab).expect("encode tab");
let decoded: TabState = serde_json::from_str(&raw).expect("decode tab");

match decoded.content {
TabContentState::Terminal { font_size, .. } => {
assert_eq!(font_size, Some(17.5));
}
other => panic!("expected terminal tab, got {other:?}"),
}
}

#[test]
fn terminal_tab_state_defaults_missing_font_size_override() {
let decoded: TabState = serde_json::from_str(
r#"{
"id": "tab-a",
"custom_name": null,
"pinned": false,
"tab_kind": "terminal",
"cwd": "/tmp/project",
"agent": null
}"#,
)
.expect("decode legacy tab");

match decoded.content {
TabContentState::Terminal { font_size, .. } => {
assert_eq!(font_size, None);
}
other => panic!("expected terminal tab, got {other:?}"),
}
}

#[test]
fn restorable_agent_resume_command_runs_from_cwd() {
let agent = RestorableAgentState {
Expand Down
66 changes: 64 additions & 2 deletions rust/limux-host-linux/src/pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,56 @@ pub struct PaneCallbacks {
#[derive(Clone)]
struct TerminalTabState {
cwd: Rc<RefCell<Option<String>>>,
font_size: Rc<RefCell<Option<f32>>>,
handle: terminal::TerminalHandle,
}

#[derive(Clone)]
pub struct TerminalShortcutTarget {
handle: terminal::TerminalHandle,
font_size: Rc<RefCell<Option<f32>>>,
callbacks: Rc<PaneCallbacks>,
}

impl TerminalShortcutTarget {
pub fn perform_binding_action(&self, action: &str) -> bool {
self.handle.perform_binding_action(action)
}

pub fn font_size_override(&self) -> Option<f32> {
*self.font_size.borrow()
}

pub fn set_font_size_override(&self, size: f32) -> bool {
if !size.is_finite() {
return false;
}

let size = size.clamp(1.0, 255.0);
let action = format!("set_font_size:{size}");
if !self.handle.perform_binding_action(&action) {
return false;
}

*self.font_size.borrow_mut() = Some(size);
(self.callbacks.on_state_changed)();
true
}

pub fn reset_font_size_override(&self, default_size: Option<f32>) -> bool {
let action = match default_size.filter(|size| size.is_finite()) {
Some(size) => format!("set_font_size:{}", size.clamp(1.0, 255.0)),
None => "reset_font_size".to_string(),
};
if !self.handle.perform_binding_action(&action) {
return false;
}

*self.font_size.borrow_mut() = None;
(self.callbacks.on_state_changed)();
true
}

pub fn show_find(&self) -> bool {
self.handle.show_find()
}
Expand Down Expand Up @@ -943,6 +980,7 @@ struct TerminalTabOptions<'a> {
pinned: bool,
cwd: Option<&'a str>,
agent: Option<RestorableAgentState>,
font_size: Option<f32>,
}

struct BrowserTabOptions<'a> {
Expand Down Expand Up @@ -976,7 +1014,11 @@ fn restore_tabs_from_state(

for saved_tab in &saved_state.tabs {
match &saved_tab.content {
TabContentState::Terminal { cwd, agent } => add_terminal_tab_inner(
TabContentState::Terminal {
cwd,
agent,
font_size,
} => add_terminal_tab_inner(
internals,
cwd.as_deref().or(working_directory),
Some(TerminalTabOptions {
Expand All @@ -985,6 +1027,7 @@ fn restore_tabs_from_state(
pinned: saved_tab.pinned,
cwd: cwd.as_deref().or(working_directory),
agent: agent.clone(),
font_size: *font_size,
}),
),
TabContentState::Browser { uri } => add_browser_tab_inner(
Expand Down Expand Up @@ -1207,6 +1250,13 @@ fn add_terminal_tab_inner(
.and_then(|value| value.cwd.map(|cwd| cwd.to_string()))
.or_else(|| working_directory.map(|cwd| cwd.to_string())),
));
let term_font_size = Rc::new(RefCell::new(
options
.as_ref()
.and_then(|value| value.font_size)
.filter(|size| size.is_finite())
.map(|size| size.clamp(1.0, 255.0)),
));
let term_callbacks = make_terminal_callbacks(internals, &tab_id, &title_label, &term_cwd);
let hover_focus = {
let callbacks = internals.callbacks.clone();
Expand Down Expand Up @@ -1249,11 +1299,19 @@ fn add_terminal_tab_inner(
);
}

let saved_font_size = *term_font_size.borrow();
let saved_font_size = saved_font_size.or_else(|| {
let config = (internals.callbacks.current_config)();
let configured = config.borrow().font_size;
configured
.filter(|size| size.is_finite())
.map(|size| size.clamp(1.0, 255.0))
});
let term = terminal::create_terminal(
working_directory,
terminal::TerminalOptions {
hover_focus,
saved_font_size: (internals.callbacks.current_config)().borrow().font_size,
saved_font_size,
startup_command,
extra_env,
},
Expand All @@ -1276,6 +1334,7 @@ fn add_terminal_tab_inner(
kind: TabKind::Terminal {
state: TerminalTabState {
cwd: term_cwd.clone(),
font_size: term_font_size.clone(),
handle: term.handle.clone(),
},
},
Expand Down Expand Up @@ -1576,6 +1635,7 @@ pub fn snapshot_pane_state(pane_widget: &gtk::Widget) -> Option<PaneState> {
TabKind::Terminal { state } => TabContentState::Terminal {
cwd: state.cwd.borrow().clone(),
agent: None,
font_size: *state.font_size.borrow(),
},
TabKind::Browser { state } => TabContentState::Browser {
uri: state.uri.borrow().clone(),
Expand Down Expand Up @@ -1839,6 +1899,8 @@ pub fn focused_shortcut_target(pane_widget: &gtk::Widget) -> FocusedShortcutTarg
..
}) => FocusedShortcutTarget::Terminal(TerminalShortcutTarget {
handle: state.handle.clone(),
font_size: state.font_size.clone(),
callbacks: internals.callbacks.clone(),
}),
Some(TabEntry {
kind: TabKind::Browser { state },
Expand Down
16 changes: 0 additions & 16 deletions rust/limux-host-linux/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1828,22 +1828,6 @@ pub fn create_terminal(
// Context menu
// ---------------------------------------------------------------------------

/// Send a binding action to every live surface.
pub(crate) fn broadcast_binding_action(action: &str) {
SURFACE_MAP.with(|map| {
for &key in map.borrow().keys() {
let surface = key as ghostty_surface_t;
unsafe {
ghostty_surface_binding_action(
surface,
action.as_ptr() as *const c_char,
action.len(),
);
}
}
});
}

fn surface_action(surface: Option<ghostty_surface_t>, action: &str) {
if let Some(surface) = surface {
unsafe {
Expand Down
79 changes: 33 additions & 46 deletions rust/limux-host-linux/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5260,66 +5260,53 @@ fn dispatch_terminal_command(state: &State, command: ShortcutCommand) -> bool {
ShortcutCommand::TerminalClearScrollback => target.perform_binding_action("clear_screen"),
ShortcutCommand::TerminalCopy => target.perform_binding_action("copy_to_clipboard"),
ShortcutCommand::TerminalPaste => target.perform_binding_action("paste_from_clipboard"),
ShortcutCommand::TerminalIncreaseFontSize => persist_font_size_delta(state, 1.0),
ShortcutCommand::TerminalDecreaseFontSize => persist_font_size_delta(state, -1.0),
ShortcutCommand::TerminalResetFontSize => persist_font_size_reset(state),
ShortcutCommand::TerminalIncreaseFontSize => adjust_terminal_font_size(state, &target, 1.0),
ShortcutCommand::TerminalDecreaseFontSize => {
adjust_terminal_font_size(state, &target, -1.0)
}
ShortcutCommand::TerminalResetFontSize => reset_terminal_font_size(state, &target),
_ => false,
}
}

fn persist_font_size_delta(state: &State, delta: f32) -> bool {
let current = {
let s = state.borrow();
let current = s.config.borrow().font_size;
current
};
let new_size = font_size_after_delta(current, crate::terminal::default_font_size(), delta);

if let Err(err) = persist_font_size(state, Some(new_size)) {
show_font_size_save_error(state, err);
return false;
}

broadcast_font_size(new_size);
true
}

fn persist_font_size_reset(state: &State) -> bool {
if let Err(err) = persist_font_size(state, None) {
show_font_size_save_error(state, err);
return false;
}

crate::terminal::broadcast_binding_action("reset_font_size");
true
fn terminal_default_font_size(state: &State) -> f32 {
terminal_default_font_size_override(state).unwrap_or_else(crate::terminal::default_font_size)
}

fn persist_font_size(state: &State, font_size: Option<f32>) -> Result<(), String> {
let mut updated = {
fn terminal_default_font_size_override(state: &State) -> Option<f32> {
let config = {
let s = state.borrow();
let updated = s.config.borrow().clone();
updated
s.config.clone()
};
updated.font_size = font_size;
app_config::save(&updated)?;

state.borrow().config.borrow_mut().font_size = font_size;
Ok(())
let configured = config.borrow().font_size;
configured
.filter(|size| size.is_finite())
.map(|size| size.clamp(1.0, 255.0))
}

fn font_size_after_delta(current: Option<f32>, default: f32, delta: f32) -> f32 {
(current.unwrap_or(default) + delta).clamp(1.0, 255.0)
fn adjust_terminal_font_size(
state: &State,
target: &pane::TerminalShortcutTarget,
delta: f32,
) -> bool {
let new_size = font_size_after_delta(
target.font_size_override(),
terminal_default_font_size(state),
delta,
);
target.set_font_size_override(new_size)
}

fn show_font_size_save_error(state: &State, err: String) {
let detail = format!("Failed to save Limux settings: {err}");
eprintln!("limux: {detail}");
show_runtime_error(state, "Failed to save settings", &detail);
fn reset_terminal_font_size(state: &State, target: &pane::TerminalShortcutTarget) -> bool {
target.reset_font_size_override(terminal_default_font_size_override(state))
}

fn broadcast_font_size(size: f32) {
let action = format!("set_font_size:{size}");
crate::terminal::broadcast_binding_action(&action);
fn font_size_after_delta(current: Option<f32>, default: f32, delta: f32) -> f32 {
let base = current
.filter(|size| size.is_finite())
.unwrap_or(default)
.clamp(1.0, 255.0);
(base + delta).clamp(1.0, 255.0)
}

fn dispatch_browser_command(state: &State, command: ShortcutCommand) -> bool {
Expand Down