diff --git a/docs/lua-configuration.md b/docs/lua-configuration.md index bd0a4a6..77ac7bd 100644 --- a/docs/lua-configuration.md +++ b/docs/lua-configuration.md @@ -105,6 +105,30 @@ Files successfully loaded through `require` are added to the live-reload watch list. Extra local files can be registered with `pebrel.add_to_config_reload_watch_list(path)`. +## Terminal Selection Colors (TOML Compatibility) + +For a legacy `pebrel.toml` configuration, the GPUI terminal accepts explicit +selection foreground and background colors in `#rrggbb` or `0xrrggbb` form: + +```toml +[colors.selection] +foreground = "#102030" +background = "#e5e9f0" +``` + +Previously the GPUI adapter loaded the selection background but ignored the +foreground, which could leave selected text unreadable against a custom +background. It also cleared a custom foreground when applying a theme such as +Paper that does not declare selection colors. Both values now survive loading +and themes that do not override them. A theme that explicitly declares selection +colors, such as Nord, retains its existing precedence. + +Missing or invalid foreground colors keep the existing fallback. Relative +`CellForeground` / `CellBackground` values are not added by this change. The +regression tests cover parsing and theme precedence; this does not change the +answer reader's selection overlay, formula selection layout, or the default +selection contrast. No new setting, dependency, or per-frame work is introduced. + ## Platform Values The module exposes stable runtime paths and platform data: diff --git a/nebula_app/src/gpui_shell/config.rs b/nebula_app/src/gpui_shell/config.rs index e87af53..3f98075 100644 --- a/nebula_app/src/gpui_shell/config.rs +++ b/nebula_app/src/gpui_shell/config.rs @@ -270,7 +270,9 @@ fn apply_theme(palette: &mut Palette, theme: nebula_settings::ThemeName) { if let Some(selection) = exact.selection_background { palette.selection = rgba8(selection); } - palette.selection_foreground = exact.selection_foreground.map(rgba8); + if let Some(selection_foreground) = exact.selection_foreground { + palette.selection_foreground = Some(rgba8(selection_foreground)); + } return; } if term.is_light { @@ -552,6 +554,7 @@ fn build_palette(raw: &RawColors) -> Palette { // 用户显式选区色保持主应用的不透明语义。 palette.selection = selection; } + palette.selection_foreground = raw.selection.foreground.as_deref().and_then(parse_rgb); let ansi8 = |group: &RawAnsi8| -> [Option; 8] { [ @@ -604,7 +607,10 @@ fn build_palette(raw: &RawColors) -> Palette { #[cfg(test)] mod tests { - use super::{Settings, apply_theme, resolve_ui_language, rgba8, runtime_background}; + use super::{ + RawColors, Settings, apply_theme, build_palette, resolve_ui_language, rgba8, + runtime_background, + }; use crate::display::UiLanguage; use crate::gpui_shell::terminal::colors::Palette; use nebula_settings::{LanguagePref, ThemeName}; @@ -652,6 +658,68 @@ mod tests { assert_eq!(palette.selection_foreground, None); } + #[test] + fn explicit_selection_colors_are_loaded_together() { + let raw: RawColors = toml::from_str( + r##" + [selection] + foreground = "#102030" + background = "0xe5e9f0" + "##, + ) + .unwrap(); + let palette = build_palette(&raw); + + assert_eq!(palette.selection_foreground, Some(rgba8([0x10, 0x20, 0x30]))); + assert_eq!(palette.selection, rgba8([0xe5, 0xe9, 0xf0])); + } + + #[test] + fn invalid_or_relative_selection_foreground_keeps_default() { + for foreground in ["#invalid", "#fff", "CellForeground", "CellBackground"] { + let raw: RawColors = toml::from_str(&format!( + "[selection]\nforeground = {foreground:?}\nbackground = \"#e5e9f0\"\n" + )) + .unwrap(); + let palette = build_palette(&raw); + + assert_eq!(palette.selection_foreground, None, "{foreground}"); + assert_eq!(palette.selection, rgba8([0xe5, 0xe9, 0xf0])); + } + } + + #[test] + fn theme_without_selection_colors_preserves_user_foreground() { + let foreground = rgba8([0x10, 0x20, 0x30]); + let background = rgba8([0xe5, 0xe9, 0xf0]); + for theme in [ThemeName::Nebula, ThemeName::Paper] { + let mut palette = Palette { + selection_foreground: Some(foreground), + selection: background, + ..Palette::default() + }; + + apply_theme(&mut palette, theme); + + assert_eq!(palette.selection_foreground, Some(foreground)); + assert_eq!(palette.selection, background); + } + } + + #[test] + fn theme_with_selection_colors_keeps_its_existing_precedence() { + let mut palette = Palette { + selection_foreground: Some(rgba8([1, 2, 3])), + selection: rgba8([4, 5, 6]), + ..Palette::default() + }; + + apply_theme(&mut palette, ThemeName::Nord); + + assert_eq!(palette.selection_foreground, Some(rgba8([0x2e, 0x34, 0x40]))); + assert_eq!(palette.selection, rgba8([0xe5, 0xe9, 0xf0])); + } + #[test] fn themes_apply_only_the_colors_they_declare() { let mut nord = Palette::default(); diff --git a/nebula_app/src/gpui_shell/terminal/colors.rs b/nebula_app/src/gpui_shell/terminal/colors.rs index cda7772..ec5b856 100644 --- a/nebula_app/src/gpui_shell/terminal/colors.rs +++ b/nebula_app/src/gpui_shell/terminal/colors.rs @@ -27,7 +27,7 @@ pub struct Palette { /// 选区叠加色。主应用默认是反色语义;本壳以半透明叠加近似, /// 用户显式配置 `colors.selection.background` 时用不透明具体色。 pub selection: Rgba, - /// 主题明确声明的选区文字色;普通 Nebula 主题保留原字色。 + /// 主题或用户明确声明的选区文字色;未声明时保留原字色。 pub selection_foreground: Option, /// ANSI 0-15。 pub ansi: [Rgba; 16],