feat: 支持单独设置中文终端字体

This commit is contained in:
GeekMr committed 2026-09-13 16:52:06 +08:00
1 parent ce068600b2
commit ae66b69e78
10 files changed
+68 -3

No files matched your search

+3
View File
@@ -77,6 +77,9 @@
"background_cover_description": "The image will extend beneath the title bar, window buttons, tabs, and SSH sidebar. Low-contrast images may make controls harder to see; the interface retains a minimum-opacity safeguard."
},
"font": {
"english": "English terminal font",
"chinese": "Chinese terminal font",
"chinese_description": "Used for Chinese and other full-width characters. Enter a font family or comma-separated families; leave empty to use the English font settings. Applies on Enter or leaving the field.",
"fallback_description": "The first family is preferred; missing glyphs fall back through the remaining families, with bundled Maple as the final safe fallback.",
"group_description": "The first family is primary; the rest are used in fallback order.",
"loading": "Enumerating system fonts...",
+3
View File
@@ -77,6 +77,9 @@
"background_cover_description": "背景图会延伸到标题栏、窗口按钮、Tab 与 SSH 侧栏下方,低对比度图片可能影响操作可见性;界面仍会保留最低不透明度保护。"
},
"font": {
"english": "英文终端字体",
"chinese": "中文终端字体",
"chinese_description": "用于中文等全宽字符。输入字体名称,多个字体以逗号分隔;留空时使用英文字体设置。按回车或离开输入框后生效。",
"fallback_description": "第 1 项优先显示;缺失字形依次使用后续字体,内置 Maple 是最终安全兜底。",
"group_description": "第 1 项是主字体,其余按顺序回退",
"loading": "正在枚举系统字体…",
+15
View File
@@ -42,6 +42,7 @@ pub struct Settings {
/// 已解析的界面语言。GPUI 组件只读这个内存全局,渲染路径不得重复读盘。
pub ui_language: UiLanguage,
pub font_family: String,
pub font_cjk: Option<[gpui::Font; 4]>,
pub font_bold_family: String,
pub font_italic_family: String,
pub font_bold_italic_family: String,
@@ -193,6 +194,20 @@ impl Settings {
cjk_bold_regular: runtime.cjk_bold_regular,
shell_id: runtime.shell.clone(),
font_family: normal_family,
font_cjk: runtime.font_family_cjk.as_deref().map(|family| {
use gpui::{FontStyle, FontWeight};
[
(FontWeight::NORMAL, FontStyle::Normal),
(FontWeight::BOLD, FontStyle::Normal),
(FontWeight::NORMAL, FontStyle::Italic),
(FontWeight::BOLD, FontStyle::Italic),
]
.map(|(weight, style)| gpui::Font {
weight,
style,
..crate::font_install::gpui_font_with_fallbacks(family)
})
}),
load_notice,
// 这里是唯一能正确合并「主题自带几何」与「用户显式覆盖」的地方:
// `theme` 已是 follow_system 折算后的**生效**主题,runtime 是同一次
@@ -175,6 +175,7 @@ pub struct SettingsPane {
/// GPUI text system 已注册的导入族名(启动扫描 + 本次导入累计)。
font_imported: Vec<String>,
font_family_input: Entity<InputState>,
font_family_cjk_input: Entity<InputState>,
/// 字体输入框上一帧的窗口坐标。字体目录是宽弹层,不能把整条设置行当
/// 锚点;否则输入框在右侧、菜单却会从正文左缘展开。
font_picker_trigger_bounds: Option<gpui::Bounds<gpui::Pixels>>,
@@ -96,11 +96,19 @@ impl SettingsPane {
let terminal = self
.group(language.pick("终端外观", "Terminal appearance"), cx)
.child(self.row(
language.pick("终端字体", "Terminal font"),
language.text(crate::i18n::Message::SettingsFontEnglish),
help("font_family", language),
font_picker,
cx,
))
.child(self.row(
language.text(crate::i18n::Message::SettingsFontChinese),
language.text(crate::i18n::Message::SettingsFontChineseDescription),
Input::new(&self.font_family_cjk_input)
.w(px(SETTINGS_SELECT_WIDTH))
.aria_label(language.text(crate::i18n::Message::SettingsFontChinese)),
cx,
))
.child(self.terminal_font_size_row(cx))
.child(self.select_row(
"cell_width_mode",
@@ -219,7 +219,7 @@ impl SettingsPane {
cx.stop_propagation();
cx.notify();
})))
.aria_label(language.pick("终端字体", "Terminal font")),
.aria_label(language.text(crate::i18n::Message::SettingsFontEnglish)),
)
// 弹层仍须取输入框的真实窗口坐标,才能正确处理滚动、缩放与 DPI。
.child(
@@ -246,7 +246,7 @@ impl SettingsPane {
return control.into_any_element();
}
self.row(
language.pick("终端字体", "Terminal font"),
language.text(crate::i18n::Message::SettingsFontEnglish),
help("font_family", language),
control,
cx,
@@ -439,6 +439,28 @@ impl SettingsPane {
},
));
let font_family_cjk_input = cx.new(|cx| {
InputState::new(window, cx)
.default_value(runtime.font_family_cjk.clone().unwrap_or_default())
});
subscriptions.push(cx.subscribe_in(
&font_family_cjk_input,
window,
|this: &mut Self, _, event: &InputEvent, window, cx| {
if matches!(event, InputEvent::PressEnter { .. } | InputEvent::Blur) {
let value = crate::font_install::normalize_font_family_chain(
&this.font_family_cjk_input.read(cx).value(),
);
this.font_family_cjk_input.update(cx, |input, cx| {
input.set_value(value.clone(), window, cx);
});
if this.runtime.font_family_cjk.as_deref().unwrap_or_default() != value {
this.persist(&[("font_family_cjk", value)], cx);
}
}
},
));
let bg_picker_hsv = {
let term = crate::gpui_shell::theme::chrome_theme_resolved(cx).palette().term_bg;
let rgb = runtime.background.unwrap_or([term.r, term.g, term.b]);
@@ -548,6 +570,7 @@ impl SettingsPane {
font_system: None,
font_imported: Vec::new(),
font_family_input,
font_family_cjk_input,
font_picker_trigger_bounds: None,
backup_selection: crate::encrypted_backup::BackupSelection::default(),
backup_pass_input: cx.new(|cx| {
@@ -497,8 +497,14 @@ impl Element for TerminalElement {
.try_global::<crate::gpui_shell::config::Settings>()
.map(|settings| settings.cjk_bold_regular)
.unwrap_or(true);
let cjk_fonts = cx
.try_global::<crate::gpui_shell::config::Settings>()
.and_then(|settings| settings.font_cjk.clone());
let pick_font = |bold: bool, italic: bool, wide: bool| {
let bold = bold && !(wide && cjk_bold_regular);
if let Some(fonts) = cjk_fonts.as_ref().filter(|_| wide) {
return fonts[usize::from(bold) + 2 * usize::from(italic)].clone();
}
match (bold, italic) {
(false, false) => font.clone(),
(true, false) => bold_font.clone(),
+5
View File
@@ -941,6 +941,7 @@ pub struct RuntimeSettings {
/// 系统外观变化时自动在主题家族的深浅成员间切换(默认关,尊重显式选择)。
pub follow_system_theme: bool,
pub font_family: Option<String>,
pub font_family_cjk: Option<String>,
/// **逻辑像素**(旧壳写盘语义:设置页 spinner 与 Ctrl+滚轮缩放持久化时
/// 已除以 scale factor)。`None` = 跟随 nebula.toml 的 `font.size`(pt)。
pub font_size_px: Option<f32>,
@@ -1081,6 +1082,7 @@ impl RuntimeSettings {
.unwrap_or_default(),
follow_system_theme: raw.bool_on("follow_system_theme").unwrap_or(false),
font_family: raw.value("font_family").map(str::to_owned),
font_family_cjk: raw.value("font_family_cjk").map(str::to_owned),
font_size_px: raw.f32("font_size").map(|size| size.clamp(4.0, 96.0)),
cursor_shape: raw.value("cursor_shape").and_then(CursorShapeName::from_settings),
cursor_blink: raw.bool_on("cursor_blink"),
@@ -1305,6 +1307,7 @@ mod tests {
completion_style=popup\n\
shell=pwsh\n\
font_family=Maple Mono Normal NF CN\n\
font_family_cjk=PingFang SC\n\
font_size=16.3\n\
cursor_shape=beam\n\
cursor_blink=1\n\
@@ -1339,6 +1342,7 @@ mod tests {
assert_eq!(settings.language, LanguagePref::ZhCn);
assert_eq!(settings.theme, ThemeName::SilverLight);
assert_eq!(settings.font_family.as_deref(), Some("Maple Mono Normal NF CN"));
assert_eq!(settings.font_family_cjk.as_deref(), Some("PingFang SC"));
// font_size 键存的是逻辑像素(旧壳写盘语义),不做 pt 换算。
assert_eq!(settings.font_size_px, Some(16.3));
assert_eq!(settings.cursor_shape, Some(CursorShapeName::Beam));
@@ -1381,6 +1385,7 @@ mod tests {
assert_eq!(settings.language, LanguagePref::System);
assert_eq!(settings.theme, ThemeName::Nord);
assert_eq!(settings.font_family, None);
assert_eq!(settings.font_family_cjk, None);
assert!(!settings.copy_on_select);
assert!(settings.multiline_paste_confirm, "多行粘贴确认默认开(上游兼容)");
assert!(settings.tab_close_visible, "标签关闭按钮默认可见(上游兼容)");
+1
View File
@@ -10,6 +10,7 @@ const RESET_KEYS: &[&str] = &[
"app_icon",
"follow_system_theme",
"font_family",
"font_family_cjk",
"font_size",
"cursor_shape",
"cursor_blink",