默认深色改用参照微信的炭灰,不再是近黑
- ShellRS Dark 的界面和终端:底色 #1e1e1f,标题栏、标签栏、状态栏 #2f2f30,边框和悬停 #3b3b3d,文字 #e0e0e4;gpui-kit 的每级灰按对照表换掉,彩色不动 - 次要文字 #929296,比微信的亮一些,对比度保持在 4.5 以上 - 终端选区和 ANSI 黑按新底色重算;「界面跟随主题」关闭时的深色界面同样生效 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd3d41c496
commit
baa6745c1a
6 files changed
+105
-31
No files matched your search
@@ -68,7 +68,8 @@ ShellRS(crate 与二进制都叫 `shellrs`)是 Xshell / WinSCP 式的 SSH
|
||||
- 远程文件原地改写:截断后写原文件,不走 `.filepart` 加改名。保存前核对,被改过就问「覆盖」。
|
||||
- **设置 › 应用 › 窗口**:「记住窗口大小」(含最大化)和「记住窗口位置」两个开关,默认都开,下次启动生效。屏幕不在了或窗口不在屏幕上就居中,比屏幕大就缩小;全屏不记。
|
||||
- **终端通知**:只做程序请求的(OSC 9 / 777)和响铃,不做 OSC 133 长命令通知:那要每台服务器装 shell 集成,收益不抵成本。不在前台发系统通知,在前台而终端在别的标签发应用内通知;响铃在终端就在眼前时不提醒。
|
||||
- **主题**:「应用外观」(浅色 / 深色 / 跟随系统)决定深浅;「外观 › 主题」左右两栏里浅色、深色各选一套,按当前外观生效。组里第一项「界面跟随主题」默认关闭:界面是中性的黑白灰(gpui-kit 默认主题),只有终端按主题配色;打开后界面也按主题配色。用户试过默认跟随,觉得中性界面更合适,别改回去。页面「重置」对卡片和开关各管各的。
|
||||
- **主题**:「应用外观」(浅色 / 深色 / 跟随系统)决定深浅;「外观 › 主题」左右两栏里浅色、深色各选一套,按当前外观生效。组里第一项「界面跟随主题」默认关闭:界面是中性的灰(浅色是 gpui-kit 默认主题,深色见下条),只有终端按主题配色;打开后界面也按主题配色。用户试过默认跟随,觉得中性界面更合适,别改回去。
|
||||
- 默认深色(ShellRS Dark)不用 gpui-kit 的近黑,用参照微信深色的炭灰:底色 `#1e1e1f`,标题栏、标签栏、状态栏 `#2f2f30`,边框和悬停 `#3b3b3d`,文字 `#e0e0e4`。用户嫌近黑刺眼。次要文字比微信的 `#7b7b7f` 亮一些(`#929296`),保持对比度 ≥ 4.5。页面「重置」对卡片和开关各管各的。
|
||||
- 左栏只列浅色主题,右栏只列深色主题;卡片是名称加示例输出,选中的整张加底色,右上角是蓝底(主题的 `blue`)对勾。
|
||||
- 只有内置的,浅色、深色各 10 套,默认 ShellRS Light / Dark(底色与界面一致),Solarized 第二,Tokyo Day / Tokyo Night 第三。同一主题的浅、深两版在两栏同一行,没有另一版的(Rosé Pine Dawn、Everforest Light、Dracula、Nord)排最后。只收宽松许可(MIT、Apache-2.0)的配色。不做自定义主题、导入、按主机指定。
|
||||
- **键盘快捷键**:设置左栏单独一个分类,排在「关键字高亮」下面,分通用、标签、终端、编辑器四组。
|
||||
@@ -294,7 +295,7 @@ cargo test -- --ignored # 会读写真实钥匙串的测试
|
||||
- **清屏只在本地模拟器里做**,全屏程序运行时不做。终端里的 ⌘K 是清屏,比全局的 `FocusSearch` 深一层,所以优先。
|
||||
- **终端字体走全局量 `TerminalFont`**(终端模块不依赖 settings),格子在下一帧 prepaint 时按新尺寸重排。
|
||||
- **终端颜色走全局量 `TerminalColors`**(`theme.rs`):`settings::apply` 按当前外观设成那一栏选中的主题,系统外观变化时也重设。终端格子、边距、选区、链接、颜色查询(OSC 4 / 10 / 11 / 12)和设置里的两个预览都从它取,不用界面主题的 `background` / `foreground`;查找高亮仍用界面的 `warning`。主题的 `key` 存进 `settings.json`,发布后不能改;读不出或深浅不符的 key 退回该外观的默认。
|
||||
- **界面配色由所选主题推出**(`settings/app_theme.rs`):从 gpui-kit 默认的浅色或深色主题出发重新着色。灰色按它在默认背景与文字之间的位置,换到该主题背景与文字之间;默认里用作可读文字的灰色(对比度 ≥ 4.5)至少保留该主题正文对比度的 80%(最高 4.5)。`base.*` 按名字换成对应的 ANSI 色,其余彩色按色相换;`.light` 和彩色的 `*foreground` 删掉,交给 gpui-kit 自己推。ShellRS Light / Dark 和「界面跟随主题」关闭时都直接用 gpui-kit 的默认主题(`TerminalThemeSettings::app_theme`)。`settings::apply` 按名字(`theme_name`)判断要不要重建,把结果放进 `Theme` 的浅色或深色槽再 `Theme::change`。
|
||||
- **界面配色由所选主题推出**(`settings/app_theme.rs`):从 gpui-kit 默认的浅色或深色主题出发重新着色。灰色按它在默认背景与文字之间的位置,换到该主题背景与文字之间;默认里用作可读文字的灰色(对比度 ≥ 4.5)至少保留该主题正文对比度的 80%(最高 4.5)。`base.*` 按名字换成对应的 ANSI 色,其余彩色按色相换;`.light` 和彩色的 `*foreground` 删掉,交给 gpui-kit 自己推。ShellRS Light(以及「界面跟随主题」关闭时的浅色,`TerminalThemeSettings::app_theme`)直接用 gpui-kit 的默认浅色主题;ShellRS Dark(以及关闭时的深色)是 gpui-kit 默认深色主题按 `SOFT_DARK` 把每级灰换成炭灰,彩色不动。`settings::apply` 按名字(`theme_name`)判断要不要重建,把结果放进 `Theme` 的浅色或深色槽再 `Theme::change`。
|
||||
- **链接**:正则找出的地址和 OSC 8 标出的都算,只认 http / https(`links.rs`);OSC 8 的格子优先于正则。一直画成蓝色(终端主题的 ANSI 蓝,悬停时亮蓝;界面主题的 `link` 和正文同色)加下划线,⌘ / Ctrl 单击才打开,普通单击照旧选文字。
|
||||
- **鼠标上报**(`mouse.rs` 是纯编码):按住 Shift 不上报,右键始终是自己的菜单。写入走 `engine.write`,不算用户输入。
|
||||
- **关键字高亮只叠加在显示上**(`highlight.rs`)。`snapshot()` 每帧对可见行把折行接成逻辑行,用 `regex` crate 匹配,结果写进 `TerminalCell.highlight`,prepaint 改文字色和粗细;不存坐标,所以缩放重排、回滚都不用失效处理。
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ ShellRS 同一时间只运行一个:已经开着时再打开一次,会把已
|
||||
|
||||
- **外观 → 常规 → 界面语言**:跟随系统、简体中文、English,默认简体中文。英文界面尚在翻译中,目前只有 GPUI Kit 组件自带的文字(如搜索框提示)会切换。
|
||||
- **外观 → 常规 → 应用外观**:深色、浅色、跟随系统,默认跟随系统;选「跟随系统」时随系统的深浅色切换。标题栏的主题按钮也会改写这一项。
|
||||
- **外观 → 主题**:左栏是浅色主题,右栏是深色主题,各选一套:应用外观为浅色时用左栏选中的那套,深色时用右栏的;跟随系统时随系统一起切换,标题栏的主题按钮切换时也一样。默认只有终端按所选主题配色,界面保持中性的黑白灰;打开上方的「界面跟随主题」,界面也按主题配色:侧栏、标签栏、对话框等用主题的底色和文字色,成功、警告、错误等状态用主题的绿、黄、红。每张卡片用该主题的颜色显示一段示例,选中的右上角有蓝色对勾。默认是 ShellRS Light 和 ShellRS Dark,即原来的界面配色。内置浅色、深色各 10 套:浅色 ShellRS Light、Solarized Light、Tokyo Day、Catppuccin Latte、Flexoki Light、One Half Light、Gruvbox Light、Kanagawa Lotus、Rosé Pine Dawn、Everforest Light;深色 ShellRS Dark、Solarized Dark、Tokyo Night、Catppuccin Mocha、Flexoki Dark、One Half Dark、Gruvbox Dark、Kanagawa Wave、Dracula、Nord。同一主题的浅色版和深色版在两栏的同一行。改动立即作用于所有已打开的终端,字体和关键字高亮的预览也用当前主题;vim 等程序询问终端的背景色时,得到的是当前主题的颜色。**尚未实现:自定义主题、导入配色、按主机指定主题。**
|
||||
- **外观 → 主题**:左栏是浅色主题,右栏是深色主题,各选一套:应用外观为浅色时用左栏选中的那套,深色时用右栏的;跟随系统时随系统一起切换,标题栏的主题按钮切换时也一样。默认只有终端按所选主题配色,界面保持中性的灰色调(深色是不刺眼的炭灰,不是纯黑);打开上方的「界面跟随主题」,界面也按主题配色:侧栏、标签栏、对话框等用主题的底色和文字色,成功、警告、错误等状态用主题的绿、黄、红。每张卡片用该主题的颜色显示一段示例,选中的右上角有蓝色对勾。默认是 ShellRS Light 和 ShellRS Dark,与界面同色。内置浅色、深色各 10 套:浅色 ShellRS Light、Solarized Light、Tokyo Day、Catppuccin Latte、Flexoki Light、One Half Light、Gruvbox Light、Kanagawa Lotus、Rosé Pine Dawn、Everforest Light;深色 ShellRS Dark、Solarized Dark、Tokyo Night、Catppuccin Mocha、Flexoki Dark、One Half Dark、Gruvbox Dark、Kanagawa Wave、Dracula、Nord。同一主题的浅色版和深色版在两栏的同一行。改动立即作用于所有已打开的终端,字体和关键字高亮的预览也用当前主题;vim 等程序询问终端的背景色时,得到的是当前主题的颜色。**尚未实现:自定义主题、导入配色、按主机指定主题。**
|
||||
- **终端 → 字体配置**:字体(只列出本机安装的等宽字体,默认是系统的等宽字体,macOS 上为 Menlo)、字号(10–28 像素,默认 13)、行高(字号的 1.0–2.0 倍,默认 1.54,即原来的 20 像素行高)。改动立即作用于所有已打开的终端;在终端里按 ⌘= / ⌘-(其他平台 Ctrl)也能逐级调字号,⌘0 回到默认。下方的预览按终端的排法显示字母、数字、易混字符、符号、中文、制表符和一行命令。设置文件里的字体在本机没有安装时,使用默认字体。
|
||||
- **关键字高亮**:总开关、预览和规则表,终端里按规则给文字上色、匹配时提醒,见「终端」一节。
|
||||
- **键盘快捷键**:主要功能的快捷键,分通用、标签、终端、编辑器四组,每行是功能名和它的按键。点按键进入「按下组合键」,按下新的组合就换上;Esc 或点别处放弃。行尾两个按钮:禁用(之后显示「无」,再点一下恢复默认)、恢复默认;页面上方的「重置」也能逐项恢复。改动立即生效,工具栏提示和右键菜单里显示的按键跟着变。
|
||||
|
||||
+91
-18
@@ -3,7 +3,8 @@
|
||||
//! keeps its place between the default's background and text, now between
|
||||
//! the theme's; each color becomes the theme's ANSI color of its hue. What
|
||||
//! gpui-kit derives from these (hover, active, tab bar, status bar …)
|
||||
//! follows. The theme's own default is gpui-kit's theme unchanged.
|
||||
//! follows. ShellRS Light is gpui-kit's light theme unchanged, ShellRS Dark
|
||||
//! its dark theme with the grays lifted off black.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -15,7 +16,7 @@ use crate::terminal::TerminalTheme;
|
||||
/// The name the app's theme has while `theme` is chosen, to tell whether it
|
||||
/// is already in effect without building it again.
|
||||
pub(super) fn ui_theme_name(theme: &'static TerminalTheme, cx: &App) -> SharedString {
|
||||
if is_default(theme) {
|
||||
if is_gpui_kits(theme) {
|
||||
default_config(theme.mode(), cx).name.clone()
|
||||
} else {
|
||||
theme.name().into()
|
||||
@@ -25,14 +26,78 @@ pub(super) fn ui_theme_name(theme: &'static TerminalTheme, cx: &App) -> SharedSt
|
||||
/// The app's theme while `theme` is chosen.
|
||||
pub(super) fn ui_theme(theme: &'static TerminalTheme, cx: &App) -> Rc<ThemeConfig> {
|
||||
let default = default_config(theme.mode(), cx);
|
||||
if is_default(theme) {
|
||||
return default.clone();
|
||||
if is_gpui_kits(theme) {
|
||||
default.clone()
|
||||
} else if theme == TerminalTheme::default_for(ThemeMode::Dark) {
|
||||
Rc::new(soften(default, theme))
|
||||
} else {
|
||||
Rc::new(recolor(default, theme))
|
||||
}
|
||||
Rc::new(recolor(default, theme))
|
||||
}
|
||||
|
||||
fn is_default(theme: &'static TerminalTheme) -> bool {
|
||||
theme == TerminalTheme::default_for(theme.mode())
|
||||
/// ShellRS Light: the app is gpui-kit's light theme as it is.
|
||||
fn is_gpui_kits(theme: &'static TerminalTheme) -> bool {
|
||||
theme == TerminalTheme::default_for(ThemeMode::Light)
|
||||
}
|
||||
|
||||
/// ShellRS Dark's grays, from WeChat's dark mode, which is easy on the eyes
|
||||
/// where gpui-kit's near black glares: each of gpui-kit's neutral grays (by
|
||||
/// value) and what it becomes. Grays between two are mixed from them.
|
||||
const SOFT_DARK: [(u8, u32); 13] = [
|
||||
// Overlays stay black.
|
||||
(0x00, 0x000000),
|
||||
// neutral-950: the background, sidebars, dialogs.
|
||||
(0x0a, 0x1e1e1f),
|
||||
// neutral-900: the title, tab and status bars.
|
||||
(0x17, 0x2f2f30),
|
||||
// neutral-800: borders, hover, selected tabs and rows.
|
||||
(0x26, 0x3b3b3d),
|
||||
(0x40, 0x48484a),
|
||||
// neutral-600: scrollbars.
|
||||
(0x52, 0x626263),
|
||||
// neutral-500: the focus ring.
|
||||
(0x73, 0x7b7b7f),
|
||||
// neutral-400: secondary text, lighter than WeChat's to stay readable.
|
||||
(0xa3, 0x929296),
|
||||
(0xd4, 0xc4c4c8),
|
||||
(0xe5, 0xd2d2d6),
|
||||
(0xf5, 0xdcdce0),
|
||||
// neutral-50: the text.
|
||||
(0xfa, 0xe0e0e4),
|
||||
(0xff, 0xe8e8ec),
|
||||
];
|
||||
|
||||
/// ShellRS Dark: gpui-kit's dark theme with its grays moved onto
|
||||
/// `SOFT_DARK`. Its colors (selection, success, danger …) stay gpui-kit's.
|
||||
fn soften(default: &ThemeConfig, theme: &TerminalTheme) -> ThemeConfig {
|
||||
let mut config = serde_json::to_value(default).expect("a theme is plain data");
|
||||
config["name"] = theme.name().into();
|
||||
config["is_default"] = false.into();
|
||||
if let Some(colors) = config["colors"].as_object_mut() {
|
||||
for value in colors.values_mut() {
|
||||
let Some(color) = value.as_str().and_then(|text| try_parse_color(text).ok()) else {
|
||||
continue;
|
||||
};
|
||||
let color = color.to_rgb();
|
||||
if is_gray(color) {
|
||||
*value = hex(soft_gray(color)).into();
|
||||
}
|
||||
}
|
||||
}
|
||||
serde_json::from_value(config).expect("written from a theme")
|
||||
}
|
||||
|
||||
fn soft_gray(color: Rgba) -> Rgba {
|
||||
let value = gray_value(color) * 255.;
|
||||
let pair = SOFT_DARK
|
||||
.windows(2)
|
||||
.find(|pair| value <= f32::from(pair[1].0))
|
||||
.unwrap_or(&SOFT_DARK[SOFT_DARK.len() - 2..]);
|
||||
let ((from, from_color), (to, to_color)) = (pair[0], pair[1]);
|
||||
let t = (value - f32::from(from)) / f32::from(to - from);
|
||||
let mut soft = mix(rgb(from_color), rgb(to_color), t);
|
||||
soft.a = color.a;
|
||||
soft
|
||||
}
|
||||
|
||||
fn default_config(mode: ThemeMode, cx: &App) -> &Rc<ThemeConfig> {
|
||||
@@ -300,24 +365,32 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui_kit::test]
|
||||
fn the_default_themes_are_gpui_kits_own(cx: &mut TestAppContext) {
|
||||
fn the_light_default_is_gpui_kits_own(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
gpui_kit::component::init(cx);
|
||||
let white = cx.theme().background;
|
||||
apply(&TerminalTheme::all()[1], cx);
|
||||
assert_ne!(cx.theme().background, white);
|
||||
for mode in [ThemeMode::Light, ThemeMode::Dark] {
|
||||
apply(TerminalTheme::default_for(mode), cx);
|
||||
assert_eq!(
|
||||
cx.theme().theme_name().as_ref(),
|
||||
match mode {
|
||||
ThemeMode::Light => "Default Light",
|
||||
ThemeMode::Dark => "Default Dark",
|
||||
}
|
||||
);
|
||||
}
|
||||
apply(TerminalTheme::default_for(ThemeMode::Light), cx);
|
||||
assert_eq!(cx.theme().theme_name().as_ref(), "Default Light");
|
||||
assert_eq!(cx.theme().background, white);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui_kit::test]
|
||||
fn the_dark_default_is_charcoal_with_gpui_kits_colors(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
gpui_kit::component::init(cx);
|
||||
apply(TerminalTheme::default_for(ThemeMode::Dark), cx);
|
||||
let app = cx.theme();
|
||||
assert_eq!(app.theme_name().as_ref(), "ShellRS Dark");
|
||||
// Bars a step lighter than the background, borders another.
|
||||
assert_close(app.background.to_rgb(), color(0x1e1e1f), "background");
|
||||
assert_close(app.title_bar.to_rgb(), color(0x2f2f30), "title bar");
|
||||
assert_close(app.border.to_rgb(), color(0x3b3b3d), "border");
|
||||
assert_close(app.foreground.to_rgb(), color(0xe0e0e4), "text");
|
||||
assert_close(app.selection.to_rgb(), color(0x1d4ed8), "selection");
|
||||
assert_close(app.danger.to_rgb(), color(0xf87171), "danger");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ impl Default for TerminalThemeSettings {
|
||||
|
||||
impl TerminalThemeSettings {
|
||||
/// The theme the app's colors come from for `mode`: the chosen one, or
|
||||
/// the default, which is gpui-kit's own, when the app does not follow.
|
||||
/// the default, ShellRS Light or Dark, when the app does not follow.
|
||||
pub fn app_theme(&self, mode: ThemeMode) -> &'static TerminalTheme {
|
||||
if self.app_follows {
|
||||
self.theme(mode)
|
||||
|
||||
@@ -170,19 +170,19 @@ const SHELLRS_LIGHT: TerminalTheme = TerminalTheme {
|
||||
],
|
||||
};
|
||||
|
||||
/// The app's dark theme around a terminal: `neutral-950`, `neutral-50`
|
||||
/// The app's dark theme around a terminal: its charcoal background and soft
|
||||
/// text and cursor, the `selection` over the background, with ANSI colors
|
||||
/// light enough to read on it.
|
||||
/// light enough to read on it; black is the app's borders.
|
||||
const SHELLRS_DARK: TerminalTheme = TerminalTheme {
|
||||
key: "shellrs-dark",
|
||||
name: "ShellRS Dark",
|
||||
mode: ThemeMode::Dark,
|
||||
foreground: 0xfafafa,
|
||||
background: 0x0a0a0a,
|
||||
cursor: 0xfafafa,
|
||||
selection: 0x101e48,
|
||||
foreground: 0xe0e0e4,
|
||||
background: 0x1e1e1f,
|
||||
cursor: 0xe0e0e4,
|
||||
selection: 0x1e2c56,
|
||||
ansi: [
|
||||
0x262626, 0xf87171, 0x4ade80, 0xfacc15, 0x60a5fa, 0xe879f9, 0x22d3ee, 0xd4d4d4, 0x737373,
|
||||
0x3b3b3d, 0xf87171, 0x4ade80, 0xfacc15, 0x60a5fa, 0xe879f9, 0x22d3ee, 0xd4d4d4, 0x737373,
|
||||
0xfca5a5, 0x86efac, 0xfde047, 0x93c5fd, 0xf0abfc, 0x67e8f9, 0xfafafa,
|
||||
],
|
||||
};
|
||||
@@ -610,7 +610,7 @@ mod tests {
|
||||
let dark = TerminalTheme::default_for(ThemeMode::Dark);
|
||||
assert_eq!(
|
||||
dark.query_color(NamedColor::Background as usize),
|
||||
rgb(0x0a0a0a)
|
||||
rgb(0x1e1e1f)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -373,7 +373,7 @@ fn terminal_themes_follow_the_appearance_and_a_card_chooses_one(cx: &mut TestApp
|
||||
let neutral = |cx: &mut TestAppContext| {
|
||||
cx.update(|cx| {
|
||||
let name = cx.theme().theme_name().clone();
|
||||
name == "Default Light" || name == "Default Dark"
|
||||
name == "Default Light" || name == "ShellRS Dark"
|
||||
})
|
||||
};
|
||||
assert!(neutral(cx));
|
||||
|
||||
Reference in new issue
Block a user