From 45ac9c6ec49b6142eca7324f741a7c72fc7d4bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Wed, 14 Oct 2026 20:52:17 -0400 Subject: [PATCH] R4.13: round 1 fixes; app language leads Han layout - ely-ui passes its resolved locale to Windows text layout, so Chinese UI lays out as Chinese whatever the Windows region. - Component a4bf696: Find box, sort and filter builders and password tooltips read the catalog; stricter shipped-copy scan. - ElyMark deletes the right-clicked picture (delete_image(at)). Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 4 +- Cargo.toml | 2 +- agents.md | 8 ++-- apps/ely-mark/src/blocks.rs | 4 +- apps/ely-mark/src/view/menu.rs | 20 +++++---- crates/ely-ui/Cargo.toml | 3 ++ crates/ely-ui/src/i18n.rs | 2 + scripts/fetch-references.sh | 2 +- tasks/TASKS.md | 2 +- tasks/reviews/R4.13-r1.md | 7 ++++ vendor/gpui-pre-windows/src/direct_write.rs | 46 ++++++++++++++++++++- vendor/gpui-pre-windows/src/gpui_windows.rs | 1 + 12 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 tasks/reviews/R4.13-r1.md diff --git a/Cargo.lock b/Cargo.lock index ffdca06..4d37913 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1923,7 +1923,7 @@ dependencies = [ [[package]] name = "ely-gpui-component" version = "0.1.1" -source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components?rev=6df9ab0aa6cba99e6a79da80f79fc166f09890a8#6df9ab0aa6cba99e6a79da80f79fc166f09890a8" +source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components?rev=a4bf6965f9dd4802ca37cb3da5d338b360a169d9#a4bf6965f9dd4802ca37cb3da5d338b360a169d9" dependencies = [ "alacritty_terminal", "anyhow", @@ -2187,6 +2187,7 @@ dependencies = [ "futures", "gpui-pre", "gpui-pre-platform", + "gpui-pre-windows", "log", "notify", "semver", @@ -2981,6 +2982,7 @@ dependencies = [ "waker-fn", "web-time", "windows 0.62.2", + "zed-font-kit", "zed-scap", ] diff --git a/Cargo.toml b/Cargo.toml index f544f8a..294bf44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ jiff = "0.2" anyhow = "1" directories = "6" dunce = "1" -ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components", rev = "6df9ab0aa6cba99e6a79da80f79fc166f09890a8" } +ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components", rev = "a4bf6965f9dd4802ca37cb3da5d338b360a169d9" } env_logger = "0.11" gpui = { package = "gpui-pre", version = "=0.3.8", default-features = false, features = ["stacker"] } gpui_platform = { package = "gpui-pre-platform", version = "=0.3.8", features = ["font-kit", "wayland", "x11"] } diff --git a/agents.md b/agents.md index 69ba1ec..bda1a07 100644 --- a/agents.md +++ b/agents.md @@ -40,7 +40,7 @@ site/ 官网(Vite 8、pnpm、TypeScript,无框架;等轴 SVG 积木 ## 组件映射 -依赖 `ely-gpui-component`(pin 6df9ab0,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。 +依赖 `ely-gpui-component`(pin a4bf696,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。 | 需要 | 用 | | --- | --- | @@ -84,7 +84,7 @@ site/ 官网(Vite 8、pnpm、TypeScript,无框架;等轴 SVG 积木 | `A11yChecker` 的 `audit` 与对照表是私有的;Omarchy 色板要照同一套规则 | `ely-ui` 的 `omarchy/palette.rs` 抄了对照表。上游待办:公开 `audit` | | 组件无 role/aria 语义:菜单、列表等控件在 AX 树仍不可见 | 上游已修核心控件(7380afb:Button、TextInput、Tabs、面板行、对话框等);其余上游待办。项目自绘控件已自带 | | gpui 无 aria-disabled API(`AriaProperties` 无 disabled 字段) | 组件 7380afb 起以本地化 aria_description 表达;上游待办:gpui 补 API | -| 组件内部文案未接 i18n(Ely 用到的组件已接:提示、默认占位、补全空行、字号示例,6df9ab0,`i18n/tests/shipped.rs` 防回退;其余组件之外:欢迎页标题、菜单项、`SortBuilder` 与 `FilterBuilder` 等) | 面板族文案已接 i18n(7380afb,内建 en/zh-CN),`CommentSidebar` 与 `RelativeTime` 同(408f2b0);其余上游待办 | +| 组件内部文案未接 i18n(Ely 用到的组件已接:提示、默认占位、补全空行、字号示例,6df9ab0;查找框、排序与筛选、密码开关,a4bf696;`i18n/tests/shipped.rs` 防回退;其余组件之外:欢迎页标题、菜单项等) | 面板族文案已接 i18n(7380afb,内建 en/zh-CN),`CommentSidebar` 与 `RelativeTime` 同(408f2b0);其余上游待办 | | LOK 的 `.uno:StyleApply` 状态给界面名(`Default Paragraph Style`),样式列表给程序名(`Standard`),两者对不上 | ElyDoc 属性栏:列表外的当前样式显示为占位文字;更新样式用状态里的界面名。上游待办:状态带程序名 | ## gpui 规则 @@ -197,7 +197,7 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely - 2026-10-04:ElyDoc 的标签、会话、打开、保存、另存为(按文件身份比对)、关闭询问、冲突、密码与标签条移入 `crates/ely-office`:应用实现 `Host`(格式表 `Kind`、每标签附加数据、内核回报钩子),共用逻辑以扩展 trait 提供(`use ely_office::prelude::*`)。ElyTable 用同一外壳。共用文案在 `office.*`。 - 2026-10-04:ElyTable(C09.1)。新建为空白 XLSX(首个工作表名随界面语言),保存保留 xlsx/ods/xls/fods,另存为 XLSX(默认)或 ODS,xlsm/xltm 进风险模式。上方名称框与公式栏(回车分别发 `.uno:GoToCell` 与 `.uno:EnterString`,Esc 交回网格),中间行列表头按内核几何自绘(点选整行整列,拖边改尺寸),下方工作表标签(右键与“工作表”菜单:插入、重命名、复制、左右移、隐藏、显示、删除;删除前列出引用该表的公式数与命名区域数)与状态栏(保存状态、内核给的选区统计)。“单元格”菜单:插删行列与单元格、合并拆分(多个数据格时先问移入或保留隐藏)、行高列宽(厘米)与最适、隐藏显示行列、冻结。填充柄拖到目标格发 `.uno:AutoFill`。选区统计文字由内核给出,当前为英文(内核界面语言未随 Ely 切换),待处理。 - 2026-10-04:ElyTable 公式、计算、名称(C09.2,TABLE-03 至 06)。公式语法为 Excel A1(用户定):`Sheet2!A1`、逗号分隔参数,由安装层 `packaging/libreoffice/ely.xcd` 设定;载入选项 `Language=en-US,DeviceFormFactor=desktop`,内核界面与数字区域都是 en-US(小数点、月/日/年输入)。单元格内编辑由内核完成:键入时内核给出匹配的函数(`editor::CompletionMenu` 显示在单元格下,上下键选,Tab 补全,经 `completeFunction` 入有序队列),参数提示(`editor::SignatureHelp`,取内核的用法文字),点选单元格插入引用并按内核颜色画框;内核只为点选的引用与再次编辑(F2)的已有公式画框,新键入的引用不画(同桌面版)。第一下 Esc 关提示,第二下取消(同内核)。语法错误的公式照原样输入,状态栏按界面语言解释当前格的错误(内核英文错误串换成界面文案,同统计);循环引用同此提示。“公式”菜单:自动计算开关(关时状态栏显示手动计算)、重新计算、全部重新计算、定义名称(名称、引用位置默认为所选区域的绝对引用、范围为工作簿或某表;名称先经内核判定)、名称列表(改引用、转到所命名的绝对区域、删除,失效引用标出;内核给出每个名称的区域,`.uno:SelectedRange` 给所选区域)、追踪错误、追踪引用与从属单元格、移去箭头。名称框可直接输入名称跳转,跳转后显示该名称。手动计算在 ods、xlsx、xls、fods 存后重开都保持。 -- 2026-10-04:ElyTable 格式、数据整理、规则(C09.3,TABLE-07 至 09)。补丁 `0016-lok-calc-formats-and-conditions.patch`(数字格式、条件格式、两处 sdi 参数)、`0017-lok-calc-filter-validation-columns.patch`(筛选、验证、分列)、`0018-lok-calc-reads-requests-search.patch`(读值、请求、查找全部):数字格式按代码设(`.uno:NumberFormat` 的 `FormatCode`),`.uno:NumberFormatCode` 读当前格代码与类别(常规按区域块首判定)或校验代码;标准筛选收 JSON 条件(`.uno:DataFilterStandardFilter` 的 `Query`,含按列取值),`.uno:AutoFilterValues` 照内核自己的筛选菜单给出条目、他列隐藏与本列勾选;数据验证收 JSON 规则(`.uno:Validation` 的 `Rule`),`.uno:Validation` 读当前格规则,`.uno:DataSelectValues` 给序列;条件格式收 JSON(单元格规则配内核单元格样式、色阶、数据条、按键删除,`cellsh.sdi` 的同名槽也加参数),`.uno:ConditionalFormats` 列出本表;分列收 `Options`(ScAsciiOptions 串);`.uno:SelectedRange` 另给所选区域内数据区的四角、首行文字与内核的标题判定。LOK 下内核不再弹自己的筛选菜单与序列下拉,改发 `LOK_CALLBACK_TOOLTIP`(`autofilter`、`datalist`,带列行),无效输入发 `validityerror`(标题、内容、样式、是否接受;接受与否取内核对话框的默认:信息接受,警告与停止拒绝),Ely 以 `ely_lok::Request` 接收后自建菜单与提示。查找全部按格报矩形(内核原把相邻命中并成一块,计数偏少)。查找替换移入 `ely-office`(`Finding`,ElyDoc 与 ElyTable 共用,`Host::search_leaves` 供 ElyDoc 记返回位置)。“粘贴为值/公式”只在系统剪贴板仍是本视图上次的复制时进行(内核自己的单元格剪贴板;Ely 复制的 HTML 末尾带一个进程内唯一的标记注释,以标记而不是文字识别,同样文字的外来复制不算);其他内容(含别的工作簿复制的单元格)明确拒绝并提示改用粘贴:内核无法从外来内容单取值或公式。排序最多三个关键字(内核限制)。右侧格式面板(数字、对齐、边框、填充)、“数据”菜单(排序、筛选、删除重复项、分列、查找替换、粘贴为值或公式、数据验证)、“格式”菜单(格式面板开关、条件格式)。组件库 `SortBuilder`、`FilterBuilder` 的文案未接 i18n,中文界面下仍为英文(上游待办)。 +- 2026-10-04:ElyTable 格式、数据整理、规则(C09.3,TABLE-07 至 09)。补丁 `0016-lok-calc-formats-and-conditions.patch`(数字格式、条件格式、两处 sdi 参数)、`0017-lok-calc-filter-validation-columns.patch`(筛选、验证、分列)、`0018-lok-calc-reads-requests-search.patch`(读值、请求、查找全部):数字格式按代码设(`.uno:NumberFormat` 的 `FormatCode`),`.uno:NumberFormatCode` 读当前格代码与类别(常规按区域块首判定)或校验代码;标准筛选收 JSON 条件(`.uno:DataFilterStandardFilter` 的 `Query`,含按列取值),`.uno:AutoFilterValues` 照内核自己的筛选菜单给出条目、他列隐藏与本列勾选;数据验证收 JSON 规则(`.uno:Validation` 的 `Rule`),`.uno:Validation` 读当前格规则,`.uno:DataSelectValues` 给序列;条件格式收 JSON(单元格规则配内核单元格样式、色阶、数据条、按键删除,`cellsh.sdi` 的同名槽也加参数),`.uno:ConditionalFormats` 列出本表;分列收 `Options`(ScAsciiOptions 串);`.uno:SelectedRange` 另给所选区域内数据区的四角、首行文字与内核的标题判定。LOK 下内核不再弹自己的筛选菜单与序列下拉,改发 `LOK_CALLBACK_TOOLTIP`(`autofilter`、`datalist`,带列行),无效输入发 `validityerror`(标题、内容、样式、是否接受;接受与否取内核对话框的默认:信息接受,警告与停止拒绝),Ely 以 `ely_lok::Request` 接收后自建菜单与提示。查找全部按格报矩形(内核原把相邻命中并成一块,计数偏少)。查找替换移入 `ely-office`(`Finding`,ElyDoc 与 ElyTable 共用,`Host::search_leaves` 供 ElyDoc 记返回位置)。“粘贴为值/公式”只在系统剪贴板仍是本视图上次的复制时进行(内核自己的单元格剪贴板;Ely 复制的 HTML 末尾带一个进程内唯一的标记注释,以标记而不是文字识别,同样文字的外来复制不算);其他内容(含别的工作簿复制的单元格)明确拒绝并提示改用粘贴:内核无法从外来内容单取值或公式。排序最多三个关键字(内核限制)。右侧格式面板(数字、对齐、边框、填充)、“数据”菜单(排序、筛选、删除重复项、分列、查找替换、粘贴为值或公式、数据验证)、“格式”菜单(格式面板开关、条件格式)。组件库 `SortBuilder`、`FilterBuilder` 的文案 a4bf696 起接 i18n(原为英文)。 - 2026-10-04:右侧属性栏宽取 `theme.sheet_size()`(380),左栏仍是 `sidebar_width`。`PropertyGrid` 名称列固定 120,240 宽时编辑器只剩约 76。颜色经色块弹出 `ColorPalette`,一次点击一条命令。 - 2026-10-01:MailCore 2 的子模块 libetpan 过旧,改钉 libetpan master `4d47862`。 - 2026-10-01:每个应用的全部数据在 `<本机数据目录>/ElyOffice//`,路径由 `ely_platform::data_dir` 给出。卸载只动这里,且先问。用户文档永不碰。 @@ -294,5 +294,5 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely - 2026-10-14(Gitea #1,Linux Wayland 报):ElyShow 左侧缩略图条滚轮不滚。根因:列表包在 `ContextMenu` 宿主里,宿主无尺寸,列表随内容长高、从不溢出,滚轮无处可滚(右栏正常);宿主改为 `size_full`(同 ElyPDF 的三处),`tests/strip.rs` 以 24 张幻灯片核对滚轮滚到最后一张。各应用其余 `ContextMenu` 宿主已核:ElyPDF 三处本有尺寸,ElyTable 的工作表标签条与下拉选项不是滚动列表。 - 2026-10-14:ElyMark 的菜单与快捷键(R4.9,MARK-15)。项与键取自 Typora 1.14.10 主进程字节码的字符串表(`.work/typora-main-menus.txt`)与其 `zh-Hans.lproj/Menu.json`;实际大小在字节码里有 Ctrl+Shift+9 与 Ctrl+Shift+0 两串,照用户所定用 Ctrl+Shift+0,R4.11 在真窗口核对。表格是“段落”的子菜单(照 Typora),顶层不再有“表格”。原 Ctrl+1/2/3 切模式、Ctrl+=/-/0 字号、Ctrl+Shift+F 专注让给 Typora 的键;可视、源码、预览留在“视图”菜单,无键。Ctrl+/ 另绑在 `ElyEditor` 上(gpui 同深度后绑者胜),盖过代码编辑器的 `//` 注释切换(Markdown 没有这种注释);`MarkdownDocument` 的键上下文只在可视模式有。带 Shift 的标点:Windows 写键名(`ctrl-shift-[`),macOS 与 Linux 写字符(`cmd-{`),见 `blocks::shifted`。Ctrl+Enter 在打开的块里原是输入框的 Submit(文档不处理),改绑为下方插入行。拒绝一律成信息提示:当场的由 `Desk::edit` 说,后台读出的经 `DocumentEvent::EditRefused`、`view::Refused` 到宿主。菜单打开时,打开的块把焦点借给菜单,块与选区都留着,菜单命令照样作用于所选(组件 6d90351)。复制为 Markdown 与粘贴为纯文本只作用于文档:焦点在文档外的输入框(查找框、对话框)时交给该框自己的复制、粘贴。表格子菜单末项为删除表格(组件 `TableEdit::Delete`,一步撤销,插入点不在表格里即拒绝;在打开的块里删除后焦点回到文档,撤销键照用,组件 6c2d28c)。Typora 有而组件库暂缺的不进菜单:行内公式、上下标、注释、提示块子菜单、任务状态、列表缩进 Ctrl+[/]、代码工具。 - 2026-10-14:ElyMark 的右键菜单(R4.10)。项、次序与显隐规则取自 Typora 1.14.10 的 `resources/window.html`(`#context-menu` 与其子菜单)与 `frame.js` 的显隐函数,记在 `.work/R4.10/typora-context-menu.txt`。菜单在 `view/menu.rs`,`MarkView` 自持(`ely_office::menu::Offer` 与 `ContextMenu::manual`):可视模式由组件的 `DocumentEvent::Pointed` 开在指针处,源码与预览由视图自己的右键开。剪切、复制、粘贴、删除所选范围为图标行,按下即把其键交回菜单打开前的焦点(`dispatch_keystroke`),与键盘同一路径;无选区时剪切、复制、删除灰显。其余项经各自的动作,显示所绑的键。组件 013f142、1b5fb07:文本框与代码编辑器右键落在选区里时保留选区,否则把插入点移到所点处;可视模式打开的块右键不写回、不关闭,目标在后台按全文读出(块外的引用定义照样解析);`MarkdownDocument::delete_image`(删除图片,一步撤销,无图片即 `EditRefusal::NoImage`);可视无块时 Ctrl+V 粘贴文字(原只收图片,文字被丢弃)。复制为 HTML 代码的图片照原地址(`Pictures::Written`,同 Typora),复制为富文本照旧内嵌。“编辑”菜单的复制项照 Typora 次序:纯文本、Markdown、HTML 代码、富文本。Typora 有而不做的:缩进与减少缩进行(可视只在打开的块里有)、复制表格、代码自动缩进、图片上传、缩放、移动与语法切换、拼写、表情复制、检查元素。gpui 的测试只发按下,菜单在 Enter 松开时选中,测试另发松开(`blocks/context.rs` 的 `choose`)。 -- 2026-10-14(用户报:整个软件中文显示问题很多):中文显示(R4.13)。Windows 上 gpui 以用户区域(本机为 en-US)排版全部文字,DirectWrite 按日文为汉字选回退字体,日文字体缺的简体字再落到另一字体,同一个词里字形与粗细不一(ElyTree 的“打开仓库”:“打开”粗、“仓库”细)。`vendor/gpui-pre-windows` 的 `text_locale`:用户区域不是中日韩时,改用 Windows 语言列表(WinRT `GlobalizationPreferences::Languages`,Win32 的界面语言只给 en-US)里第一个中日韩语言排版(日志 `text: laid out as …`);在 Windows 上打中文须把中文加进该列表,所以它就是用户读的汉字。只换排版的区域,字体族名与字体列表仍按用户区域。读不出语言列表即记警告、照用户区域。组件库 6df9ab0:Ely 用到的组件里写死的英文(下拉的“Choose…”、清除、关闭、移除、取消固定的提示,公式补全的“No suggestions”,设置里字号的重置提示与示例句)改走内建中英目录。仍为英文的是内核的文字(LibreOffice 界面语言固定英文:幻灯片占位“Click to add Title”、母版“Default”、配色名、数字格式代码“General”、函数名),以及启动失败对话框(出现在读入语言之前,中英并列)。 +- 2026-10-14(用户报:整个软件中文显示问题很多):中文显示(R4.13)。Windows 上 gpui 以用户区域(本机为 en-US)排版全部文字,DirectWrite 按日文为汉字选回退字体,日文字体缺的简体字再落到另一字体,同一个词里字形与粗细不一(ElyTree 的“打开仓库”:“打开”粗、“仓库”细)。`vendor/gpui-pre-windows` 的 `text_locale`:用户区域不是中日韩时,改用 Windows 语言列表(WinRT `GlobalizationPreferences::Languages`,Win32 的界面语言只给 en-US)里第一个中日韩语言排版(日志 `text: laid out as …`);在 Windows 上打中文须把中文加进该列表,所以它就是用户读的汉字。只换排版的区域,字体族名与字体列表仍按用户区域。读不出语言列表即记警告、照用户区域。Ely 的界面语言是中文时(设置或 `ELY_LOCALE`),`ely_ui::i18n::install_as` 经 `gpui_windows::set_text_language` 让排版随它,不论 Windows 区域(日文区域下“打开仓库”原为 Yu Gothic UI 600 与 Microsoft JhengHei UI 700 两种字);语言下次启动生效,故启动时设一次。组件库 6df9ab0:Ely 用到的组件里写死的英文(下拉的“Choose…”、清除、关闭、移除、取消固定的提示,公式补全的“No suggestions”,设置里字号的重置提示与示例句)改走内建中英目录。a4bf696:查找框(计数、三个选项与上下一处的提示、替换与全部替换)、排序与筛选(添加规则、满足全部或任一、条件、升降序、添加排序)、密码框的显示与隐藏提示同此;筛选条件以目录键为值(`Test::key`);ElyMark 的删除图片按右键所点的字节(`delete_image(at)`)。仍为英文的是内核的文字(LibreOffice 界面语言固定英文:幻灯片占位“Click to add Title”、母版“Default”、配色名、数字格式代码“General”、函数名),以及启动失败对话框(出现在读入语言之前,中英并列)。 - 2026-10-14(用户定:dmg 不再显示未公证):macOS 发布以 Developer ID Application 证书签名、经 `notarytool` 公证并钉票。证书与公证凭据(`xcrun notarytool store-credentials`,App 专用密码只在钥匙串里)装在构建用的 Mac 上;发布流程的 macOS 任务从仓库变量 `ELY_SIGN_IDENTITY`(证书全名)与 `ELY_NOTARY_PROFILE`(凭据名)取得,`ci.sh` 在 macOS 上缺任一即失败,不出未公证的包。本机手动出包同样设这两个变量再跑 `packaging/macos/release.sh`。 diff --git a/apps/ely-mark/src/blocks.rs b/apps/ely-mark/src/blocks.rs index b95242f..a3a35c2 100644 --- a/apps/ely-mark/src/blocks.rs +++ b/apps/ely-mark/src/blocks.rs @@ -46,8 +46,7 @@ actions!( Hyperlink, InsertImage, ClearFormat, - DeleteBlock, - DeleteImage + DeleteBlock ] ); @@ -230,7 +229,6 @@ impl Desk { InsertImage => |d, cx| d.format(F::Image, cx), ClearFormat => |d, cx| d.format(F::Clear, cx), DeleteBlock => |d, cx| d.block(B::Delete, cx), - DeleteImage => |d, cx| d.delete_image(cx), ) } } diff --git a/apps/ely-mark/src/view/menu.rs b/apps/ely-mark/src/view/menu.rs index acabb28..30a6b1b 100644 --- a/apps/ely-mark/src/view/menu.rs +++ b/apps/ely-mark/src/view/menu.rs @@ -90,16 +90,14 @@ impl MarkView { true => "mark.image.browser", false => "mark.image.location", }; - let view = view.clone(); + let (shown, dropped, at) = (view.clone(), view.clone(), target.at); rows = rows.row(MenuItem::new(said(key, cx)).on_click(move |_, cx| { - view.update(cx, |view, cx| view.show_picture(&url, cx)) + shown.update(cx, |view, cx| view.show_picture(&url, cx)) + })) + .row(MenuItem::new(said("mark.image.delete", cx)).on_click(move |_, cx| { + dropped.update(cx, |view, cx| view.delete_image(at, cx)) })) - .row(run( - said("mark.image.delete", cx), - Box::new(b::DeleteImage), - window, - )) .line(); } if let Some(url) = &target.link { @@ -162,6 +160,14 @@ impl MarkView { rows.menu() } + /// Deletes the picture at byte `at`, or says why not. + fn delete_image(&mut self, at: usize, cx: &mut Context) { + let deleted = (self.document).update(cx, |document, cx| document.delete_image(at, cx)); + if let Err(why) = deleted { + cx.emit(super::Refused(why)); + } + } + /// Shows a local picture's file, or opens a web one. fn show_picture(&mut self, url: &str, cx: &mut Context) { if web(url) { diff --git a/crates/ely-ui/Cargo.toml b/crates/ely-ui/Cargo.toml index dc15829..ad26943 100644 --- a/crates/ely-ui/Cargo.toml +++ b/crates/ely-ui/Cargo.toml @@ -19,6 +19,9 @@ log.workspace = true notify.workspace = true serde_json.workspace = true +[target.'cfg(target_os = "windows")'.dependencies] +gpui_windows = { package = "gpui-pre-windows", version = "=0.3.8" } + [features] test-support = [] diff --git a/crates/ely-ui/src/i18n.rs b/crates/ely-ui/src/i18n.rs index a4d3d7c..a079122 100644 --- a/crates/ely-ui/src/i18n.rs +++ b/crates/ely-ui/src/i18n.rs @@ -147,6 +147,8 @@ pub fn install(cx: &mut App) { /// Installs the catalogs speaking `locale` (en-US or zh-CN). pub fn install_as(locale: &'static str, cx: &mut App) { + #[cfg(target_os = "windows")] + gpui_windows::set_text_language(locale); let (en, zh) = catalogs(); cx.set_global( I18n::new(locale) diff --git a/scripts/fetch-references.sh b/scripts/fetch-references.sh index 56e991f..3f9ce57 100755 --- a/scripts/fetch-references.sh +++ b/scripts/fetch-references.sh @@ -36,7 +36,7 @@ fetch() { # name url rev [sparse paths...] echo "fetch $name $rev" } -fetch Ely-GPUI-Components https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git 6df9ab0aa6cba99e6a79da80f79fc166f09890a8 +fetch Ely-GPUI-Components https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git a4bf6965f9dd4802ca37cb3da5d338b360a169d9 fetch zed https://github.com/zed-industries/zed.git 1a28cff4b409169bac058bca40dfbfeb7621d19b fetch libreoffice-core https://github.com/LibreOffice/core.git 8221e31b3ac356a1623c672912a3d2b492f7e3d1 \ include/LibreOfficeKit libreofficekit desktop/source/lib desktop/inc \ diff --git a/tasks/TASKS.md b/tasks/TASKS.md index be3ff48..730bc0c 100644 --- a/tasks/TASKS.md +++ b/tasks/TASKS.md @@ -241,7 +241,7 @@ - [~] R4.7 组件库:块命令(标题 1–6、段落、标题升降一级;引用、有序、无序、任务列表的切换;插入分隔线、代码块、公式块、目录、front matter、脚注、链接定义、前后空段落;删除块),一步撤销,拒绝说明原因。组件 58e212d 至 0558f0c(已推送):`MarkdownDocument::block` 照 Typora 的 `changeBlock`、`changeHeadingLevel`、`insertBlock`、`toggleIndent`、`insertParagraph`、`deleteBlock`,与格式共用一个后台执行器与 `EditRefusal`;同级标题保持不变(照 Typora 菜单),代码上的标题命令、任务首行的标题与引用拒绝(GFM 读作文字),前后空段落写成空行,越出打开的块先写回(多一步撤销)。审查三轮未过(`tasks/reviews/R4.7-r1..r3.md`):r1 十三项(标题标记里插入崩溃、任务框丢勾、列表里提示块拆开、拆分时行内标记断开、缩进代码改类型、列表项与脚注的前置段落、删除使邻段相接、尾部 `#` 与块起始符、任务首行、跨列表的选区、同级标题当开关);r2 十二项(拆分后重开标记成块语法、闭合 `#`、标记下一行的任务框、提示块的懒行与空行、标记与正文同行、Tab 缩进代码、任务列表包代码、引用里删除、拆分剩下的块起始符、长注释),r2 起标题与拆分写出前重新解析核对;r3 九项:核对忽略代码里的空白、按 mdast 而非可视读法核对(下划线、表情漏判)、提示块正文块失去缩进、按内容认围栏、非 ASCII 空白算缩进、去掉任务框露出块起始符、续行任务框解除时留下、空标题转段落误拒、转义的提示标记误拒。r3 已修:核对经 `MarkdownReader` 照绘制读(代码与公式里的空白计入),提示块的标记按提示读法判定、正文块保留缩进,围栏按源码结构认,缩进只数 ASCII 空格,去掉任务框时转义露出的起始符(续行同),空标题可转空段落;七项回归在 r3 的源码上失败(`.work/R4.7-fix3/red.md`);轮数用尽,未再审。遗留:列表与引用的切换不重新解析核对,只逐项修正 - [~] R4.8 组件库:表格行列上下左右移动、整理表格源码;代码块取代码内容;与已有的增删行列同一套拒绝。组件 bcee87c 至 6b033f3(已推送):`TableEdit` 加上下移行(表头与首行互换,超出表头列数的行不作表头,到边拒绝为 `TableRefusal::Edge`)、左右移列、整理;表格按显示宽度补齐(`unicode-width`,Tab 记 4、控制字符记 0,照 Typora 的 `wcswidth`),写回表格自身的换行;表格编辑并入后台执行器;右键代码块时 `DocumentTarget` 带出代码文字。审查三轮未过(`tasks/reviews/R4.8-r1..r3.md`):r1 两项(纯 CR 文本里整理表格把前文当作容器前缀、行内 Tab 宽度不合 Typora);r2 一项(End 后插入表格拆开 CRLF);r3 一项(r2 的修法让行辅助函数返回光标之前的位置,End 后的块命令崩溃)。r3 已修:编辑器只按 LF 分行,光标可落在 CR 与 LF 之间;执行器与插入表格在入口把选区移到 CR 前,行辅助函数改为断言不收这种位置;两项回归在 r3 的源码上失败(`.work/R4.8-fix3/red.md`);轮数用尽,未再审。遗留:编辑器自身的 CRLF 模型不变,End 仍落在 CR 之后;表情宽度按 `unicode-width` 记 2 列(Typora 记 1) - [x] R4.9 ElyMark:“段落”“格式”菜单与 Typora 同项同键(Ctrl+1–6、Ctrl+0、Ctrl+=/-、Ctrl+B/I/U、Ctrl+Shift+`、Alt+Shift+5、Ctrl+K、Ctrl+Shift+I、Ctrl+\、Ctrl+T、Ctrl+Shift+K/M/Q/[/]);源码模式 Ctrl+/;字号 Ctrl+Shift+=/-/0;专注 F8、打字机 F9;表格菜单加移动行列;复制为 Markdown Ctrl+Shift+C、粘贴为纯文本 Ctrl+Shift+V。ElyMark 5392062、f84f990、09fcb0b,组件 6d90351、24ffc45、6c2d28c(已推送),pin 6c2d28c:键与菜单项取自 Typora 字节码与其中文菜单;表格为“段落”子菜单,加删除表格;拒绝成提示;菜单借打开的块的焦点,块与选区留着;复制为 Markdown、粘贴为纯文本只作用于文档。R1 FAIL 六项、R2 FAIL 四项已修,R3 PASS -- [ ] R4.13 全部应用:中文显示(用户报,2026-10-14):Windows 上同一词里字形粗细不一(gpui 以 en-US 排版,汉字按日文回退);Ely 用到的组件里写死的英文改走中英目录;先于 R4.10 的修复做 +- [ ] R4.13 全部应用:中文显示(用户报,2026-10-14):Windows 上同一词里字形粗细不一(gpui 以 en-US 排版,汉字按日文回退);Ely 用到的组件里写死的英文改走中英目录;先于 R4.10 的修复做。第 1 轮未过(三项:排版语言不随 Ely 的中文设置、查找框英文、排序筛选英文),已修,见 `.work/R4.13-fix1` - [ ] R4.10 ElyMark:右键菜单照 Typora 按目标变化:剪贴板行(剪切、复制、粘贴、删除)、复制/粘贴为 ▸(Markdown、HTML 代码、纯文本、粘贴为纯文本)、格式行、段落 ▸ 或表格 ▸ 或代码项、插入 ▸、链接(打开、复制地址)、图片(打开所在位置、删除图片)、删除块;预览只读时只留复制类 - [x] R4.12 全部应用:已开着时再打开文件,交给运行中的那份开成新标签并带到前台,不再弹“已在运行”(用户报,2026-10-14);先于 R4.11 做。第 1 轮 4 项:令牌被拒时连接重置使重试失效、慢速来者占住门、主窗口关后仍回执、ElyMail 与 ElyAgent 不到前台;已修,各有回归;第 2 轮通过 - [ ] R4.11 验收:真窗口逐项操作,每项功能可用(不求与 Typora 一一相同);帮助、PRD、`agents.md` 随之;逐条核对表 diff --git a/tasks/reviews/R4.13-r1.md b/tasks/reviews/R4.13-r1.md new file mode 100644 index 0000000..91e1919 --- /dev/null +++ b/tasks/reviews/R4.13-r1.md @@ -0,0 +1,7 @@ +FAIL + +1. `vendor/gpui-pre-windows/src/direct_write.rs:1865` — 排版语言不跟随应用选择的简体中文。Windows 用户区域为 `ja-JP` 时,这里直接保留日文;即使 Ely 设置为简体中文或指定 `ELY_LOCALE=zh-CN`,所有中文标签仍按日文选字体。隔离副本用真实 DirectWrite 和组件的 Inter SemiBold 排版“打开仓库”:`ja-JP` 得到前两字 Yu Gothic UI、字重 600,后两字 Microsoft JhengHei UI、字重 700;`zh-CN` 则四字均为 Microsoft YaHei UI、字重 600,原问题仍可复现。应让中文界面的实际语言到达排版层,并覆盖系统区域与应用语言不一致的测试;仅验证 `han()` 的语言前缀不能发现此问题。证据:`.work/R4.13-r1/probes.log`。 + +2. `Cargo.toml:22` — 新 pin 的 `FindWidget` 只翻译了关闭提示,查找界面仍有大量固定英文(组件 `src/editor/find.rs:179`、`:325`;ElyMark 在 `apps/ely-mark/src/finding.rs:186` 使用,ElyPDF 在 `apps/ely-pdf/src/search.rs:188` 使用)。简体中文下打开查找,无匹配仍显示 `No results`,有匹配显示 `1 of 2`,展开替换仍显示 `Replace`、`Replace all`;大小写、全词及前后匹配的提示也仍是英文。设置 `I18n::new("zh-CN")` 后的渲染探针直接记录到这些文字。应在共享组件中将计数、按钮和提示全部接入中英目录,再更新 pin;增加中文渲染断言,现有 `shipped.rs` 扫描在这些英文实际显示时仍通过。证据:`.work/R4.13-r1/probes.log`、`.work/R4.13-r1/component-and-windows-baseline.log`。 + +3. `apps/ely-table/src/data/dialogs.rs:61` — ElyTable 实际使用的排序、筛选组件仍未完成中文显示,`agents.md:87` 记录的这项缺口属于 R4.13 要求覆盖的已使用组件。简体中文下选中数据并打开排序,仍显示 `Add sort`、`Ascending`、`Descending`;打开筛选并添加两条规则,仍显示 `Add rule`、`Match all`、`Match any` 和 `contains` 等条件。组件 `src/tables/builders.rs:227`、`:246`、`:368`、`:398` 及 `src/tables/rules.rs:29` 保留了这些英文,渲染探针已复现。应将这些显示文字接入组件的中英目录,保留条件的内部标识,更新 pin 与缺口记录,并覆盖按钮、分段选项和条件列表的中文渲染测试。证据:`.work/R4.13-r1/probes.log`。 diff --git a/vendor/gpui-pre-windows/src/direct_write.rs b/vendor/gpui-pre-windows/src/direct_write.rs index fa8216c..a27bc18 100644 --- a/vendor/gpui-pre-windows/src/direct_write.rs +++ b/vendor/gpui-pre-windows/src/direct_write.rs @@ -40,6 +40,16 @@ pub(crate) struct DirectWriteTextSystem { state: RwLock, } +/// The app's Han language, set before any window opens. +static APP_LANGUAGE: RwLock> = RwLock::new(None); + +/// Han layout follows `tag` when it is Han. +pub fn set_text_language(tag: &str) { + let chosen = han(tag).then(|| HSTRING::from(tag)); + log::info!("text: the app's language is {tag}; Han layout follows it: {}", chosen.is_some()); + *APP_LANGUAGE.write() = chosen; +} + struct DirectWriteComponents { locale: HSTRING, /// Text's language; picks Han glyphs and fonts. @@ -559,7 +569,7 @@ impl DirectWriteState { font_info.font_face.GetStyle(), DWRITE_FONT_STRETCH_NORMAL, font_size.as_f32(), - &components.text_locale, + APP_LANGUAGE.read().as_ref().unwrap_or(&components.text_locale), )? .cast()?; if let Some(ref fallbacks) = font_info.fallbacks { @@ -1859,7 +1869,6 @@ const fn make_direct_write_tag(tag_name: &str) -> DWRITE_FONT_FEATURE_TAG { DWRITE_FONT_FEATURE_TAG(make_open_type_tag(tag_name)) } -#[inline] /// The user's locale, or their first Han language. fn text_locale(user: &HSTRING) -> HSTRING { if han(&user.to_string()) { @@ -1900,8 +1909,41 @@ mod han_tests { assert!(!super::han(tag), "{tag}"); } } + + /// Family and weight of each run laying out `text`. + fn faces(system: &super::DirectWriteTextSystem, text: &str) -> Vec<(String, u32)> { + use gpui::PlatformTextSystem as _; + let font = gpui::Font { weight: gpui::FontWeight::SEMIBOLD, ..gpui::font("Segoe UI") }; + let font_id = system.font_id(&font).expect("Segoe UI"); + let run = gpui::FontRun { len: text.len(), font_id }; + let layout = system.layout_line(text, gpui::px(16.), &[run]); + let state = system.state.read(); + let mut faces: Vec<(String, u32)> = Vec::new(); + for run in &layout.runs { + let info = &state.fonts[run.font_id.0]; + let face = (info.font_family_h.to_string(), unsafe { info.font_face.GetWeight() }.0 as u32); + if !faces.contains(&face) { + faces.push(face); + } + } + faces + } + + #[test] + fn the_apps_chinese_leads_layout_under_a_japanese_user() { + let mut system = super::DirectWriteTextSystem::new(None).expect("DirectWrite"); + system.components.text_locale = windows::core::HSTRING::from("ja-JP"); + super::set_text_language("en-US"); + let japanese = faces(&system, "打开仓库"); + assert!(japanese.len() > 1, "ja-JP should split the label: {japanese:?}"); + super::set_text_language("zh-CN"); + let chinese = faces(&system, "打开仓库"); + super::set_text_language("en-US"); + assert_eq!(chinese.len(), 1, "one face for the label: {chinese:?}"); + } } +#[inline] fn get_name(string: IDWriteLocalizedStrings, locale: &HSTRING) -> Result { let mut locale_name_index = 0u32; let mut exists = BOOL(0); diff --git a/vendor/gpui-pre-windows/src/gpui_windows.rs b/vendor/gpui-pre-windows/src/gpui_windows.rs index e7a45a0..14c2317 100644 --- a/vendor/gpui-pre-windows/src/gpui_windows.rs +++ b/vendor/gpui-pre-windows/src/gpui_windows.rs @@ -38,6 +38,7 @@ pub(crate) use vsync::*; pub(crate) use window::*; pub(crate) use wrapper::*; +pub use direct_write::set_text_language; pub use platform::WindowsPlatform; pub(crate) use windows::Win32::Foundation::HWND;