Skip to content

fix(desktop): Hover 弹窗渲染 Markdown 并贴近指针,移向弹窗时不再收回 - #1877

Merged
xiaoyumuxi merged 1 commit into
previewfrom
fix/hover-popup-markdown-sticky
Nov 14, 2026
Merged

xiaoyumuxi merged 1 commit into
previewfrom
fix/hover-popup-markdown-sticky

Conversation

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

关联 Issue / Linked issue

  • 无:问题由用户在会话中直接报告(附 IDEA/GoLand 对照截图),尚未建 Issue;需要时补建后改为 Closes #。

PR 类型 / Type

  • feat:新功能或用户可见的改进
  • fix:缺陷修复
  • docs:文档、Agent Note、Skill 等规则文件
  • release:稳定版发布准备

改动说明 / What changes

背景 / Why

编辑器里指针停在符号上弹出的 Hover 有三个问题:

  1. 语言服务器返回的 Markdown 原样显示,能看到 ```go、** 等源码,签名、正文、示例和链接挤在一起,宽度固定。
  2. 弹窗出现在指针右下方 (12, 18) 处,压住下一行,离指针所在行较远。
  3. 指针一旦从编辑器文档移进弹窗就立刻关闭;在同一个符号上挪动也会重新计时、重新请求,所以用户常常还没移进弹窗它就收回了。

本 PR 让 Hover 按 IDEA/GoLand 的样子渲染,贴着指针所在行显示,并且允许指针移进弹窗。

改动 / Changes

渲染(新文件 desktop/internal/workbench/pluginsemantics_hover.go)

  • 用 Viva 的 components.MarkdownView 渲染 Hover 内容:代码块、分隔线、标题、列表、行内代码、粗体、链接都按 Markdown 显示;正文用比例字体,代码用编辑器字体。
  • 代码块用编辑器同一套 TextMate 语法和主题着色。着色在后台线程完成,结果回到窗口线程后只用于仍在显示的那一页;围栏语言缺省时,Markdown 内容沿用当前文档的语言。明暗主题切换后重建颜色表。
  • 纯文本(isMarkdown: false)包成代码块显示,围栏长度大于正文里最长的反引号串;CRLF/CR 统一成 LF,Tab 展开为 4 个空格。
  • 宽度在 280–620 之间取不增加高度的最窄值,最高 420,超出时可滚轮滚动并显示细滚动条。
  • 点击 http/https/mailto 链接用系统浏览器打开,然后关闭弹窗;打开失败记录 operationFailed 事件(复用已有事件,不新增事件)。
  • 弹窗容器的无障碍名称沿用已有词条 desktop.semantics.hover,正文控件的标签是渲染后的完整文本。

位置

  • 锚点改为指针所在行:弹窗顶边贴在该行下沿,左缘在指针左侧 12 px;下方放不下且上方空间更大时翻到行上方;左右按窗口边界收进。
  • 删除旧的固定宽度常量 semanticHoverWidth 和 pluginsemantics_view.go 里的旧 hoverPopup。

保持与延迟关闭(pluginsemantics.go、root.go)

  • 记录 Hover 覆盖的符号范围:优先用服务器返回的单行 range,否则按 UTF-16 列在当前行取标识符(字母、数字、_、$)。指针在这个范围内移动时不再重新请求。
  • root.go 在窗口级指针回调前加 hoverTrackPointer:指针在弹窗矩形内时保持打开;落在编辑器文档上时交给编辑器判断;落在其他位置时开始延迟关闭。
  • 离开符号和弹窗后 400 ms 才关闭,期间指针回到符号或弹窗会取消关闭,并丢弃途中排队的 Hover 请求,避免迟到的结果替换掉当前弹窗。只对指针触发的 Hover 生效,F1 打开的 Hover 行为不变。
  • pluginSemanticsReset 重置新增的 hoverHideDelay 测试开关;closeSemanticHover 会先停止待执行的关闭。

测试与矩阵

  • 新增 pluginsemantics_hover_test.go,覆盖:
    • Markdown 渲染且不露源码,弹窗贴着所在行下沿。
    • 在符号内和弹窗内保持打开,两处都离开才关闭。
    • 延迟关闭期间指针回来会保持,并丢弃迟到结果。
    • 代码块着色。
    • 服务器 range 优先。
    • UTF-16 单词范围。
    • 纯文本围栏与 Tab 展开。
  • shared/platform-feature-matrix/features/lsp-completion-hover.json:两端证据加入新文件,验证说明追加 2026-11-14 的行为;状态仍为 partial / pending。

不在本 PR 范围内 / Out of scope

  • 原生窗口验收、截图(明暗主题、中英文)尚未完成,见“验证”。
  • 只处理外部链接;file: 与工作区内跳转链接不处理(点击无反应)。
  • 不改 internal/editor/**、internal/editorview/**。

类型专属:fix

复现与根因

  • 复现步骤、实际结果、期望结果:在装有语言服务器的 Go/Java 文件里,把指针停在函数名上约 500 ms。实际结果:

    • 弹窗显示 Markdown 源码。
    • 弹窗出现在指针右下方并压住下一行。
    • 把指针移向弹窗时,它在进入前后就关闭了。

    期望结果:渲染后的文档贴在所在行下方,指针可以移进弹窗阅读并点击链接。

  • 根因(git log -S semanticHoverWidth / semanticHoverDelay):0faecba49(Epic(0.7.0): 基于 Viva 框架的跨平台方案的迁移(#1794) #1799,Viva 迁移)引入的 Hover 有以下问题:

    • 用纯文本标签显示内容,宽度固定。
    • 锚点取 event.X + 12, event.Y + 18。
    • pluginSemanticsPointer 在指针离开编辑器文档(d == nil,包括移进弹窗)时立即 closeSemanticHover。
    • 每个新列都 pointPluginHover 重新计时、重新请求。
  • 影响平台:macOS 与 Windows(同一份 Viva 桌面端代码)。

检查项

  • 已先写回归测试,确认它在修复前失败、修复后通过:未做修复前失败验证。新测试依赖本次新增的状态(hover.rect、hideCancel、hover.page、hoverWordSpan),无法在修复前的代码上编译;修复后 desktop/internal/workbench/pluginsemantics_hover_test.go 全部通过。
  • [N/A] 无法自动化时的手动步骤:行为已用无头窗口测试自动化;原生窗口验收见“验证”。
  • 已搜索被修改函数的全部调用方:startProviderHover、startPluginHover、showSemanticHover、hoverPopup 的调用都在 pluginsemantics.go / pluginsemantics_view.go,已全部更新;插件契约、SDK、脚本不涉及。
  • 已检查另一平台是否有同类缺陷:Windows 使用同一份代码,本 PR 一并修复。
  • 只改了修复所需的代码;未发现需要另建 Issue 的问题。
  • [N/A] 未写 bug-fix Note:行为约束已由测试和功能矩阵验证说明记录,没有新的长期取舍。

范围检查 / Scope checks

功能矩阵 / Feature matrix

  • 已更新 shared/platform-feature-matrix/features/lsp-completion-hover.json:两端加入新证据文件,验证说明如实写明只完成无头测试,状态保持 partial / pending
  • 已运行 ./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh(221 项通过)和 ./scripts/platform-feature-matrix/verify-platform-feature-matrix-change.sh <preview> HEAD(通过)
  • 没有提交 .artifacts/ 下生成的矩阵视图
  • [N/A] 已更新矩阵

统一日志系统 / Structured logging

  • 链接打开失败复用已有的 operationFailedEvent 与 operationFailedFields("plugin_semantics", err),没有新增事件、没有 print/stderr
  • 一个操作只有一条终态;指针移动、着色等高频路径不写日志;关闭等待被取消的分支按约定写了 // log-exempt: 用户取消:…
  • 没有记录 Hover 内容、源码或链接地址
  • [N/A] node scripts/logging/generate-log-events.mjs --check 与 verify-log-usage.mjs:本仓库 Epic(0.7.0): 基于 Viva 框架的跨平台方案的迁移(#1794) #1799 之后已没有 scripts/logging/,无法运行

界面文案 / i18n

  • 没有新增文案:弹窗容器的无障碍名称沿用已有词条 desktop.semantics.hover,其余内容是语言服务器返回的文档(不翻译)
  • 已运行 node scripts/i18n/verify-app-language-localization.mjs 和 node scripts/i18n/generate-i18n.mjs --check,均通过,没有放宽脚本

逻辑归属 / Logic placement

  • Markdown 解析、排版、着色都使用 Viva 与编辑器已有的组件,宿主只做呈现和指针交互;没有可下沉 Core 的业务规则
  • [N/A] 平台层已有的可共享逻辑:未修改
  • 没有按工具或语言名加分支;代码块语言来自围栏信息或当前文档的 languageID

性能与内存 / Performance and memory

  • 新增的计时器是一次性的 400 ms 关闭等待,只在指针离开时启动,没有空闲轮询;着色只在弹窗含代码块时启动一次,在后台线程运行,超过 4000 字节的行不着色;token 随弹窗释放
  • 主线程没有文件读写或进程启动;排版结果按宽度、字号、字体、配色缓存,每次排版最多做约 10 次宽度试排;窗口级指针回调在没有指针 Hover 时立即返回
  • [N/A] 不涉及启动、打开项目、输入、滚动、大文件、长输出或搜索;未改 internal/editor/**、internal/editorview/**,不需要编辑器基线

设置 / Settings

N/A:没有新增或修改设置。

插件契约 / Plugin contract

N/A:不改契约、能力版本或宿主方法,只消费已有的 Hover 结果(contents、isMarkdown、range)。

发行包与资源 / Bundle and resources

  • 没有新增运行时资源;已运行 ./scripts/desktop/verify/verify-runtime-bundle-immutability.sh(macOS 与 Windows 源码静态检查通过)
  • [N/A] 没有新增下载、解压、生成或缓存资源

无用代码与重构 / Dead code and refactoring

  • 旧的 hoverPopup 和 semanticHoverWidth 常量已删除
  • [N/A] 没有发现本次之前就存在、需要建单的无用代码

验证 / Verification

命令或操作 平台 结果
scripts/desktop/desktop-go.sh test ./internal/workbench/ -run 'Hover|Semantic' -count=1 -v macOS arm64 13 个测试通过
scripts/desktop/desktop-go.sh test ./internal/workbench/ -count=1(变基到 preview 1cad09c 后) macOS arm64 通过(140 s)
scripts/desktop/desktop-go.sh vet ./internal/workbench/ macOS arm64 通过
gofmt -l desktop/internal/workbench/ macOS arm64 无输出
.agents/skills/write-stable-tests/scripts/test-stability-macos.sh --package ./internal/workbench -- --filter 'TestPointerHover|TestHover' macOS arm64 9 个测试通过,单测试计时在限值内
node scripts/i18n/generate-i18n.mjs --check、node scripts/i18n/verify-app-language-localization.mjs macOS 通过
./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh、verify-platform-feature-matrix-change.sh macOS 通过
./scripts/desktop/verify/verify-runtime-bundle-immutability.sh macOS(静态覆盖两端源码) 通过

未能执行的检查和原因:

  • 没有在原生窗口里用真实语言服务器验收,也没有截图(明暗主题、中英文);以上均为无头窗口测试。
  • 没有在 Windows 上运行测试。
  • 日志门禁脚本已不在本仓库,无法运行。

提交前确认 / Final checks

  • 分支基于最新的 preview(1cad09c2a),目标分支 preview
  • 以 Draft 提交,原生验收完成前不标记 ready
  • 没有无关改动,没有提交产物、凭据、个人路径或日志
  • 本次测试未启动 Lithe 应用;只用了无头测试窗口,测试结束即释放

- Hover 内容按 Markdown 渲染:签名代码块用编辑器语法着色,分隔线、正文、示例和链接分开显示;纯文本包成代码块显示
- http/https/mailto 链接点击后用系统浏览器打开,打开后关闭弹窗
- 宽度在 280–620 之间按内容收窄,超过 420 高度时可滚动并显示细滚动条
- 弹窗贴在指针所在行的下沿,左缘对齐指针;下方放不下且上方更宽裕时翻到上方
- 指针停在同一符号范围内不再重复请求;移入弹窗时保持打开
- 离开符号和弹窗 400 ms 后才关闭,期间回到符号或弹窗会取消关闭,也会丢弃途中排队的 Hover 请求
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Nov 13, 2026
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 14, 2026 02:59
@xiaoyumuxi
xiaoyumuxi merged commit 854eba6 into preview Nov 14, 2026
5 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant