From 44998181d9535f25714e1bf24c0ff28d9df4183b Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Mon, 23 Mar 2026 17:21:50 -0300 Subject: [PATCH 1/5] feat: add configurable line_height setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hardcoded 1.4 line-height multiplier with a user-facing config option (range 0.8–2.5, default 1.4). Adds parsing, validation, settings UI stepper, and live config reload support. --- Cargo.lock | 4 +-- crates/config_core/src/constants.rs | 9 +++++++ crates/config_core/src/default_config.txt | 2 ++ crates/config_core/src/lib.rs | 5 +++- crates/config_core/src/parser.rs | 27 ++++++++++++++++++-- crates/config_core/src/parser_tests.rs | 29 +++++++++++++++++++--- crates/config_core/src/schema.rs | 11 +++++++++ crates/config_core/src/types.rs | 5 ++++ crates/terminal_ui/src/grid.rs | 13 +++++----- docs/configuration.md | 5 ++++ src/main.rs | 4 --- src/settings_view/sections.rs | 10 ++++++++ src/settings_view/state.rs | 30 +++++++++++++++++++++++ src/settings_view/state_apply.rs | 23 +++++++++++++++++ src/terminal_view/mod.rs | 4 ++- 15 files changed, 160 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1210f270..bf8ce4bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7333,7 +7333,7 @@ dependencies = [ [[package]] name = "termy" -version = "0.1.65" +version = "0.1.67" dependencies = [ "alacritty_terminal", "anyhow", @@ -7420,7 +7420,7 @@ dependencies = [ [[package]] name = "termy_cli" -version = "0.1.65" +version = "0.1.67" dependencies = [ "anyhow", "clap", diff --git a/crates/config_core/src/constants.rs b/crates/config_core/src/constants.rs index b5c87ff9..3f4d34cc 100644 --- a/crates/config_core/src/constants.rs +++ b/crates/config_core/src/constants.rs @@ -11,6 +11,15 @@ pub(crate) const DEFAULT_TMUX_BINARY: &str = "tmux"; pub(crate) const DEFAULT_TMUX_PERSISTENCE: bool = true; pub(crate) const DEFAULT_TMUX_SHOW_ACTIVE_PANE_BORDER: bool = false; pub(crate) const DEFAULT_MOUSE_SCROLL_MULTIPLIER: f32 = 3.0; +/// Unitless multiplier applied to the font's natural cell height to produce the +/// terminal row height. `1.0` means no extra vertical space; `2.0` doubles it. +pub const DEFAULT_LINE_HEIGHT: f32 = 1.4; +/// Lowest accepted line-height multiplier. Values below this cause rows to +/// visually overlap. +pub const MIN_LINE_HEIGHT: f32 = 0.8; +/// Highest accepted line-height multiplier. Values above this make the grid +/// unusably sparse. +pub const MAX_LINE_HEIGHT: f32 = 2.5; pub(crate) const DEFAULT_SCROLLBACK_HISTORY: usize = 2000; pub(crate) const MAX_SCROLLBACK_HISTORY: usize = 100_000; pub(crate) const DEFAULT_INACTIVE_TAB_SCROLLBACK: Option = None; diff --git a/crates/config_core/src/default_config.txt b/crates/config_core/src/default_config.txt index 0579dd32..17a84c75 100644 --- a/crates/config_core/src/default_config.txt +++ b/crates/config_core/src/default_config.txt @@ -73,6 +73,8 @@ window_height = 820 font_family = JetBrains Mono # Terminal font size in pixels font_size = 14 +# Terminal line height multiplier (0.8 to 2.5) +line_height = 1.4 # Shape of the terminal cursor cursor_style = block # Enable blinking cursor animation diff --git a/crates/config_core/src/lib.rs b/crates/config_core/src/lib.rs index 309926f4..b79a2460 100644 --- a/crates/config_core/src/lib.rs +++ b/crates/config_core/src/lib.rs @@ -9,7 +9,10 @@ mod schema; mod types; pub use color_keys::{ColorEntryError, apply_color_entry, canonical_color_key}; -pub use constants::{SHELL_DECIDE_THEME_ID, VALID_ROOT_KEYS, VALID_SECTIONS}; +pub use constants::{ + DEFAULT_LINE_HEIGHT, MAX_LINE_HEIGHT, MIN_LINE_HEIGHT, SHELL_DECIDE_THEME_ID, VALID_ROOT_KEYS, + VALID_SECTIONS, +}; pub use diagnostics::{ConfigDiagnostic, ConfigDiagnosticKind, ConfigParseReport}; pub use document::{ ColorSettingUpdate, apply_color_updates, remove_raw_root_key, remove_root_setting, diff --git a/crates/config_core/src/parser.rs b/crates/config_core/src/parser.rs index 24a28d20..704d5388 100644 --- a/crates/config_core/src/parser.rs +++ b/crates/config_core/src/parser.rs @@ -2,8 +2,8 @@ use std::collections::{BTreeMap, HashMap}; use crate::color_keys::{ColorEntryError, apply_color_entry}; use crate::constants::{ - MAX_MOUSE_SCROLL_MULTIPLIER, MAX_PANE_FOCUS_STRENGTH, MAX_SCROLLBACK_HISTORY, - MIN_MOUSE_SCROLL_MULTIPLIER, SHELL_DECIDE_THEME_ID, VALID_SECTIONS, + MAX_LINE_HEIGHT, MAX_MOUSE_SCROLL_MULTIPLIER, MAX_PANE_FOCUS_STRENGTH, MAX_SCROLLBACK_HISTORY, + MIN_LINE_HEIGHT, MIN_MOUSE_SCROLL_MULTIPLIER, SHELL_DECIDE_THEME_ID, VALID_SECTIONS, }; use crate::diagnostics::{ConfigDiagnostic, ConfigDiagnosticKind, ConfigParseReport}; use crate::schema::{RootSettingId, root_setting_from_key, root_setting_spec}; @@ -519,6 +519,29 @@ impl AppConfig { config.font_size = parsed; } } + RootSettingId::LineHeight => { + if let Some(parsed) = parse_f32_field( + &mut diagnostics, + line_number, + key, + value, + "a finite number between 0.8 and 2.5", + ) { + if parsed.is_finite() + && (MIN_LINE_HEIGHT..=MAX_LINE_HEIGHT).contains(&parsed) + { + config.line_height = parsed; + } else { + push_invalid_value( + &mut diagnostics, + line_number, + key, + value, + "a finite number between 0.8 and 2.5", + ); + } + } + } RootSettingId::CursorStyle => { if let Some(parsed) = CursorStyle::from_str(value) { config.cursor_style = parsed; diff --git a/crates/config_core/src/parser_tests.rs b/crates/config_core/src/parser_tests.rs index 0854b276..bfd9c421 100644 --- a/crates/config_core/src/parser_tests.rs +++ b/crates/config_core/src/parser_tests.rs @@ -1,8 +1,8 @@ use crate::{ - AiProvider, AppConfig, ConfigDiagnosticKind, ConfigParseReport, CursorStyle, PaneFocusEffect, - Rgb8, RootSettingId, RootSettingValueKind, TabCloseVisibility, TabTitleMode, TabTitleSource, - TabWidthMode, TerminalScrollbarStyle, TerminalScrollbarVisibility, WorkingDirFallback, - root_setting_specs, + AiProvider, AppConfig, ConfigDiagnosticKind, ConfigParseReport, CursorStyle, + DEFAULT_LINE_HEIGHT, PaneFocusEffect, Rgb8, RootSettingId, RootSettingValueKind, + TabCloseVisibility, TabTitleMode, TabTitleSource, TabWidthMode, TerminalScrollbarStyle, + TerminalScrollbarVisibility, WorkingDirFallback, root_setting_specs, }; fn parse(input: &str) -> AppConfig { @@ -21,6 +21,7 @@ fn defaults_enable_tmux_persistence_and_raise_pane_focus_strength() { assert!(!defaults.background_opacity_cells); assert!(!defaults.chrome_contrast); assert!((defaults.pane_focus_strength - 0.6).abs() < f32::EPSILON); + assert!((defaults.line_height - DEFAULT_LINE_HEIGHT).abs() < f32::EPSILON); } #[test] @@ -390,6 +391,26 @@ fn numeric_keys_parse_table_driven() { assert_eq!(parsed, fallback); } + assert_eq!(parse("line_height = 0.8\n").line_height, 0.8); + assert_eq!(parse("line_height = 1.25\n").line_height, 1.25); + assert_eq!(parse("line_height = 2.5\n").line_height, 2.5); + assert_eq!( + parse("line_height = 0.79\n").line_height, + defaults.line_height + ); + assert_eq!( + parse("line_height = 2.51\n").line_height, + defaults.line_height + ); + assert_eq!( + parse("line_height = inf\n").line_height, + defaults.line_height + ); + assert_eq!( + parse("line_height = NaN\n").line_height, + defaults.line_height + ); + let non_negative_float_cases = [("padding_x", 2.0), ("padding_y", 4.0)]; for (key, expected) in non_negative_float_cases { let valid = parse(&format!("{} = {}\n", key, expected)); diff --git a/crates/config_core/src/schema.rs b/crates/config_core/src/schema.rs index fba029a2..447bf8c4 100644 --- a/crates/config_core/src/schema.rs +++ b/crates/config_core/src/schema.rs @@ -333,6 +333,7 @@ define_root_settings! { (WindowHeight, "window_height", [], Advanced, "WINDOW", "Window Height", "Default startup window height in pixels", ["window", "height", "startup", "size"], RootSettingValueKind::Numeric, false), (FontFamily, "font_family", [], Appearance, "FONT", "Font Family", "Font family used in terminal UI", ["font", "typeface", "text"], RootSettingValueKind::Special, false), (FontSize, "font_size", [], Appearance, "FONT", "Font Size", "Terminal font size in pixels", ["font", "size", "text"], RootSettingValueKind::Numeric, false), + (LineHeight, "line_height", [], Appearance, "FONT", "Line Height", "Terminal line height multiplier (0.8 to 2.5)", ["font", "line", "height", "spacing", "rows"], RootSettingValueKind::Numeric, false), (CursorStyle, "cursor_style", [], Terminal, "CURSOR", "Cursor Style", "Shape of the terminal cursor", ["cursor", "shape", "block", "line"], RootSettingValueKind::Enum, false), (CursorBlink, "cursor_blink", [], Terminal, "CURSOR", "Cursor Blink", "Enable blinking cursor animation", ["cursor", "blink", "animation"], RootSettingValueKind::Boolean, false), (BackgroundOpacity, "background_opacity", [], Appearance, "WINDOW", "Background Opacity", "Window background opacity (0.0 to 1.0)", ["background", "opacity", "transparency"], RootSettingValueKind::Numeric, false), @@ -478,6 +479,7 @@ pub fn root_setting_default_value(config: &AppConfig, id: RootSettingId) -> Opti RootSettingId::WindowHeight => Some(config.window_height.to_string()), RootSettingId::FontFamily => Some(config.font_family.clone()), RootSettingId::FontSize => Some(config.font_size.to_string()), + RootSettingId::LineHeight => Some(config.line_height.to_string()), RootSettingId::CursorStyle => Some(match config.cursor_style { CursorStyle::Line => "line".to_string(), CursorStyle::Block => "block".to_string(), @@ -641,4 +643,13 @@ mod tests { Some("false".to_string()) ); } + + #[test] + fn line_height_default_value_matches_app_config() { + let defaults = AppConfig::default(); + assert_eq!( + root_setting_default_value(&defaults, RootSettingId::LineHeight), + Some(defaults.line_height.to_string()) + ); + } } diff --git a/crates/config_core/src/types.rs b/crates/config_core/src/types.rs index bdad3504..682bb5d4 100644 --- a/crates/config_core/src/types.rs +++ b/crates/config_core/src/types.rs @@ -286,6 +286,10 @@ pub struct AppConfig { pub window_height: f32, pub font_family: String, pub font_size: f32, + /// Unitless multiplier on the font cell height that controls vertical row + /// spacing. Clamped to [`MIN_LINE_HEIGHT`]..=[`MAX_LINE_HEIGHT`] at the + /// use-site in `TerminalView`. + pub line_height: f32, pub cursor_style: CursorStyle, pub cursor_blink: bool, pub background_opacity: f32, @@ -360,6 +364,7 @@ impl Default for AppConfig { window_height: 820.0, font_family: "JetBrains Mono".to_string(), font_size: 14.0, + line_height: crate::constants::DEFAULT_LINE_HEIGHT, cursor_style: CursorStyle::default(), cursor_blink: DEFAULT_CURSOR_BLINK, background_opacity: 1.0, diff --git a/crates/terminal_ui/src/grid.rs b/crates/terminal_ui/src/grid.rs index cb77f24e..a032a29d 100644 --- a/crates/terminal_ui/src/grid.rs +++ b/crates/terminal_ui/src/grid.rs @@ -717,7 +717,11 @@ impl TerminalGrid { Self::push_pending_text_batch(&mut current, &mut ops); current = Some(TextBatch::new( - cell.col, cell.row, cell.char, key, underline, + cell.col, + cell.row, + cell.char, + key, + underline, )); } @@ -1058,11 +1062,6 @@ impl TerminalGrid { self.cells.iter().map(|row| row.len()).sum() } - #[cfg(test)] - fn iter_cells(&self) -> impl Iterator { - self.cells.iter().flat_map(|row| row.iter()) - } - fn cell_is_drawable_text(cell: &CellRenderInfo) -> bool { cell.render_text && cell.char != ' ' && cell.char != '\0' && !cell.char.is_control() } @@ -1107,7 +1106,7 @@ impl TerminalGrid { let mut ops = Vec::with_capacity(self.cell_count()); let mut current: Option = None; - for cell in self.iter_cells() { + for cell in self.cells.iter().flat_map(|row| row.iter()) { if !Self::cell_is_drawable_text(cell) { Self::push_pending_text_batch(&mut current, &mut ops); continue; diff --git a/docs/configuration.md b/docs/configuration.md index bdc473d6..3d13927c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -28,6 +28,11 @@ Tmux integration is an add-on. Set `tmux_enabled = true` to start in tmux mode b - Terminal font size in pixels - Group: `FONT` +`line_height` +- Default: `1.4` +- Terminal line height multiplier (0.8 to 2.5) +- Group: `FONT` + `background_opacity` - Default: `1` - Window background opacity (0.0 to 1.0) diff --git a/src/main.rs b/src/main.rs index db7a366e..34839ed8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -447,10 +447,6 @@ fn main() { }); application.run(move |cx: &mut App| { - // Set the dock icon for development builds (no .app bundle). - #[cfg(debug_assertions)] - termy_native_sdk::set_dock_icon_from_png(include_bytes!("../assets/termy_icon@1024px.png")); - spawn_deeplink_listener(cx, deeplink_rx); cx.on_action(|_: &OpenConfig, _cx| { diff --git a/src/settings_view/sections.rs b/src/settings_view/sections.rs index cf6c4437..9acb5400 100644 --- a/src/settings_view/sections.rs +++ b/src/settings_view/sections.rs @@ -89,12 +89,14 @@ impl SettingsWindow { let chrome_contrast = self.config.chrome_contrast; let font_family = self.config.font_family.clone(); let font_size = self.config.font_size; + let line_height = self.config.line_height; let padding_x = self.config.padding_x; let padding_y = self.config.padding_y; let theme_meta = Self::setting_metadata_or_fallback("theme"); let opacity_meta = Self::setting_metadata_or_fallback("background_opacity"); let font_family_meta = Self::setting_metadata_or_fallback("font_family"); let font_size_meta = Self::setting_metadata_or_fallback("font_size"); + let line_height_meta = Self::setting_metadata_or_fallback("line_height"); let padding_x_meta = Self::setting_metadata_or_fallback("padding_x"); let padding_y_meta = Self::setting_metadata_or_fallback("padding_y"); @@ -161,6 +163,14 @@ impl SettingsWindow { format!("{}px", font_size as i32), cx, ), + self.render_editable_row( + "line_height", + EditableField::LineHeight, + line_height_meta.title, + line_height_meta.description, + format!("{line_height:.2}"), + cx, + ), ]; let font_group = self.render_settings_group("FONT", font_rows); diff --git a/src/settings_view/state.rs b/src/settings_view/state.rs index 144b5b95..1720a2c7 100644 --- a/src/settings_view/state.rs +++ b/src/settings_view/state.rs @@ -4,6 +4,12 @@ const MAX_THEME_SUGGESTIONS: usize = 16; const MAX_FONT_SUGGESTIONS: usize = 200; const PANE_FOCUS_MAX: f32 = 2.0; +/// Format a line-height multiplier for display and persistence (two decimal +/// places, e.g. `"1.40"`). +fn format_line_height(value: f32) -> String { + format!("{value:.2}") +} + #[cfg_attr(target_os = "windows", allow(dead_code))] #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(super) enum EditableField { @@ -11,6 +17,7 @@ pub(super) enum EditableField { BackgroundOpacity, FontFamily, FontSize, + LineHeight, PaddingX, PaddingY, Shell, @@ -404,6 +411,7 @@ impl SettingsWindow { | EditableField::BackgroundOpacity | EditableField::FontFamily | EditableField::FontSize + | EditableField::LineHeight | EditableField::PaddingX | EditableField::PaddingY => Self::appearance_field_spec(field), EditableField::Shell @@ -474,6 +482,16 @@ impl SettingsWindow { max: 4096.0, }), }, + EditableField::LineHeight => FieldSpec { + root_setting: Some(RootSettingId::LineHeight), + codec: FieldCodec::Numeric, + dropdown_click_only: false, + numeric_step: Some(NumericStepSpec { + delta: 0.05, + min: termy_config_core::MIN_LINE_HEIGHT, + max: termy_config_core::MAX_LINE_HEIGHT, + }), + }, EditableField::PaddingX => FieldSpec { root_setting: Some(RootSettingId::PaddingX), codec: FieldCodec::Numeric, @@ -822,6 +840,7 @@ impl SettingsWindow { ), EditableField::FontFamily => self.config.font_family.clone(), EditableField::FontSize => format!("{}", self.config.font_size.round() as i32), + EditableField::LineHeight => format_line_height(self.config.line_height), EditableField::PaddingX => format!("{}", self.config.padding_x.round() as i32), EditableField::PaddingY => format!("{}", self.config.padding_y.round() as i32), EditableField::Shell => self.config.shell.clone().unwrap_or_default(), @@ -975,6 +994,15 @@ impl SettingsWindow { &next.to_string(), ) } + EditableField::LineHeight => { + let next = (self.config.line_height + (delta as f32 * step.delta)) + .clamp(step.min, step.max); + self.config.line_height = next; + config::set_root_setting( + termy_config_core::RootSettingId::LineHeight, + &format_line_height(next), + ) + } EditableField::PaddingX => { let next = (self.config.padding_x + (delta as f32 * step.delta)).clamp(step.min, step.max); @@ -1172,6 +1200,7 @@ mod tests { EditableField::BackgroundOpacity, EditableField::FontFamily, EditableField::FontSize, + EditableField::LineHeight, EditableField::PaddingX, EditableField::PaddingY, EditableField::Shell, @@ -1238,6 +1267,7 @@ mod tests { let numeric_fields = [ EditableField::BackgroundOpacity, EditableField::FontSize, + EditableField::LineHeight, EditableField::PaddingX, EditableField::PaddingY, EditableField::ScrollbackHistory, diff --git a/src/settings_view/state_apply.rs b/src/settings_view/state_apply.rs index 763482cf..a761b980 100644 --- a/src/settings_view/state_apply.rs +++ b/src/settings_view/state_apply.rs @@ -12,6 +12,7 @@ impl SettingsWindow { | EditableField::BackgroundOpacity | EditableField::FontFamily | EditableField::FontSize + | EditableField::LineHeight | EditableField::PaddingX | EditableField::PaddingY => self.apply_appearance_field(field, value), EditableField::Shell @@ -92,6 +93,28 @@ impl SettingsWindow { &format!("{}", parsed), ) } + EditableField::LineHeight => { + let parsed = value + .parse::() + .map_err(|_| "Line height must be a number".to_string())?; + if !parsed.is_finite() { + return Err("Line height must be finite".to_string()); + } + if !(termy_config_core::MIN_LINE_HEIGHT..=termy_config_core::MAX_LINE_HEIGHT) + .contains(&parsed) + { + return Err(format!( + "Line height must be between {} and {}", + termy_config_core::MIN_LINE_HEIGHT, + termy_config_core::MAX_LINE_HEIGHT + )); + } + self.config.line_height = parsed; + config::set_root_setting( + termy_config_core::RootSettingId::LineHeight, + &parsed.to_string(), + ) + } EditableField::PaddingX => { let parsed = value .parse::() diff --git a/src/terminal_view/mod.rs b/src/terminal_view/mod.rs index 34f4f8af..5ce48efb 100644 --- a/src/terminal_view/mod.rs +++ b/src/terminal_view/mod.rs @@ -35,6 +35,7 @@ use std::{ use sysinfo::{ProcessesToUpdate, System, get_current_pid}; #[cfg(target_os = "macos")] use termy_auto_update::{AutoUpdater, UpdateState}; +use termy_config_core::{MAX_LINE_HEIGHT, MIN_LINE_HEIGHT}; use termy_search::SearchState; use termy_terminal_ui::{ CellRenderInfo, PaneTerminal, TabTitleShellIntegration, Terminal as NativeTerminal, @@ -2962,7 +2963,7 @@ impl TerminalView { mouse_scroll_multiplier: config.mouse_scroll_multiplier, pane_focus_effect: config.pane_focus_effect, pane_focus_strength: config.pane_focus_strength, - line_height: 1.4, + line_height: config.line_height.clamp(MIN_LINE_HEIGHT, MAX_LINE_HEIGHT), copy_on_select: config.copy_on_select, copy_on_select_toast: config.copy_on_select_toast, last_terminal_modifiers: gpui::Modifiers::default(), @@ -3274,6 +3275,7 @@ impl TerminalView { self.font_family = config.font_family.into(); self.base_font_size = config.font_size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE); self.font_size = px(self.base_font_size); + self.line_height = config.line_height.clamp(MIN_LINE_HEIGHT, MAX_LINE_HEIGHT); self.cursor_style = config.cursor_style; self.cursor_blink = config.cursor_blink; self.cursor_blink_visible = true; From 567948c86bb8d9b39c872572efa72309ec600d41 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Mon, 23 Mar 2026 17:40:23 -0300 Subject: [PATCH 2/5] fix: restore debug dock icon setup --- src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.rs b/src/main.rs index 34839ed8..db7a366e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -447,6 +447,10 @@ fn main() { }); application.run(move |cx: &mut App| { + // Set the dock icon for development builds (no .app bundle). + #[cfg(debug_assertions)] + termy_native_sdk::set_dock_icon_from_png(include_bytes!("../assets/termy_icon@1024px.png")); + spawn_deeplink_listener(cx, deeplink_rx); cx.on_action(|_: &OpenConfig, _cx| { From a890e7eee3a4968a74d66e2973486e5d600330d3 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Mon, 23 Mar 2026 17:40:48 -0300 Subject: [PATCH 3/5] fix: normalize line height persistence --- crates/config_core/src/lib.rs | 4 ++++ crates/config_core/src/schema.rs | 4 ++-- src/settings_view/mod.rs | 2 +- src/settings_view/state.rs | 6 ------ src/settings_view/state_apply.rs | 7 ++++--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/config_core/src/lib.rs b/crates/config_core/src/lib.rs index b79a2460..95ca3a7e 100644 --- a/crates/config_core/src/lib.rs +++ b/crates/config_core/src/lib.rs @@ -35,5 +35,9 @@ pub use types::{ TerminalScrollbarStyle, TerminalScrollbarVisibility, ThemeId, WorkingDirFallback, }; +pub fn format_line_height(value: f32) -> String { + format!("{value:.2}") +} + #[cfg(test)] mod parser_tests; diff --git a/crates/config_core/src/schema.rs b/crates/config_core/src/schema.rs index 447bf8c4..e9fa11df 100644 --- a/crates/config_core/src/schema.rs +++ b/crates/config_core/src/schema.rs @@ -479,7 +479,7 @@ pub fn root_setting_default_value(config: &AppConfig, id: RootSettingId) -> Opti RootSettingId::WindowHeight => Some(config.window_height.to_string()), RootSettingId::FontFamily => Some(config.font_family.clone()), RootSettingId::FontSize => Some(config.font_size.to_string()), - RootSettingId::LineHeight => Some(config.line_height.to_string()), + RootSettingId::LineHeight => Some(crate::format_line_height(config.line_height)), RootSettingId::CursorStyle => Some(match config.cursor_style { CursorStyle::Line => "line".to_string(), CursorStyle::Block => "block".to_string(), @@ -649,7 +649,7 @@ mod tests { let defaults = AppConfig::default(); assert_eq!( root_setting_default_value(&defaults, RootSettingId::LineHeight), - Some(defaults.line_height.to_string()) + Some(crate::format_line_height(defaults.line_height)) ); } } diff --git a/src/settings_view/mod.rs b/src/settings_view/mod.rs index aa52899d..17af7ed5 100644 --- a/src/settings_view/mod.rs +++ b/src/settings_view/mod.rs @@ -21,7 +21,7 @@ use std::time::{Duration, Instant}; use termy_command_core::CommandId; use termy_config_core::{ RootSettingId, RootSettingValueKind, SettingsSection as CoreSettingsSection, - color_setting_from_key, color_setting_specs, root_setting_default_value, + color_setting_from_key, color_setting_specs, format_line_height, root_setting_default_value, root_setting_enum_choices, root_setting_from_key, root_setting_specs, root_setting_value_kind, }; diff --git a/src/settings_view/state.rs b/src/settings_view/state.rs index 1720a2c7..fba0d890 100644 --- a/src/settings_view/state.rs +++ b/src/settings_view/state.rs @@ -4,12 +4,6 @@ const MAX_THEME_SUGGESTIONS: usize = 16; const MAX_FONT_SUGGESTIONS: usize = 200; const PANE_FOCUS_MAX: f32 = 2.0; -/// Format a line-height multiplier for display and persistence (two decimal -/// places, e.g. `"1.40"`). -fn format_line_height(value: f32) -> String { - format!("{value:.2}") -} - #[cfg_attr(target_os = "windows", allow(dead_code))] #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(super) enum EditableField { diff --git a/src/settings_view/state_apply.rs b/src/settings_view/state_apply.rs index a761b980..a28c2605 100644 --- a/src/settings_view/state_apply.rs +++ b/src/settings_view/state_apply.rs @@ -109,11 +109,12 @@ impl SettingsWindow { termy_config_core::MAX_LINE_HEIGHT )); } - self.config.line_height = parsed; config::set_root_setting( termy_config_core::RootSettingId::LineHeight, - &parsed.to_string(), - ) + &format_line_height(parsed), + )?; + self.config.line_height = parsed; + Ok(()) } EditableField::PaddingX => { let parsed = value From 9f1f8e303ce44e3c4ad1f290aae6c868187a37d1 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Mon, 23 Mar 2026 17:41:30 -0300 Subject: [PATCH 4/5] style: format grid text batch call --- crates/terminal_ui/src/grid.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/terminal_ui/src/grid.rs b/crates/terminal_ui/src/grid.rs index a032a29d..d173ca20 100644 --- a/crates/terminal_ui/src/grid.rs +++ b/crates/terminal_ui/src/grid.rs @@ -717,11 +717,7 @@ impl TerminalGrid { Self::push_pending_text_batch(&mut current, &mut ops); current = Some(TextBatch::new( - cell.col, - cell.row, - cell.char, - key, - underline, + cell.col, cell.row, cell.char, key, underline, )); } From 59b59e4d26bfeae5d8eefd8711747488d2158972 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Mon, 23 Mar 2026 17:49:01 -0300 Subject: [PATCH 5/5] fix: persist stepped line height after config write --- src/settings_view/state.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/settings_view/state.rs b/src/settings_view/state.rs index fba0d890..cbda67b1 100644 --- a/src/settings_view/state.rs +++ b/src/settings_view/state.rs @@ -991,11 +991,14 @@ impl SettingsWindow { EditableField::LineHeight => { let next = (self.config.line_height + (delta as f32 * step.delta)) .clamp(step.min, step.max); - self.config.line_height = next; - config::set_root_setting( + let result = config::set_root_setting( termy_config_core::RootSettingId::LineHeight, &format_line_height(next), - ) + ); + if result.is_ok() { + self.config.line_height = next; + } + result } EditableField::PaddingX => { let next = @@ -1278,6 +1281,14 @@ mod tests { assert_eq!(spec.codec, FieldCodec::Numeric); assert!(spec.numeric_step.is_some()); } + + let line_height_spec = SettingsWindow::field_spec(EditableField::LineHeight); + let step = line_height_spec + .numeric_step + .expect("missing line-height step"); + assert!((step.delta - 0.05).abs() < f32::EPSILON); + assert!((step.min - termy_config_core::MIN_LINE_HEIGHT).abs() < f32::EPSILON); + assert!((step.max - termy_config_core::MAX_LINE_HEIGHT).abs() < f32::EPSILON); } #[test]