- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
导读
本文以 MooTool 下一代 Compose Desktop 客户端(next-compose)中 F01「随手记(QuickNote)工具栏与文件树」的实证文档为核心,深入剖析三项与 Electron 旧版对齐的编辑能力:笔记颜色(写入 frontmatter 并驱动文件树着色)、行距倍数(作用于 RSyntaxTextArea 行高)、无序/有序列表前缀(覆盖选区整行)。读者将理解这三项功能从设计决策(DIFF-036)、核心领域模型(NoteListEngine、NoteColors、NoteFrontmatter)到存储层(NoteVault.list())与渲染层(EditorBuffer.applyTheme、VaultTreeList)的完整调用链,以及 196/196 桌面测试如何印证其与 Electron 行为的一致性,最后还会看到当前仍未覆盖的测试盲区与已知限制。
一、背景:功能对齐(feature-parity)驱动的 DIFF-036
next-compose是 MooTool 基于 JetBrains Compose Multiplatform / Swing 桥接(SwingPanel+ RSyntaxTextArea)重写的桌面客户端。它并非简单照搬旧 Electron 实现,而是逐项比对功能差异并形成 DIFF 文档。设计文档(DIFF-036,影响范围 F01 随手记工具栏与文件树)明确说明:
原行为(Electron):工具栏有笔记颜色、行距倍数、无序/有序列表。颜色写入 frontmatter 并给文件树图标着色。行距按
1.65 * lineSpacing作用到编辑器。列表前缀覆盖选区所在整行,编号从 1 起。
也就是说,对齐的不只是字体和语法高亮,还包括列表动作与文档元数据(颜色、行距)。这类功能必须同时改变编辑器正文、文件树节点或文档 frontmatter,无法用简单开关实现,因此 DIFF-036 独立立项,并沉淀出配套证据目录docs/evidence/2026-09-15-note-toolbar/。
本产品(Compose Desktop)在 results.md 中给出的对齐结论是:
NoteListEngine与 Electron 的「选区整行前缀」规则一致;NoteColors七色与文件树着色条件一致;EditorBuffer.applyTheme(lineSpacing=2)使行高至少约加倍;NoteVault.list()会带回 frontmatter 中的 color 字段。
二、笔记颜色:从 frontmatter 到文件树的完整链路
2.1 七色定义与归一化
颜色模型由 NoteColors.kt 实现,swatches定义了七档色板:
| 标识符 | ARGB 色值 | 用途 |
|---|---|---|
default | 0xFF8A8A8A | 默认灰,不参与树着色 |
coral | 0xFFD97868 | 珊瑚色 |
yellow | 0xFFC99535 | 黄色 |
green | 0xFF4E9275 | 绿色 |
blue | 0xFF4F83CC | 蓝色 |
purple | 0xFF8A72B5 | 紫色 |
red | 0xFFC96761 | 红色 |
三个核心 API 承担了「写入、读取、着色」三个职责:
normalize(id):将任意输入(如"Coral"、"UNKNOWN")归一化为合法的色板键,未知值一律回落为default;argb(id):由归一化键取 ARGB 色值,供 ComposeColor(...)渲染;tintsTree(id):仅当normalize(id) != "default"时才对文件树进行着色。
2.2 颜色写入 frontmatter 并持久化
工具栏的颜色选择由 QuickNoteScreen.kt 驱动。色板按钮点击后执行session.metadata = session.metadata.copy(color = id),随后随笔记保存写入 YAML frontmatter。序列化由 NoteFrontmatter.kt 完成,典型产物形如:
--- title: API ideas style: syntax: text/plain font_name: "" font_size: "14" line_spacing: "1.0" color: coral line_wrap: "1" created_at: 2026-09-15T00:00:00Z modified_at: 2026-09-15T00:00:00Z --- # Hello 正文内容...serialize()对color使用yamlScalar安全转义,保证任意合法色板键都可往返读写;解析侧parse()通过stringValue(values["color"], "default")缺省回落,健壮性有保障。
2.3 文件树着色条件
文件树渲染位于 VaultTreeList.kt:当节点为文件且NoteColors.tintsTree(entry.color)为真时,行首渲染一个 6dp 圆形色点(Box(Modifier.size(6.dp).clip(CircleShape).background(Color(NoteColors.argb(...))))),同时文件名文本本身也使用该颜色;否则按colors.textBody显示。default色不触发着色,与 Electron 行为对齐。
2.4 「未保存仍按会话色显示」的实现细节
QuickNoteScreen.kt 在向VaultTreeList传递条目时做了就地覆盖:
items = vaultItems.map { item -> if (item.relativePath == session.currentFile) item.copy(color = session.metadata.color) else item }即当前打开文件即使尚未落盘,其会话元数据session.metadata.color也会立即反映到文件树节点上。
2.5 Vaultlist()带回 frontmatter color
证据文档特别强调Vault list()带回 color。NoteVault.kt 的snapshot()对每个非目录条目执行NoteFrontmatter.parse(...),将parsed?.metadata?.color写入VaultEntry.color,list()再经VaultSearchIndex.filter返回。测试 NoteVaultTest.kt 明确验证:
vault.saveNote(note.relativePath, "# Hello\nneedle", note.metadata.copy(color = "coral")) ... assertEquals("coral", vault.list().first { it.relativePath == saved.relativePath }.color)同时该测试还验证了created_at、modified_at随 frontmatter 往返、font_size: "15"与line_spacing: "1.0"的序列化格式(注意font_size/line_spacing以字符串形式写入)。
三、行距倍数:1.0–2.0 如何抬高 RSTA 行高
3.1 参数范围与归一化
NoteMetadata.lineSpacing默认1.0。UI 层(QuickNoteScreen.kt)提供档位1.0、1.2、1.4、1.6、1.8、2.0(主工具栏直接展示,溢出菜单moreOpen中重复提供同样的档位)。存储层通过NoteFrontmatter.clampLineSpacing做双重约束:
fun clampLineSpacing(value: Double): Double { val normalized = if (value.isFinite()) value else 1.0 return (normalized.coerceIn(1.0, 2.0) * 10).roundToInt() / 10.0 }任何非有限值回落为1.0,区间限定[1.0, 2.0],并按 0.1 步长取整,防止恶意或脏数据破坏编辑器布局。
3.2 行距如何作用到编辑器
行距不是 Compose 侧属性,而是经由 EditorHost.kt 的lineSpacing参数下传至 EditorBuffer.kt 的applyTheme(...)。其内部applyLineSpacing的实现值得注意:
private fun applyLineSpacing(spacing: Double) { val multiplier = NoteFrontmatter.clampLineSpacing(spacing) if (multiplier <= 1.0) return // 1.0 保持默认行高 runCatching { val field = RSyntaxTextArea::class.java.getDeclaredField("lineHeight") field.isAccessible = true val base = area.lineHeight val next = round(base * multiplier).toInt().coerceAtLeast(base) field.setInt(area, next) area.revalidate() scrollPane.revalidate() area.repaint() } }关键点:
- 1.0 语义:
multiplier <= 1.0直接返回,不改动 RSTA 默认行高; - 反射写私有字段:RSTA 的
lineHeight是私有字段,这里通过反射在RSTextArea计算行高之后按倍数抬高; - 异常防护:
runCatching包裹反射与重绘,升级 RSTA 后若私有字段改名/失效,功能静默回退到默认行高,不会崩溃。
3.3 测试断言「至少约加倍」
证据文档中的「EditorBuffer.applyTheme(lineSpacing=2)行高至少约加倍」由 EditorThemeTest.kt 的用例直接背书:
buffer.applyTheme(false, "Monospaced", 14, true, lineSpacing = 1.0) val compact = buffer.area.lineHeight buffer.applyTheme(false, "Monospaced", 14, true, lineSpacing = 2.0) assertTrue(buffer.area.lineHeight >= compact * 2 - 1) assertTrue(compact > 0)同一文件还验证了主题色、语法 token(LITERAL_STRING_DOUBLE_QUOTE、LITERAL_NUMBER_DECIMAL_INT)随applyTheme生效。
3.4 与 Electron 的倍数差异(已知限制)
设计文档明确记录了两端行高基数的差异:
- Electron 采用
1.65 * lineSpacing的直接换算; - Compose Desktop 以 RSTA 默认行高为基数,
lineSpacing=1.0不改默认值,倍数关系与 Electron一致,但绝对行高基数不同。
因此 UI 观感在绝对值上可能与 Electron 存在细微差异,属于有意的「倍数对齐而非绝对值对齐」。同时由于依赖 RSTA 私有字段,升级 RSTA 版本必须回归复测此用例。
四、列表前缀:NoteListEngine 的整行语义
4.1 核心算法
NoteListEngine.kt 是一个无状态的object,提供prefixSelectedLines(text, start, end, prefix)纯函数。其选区扩展逻辑:
- 空文档:直接插入
-(无序)或1.(有序),选区选中新插入内容; - 非空:从
caretStart向前找到上一个\n得到行首,从caretEnd向后找到下一个\n得到行尾,即把选区扩展覆盖到其所在的完整首行与末行; - 对整段选区按行切分:无序行加
-前缀;有序行按index + 1生成1.、2.、3.…(编号从 1 起); - 返回新文本与扩展后的选区区间,供 UI 重设选区。
enum class Prefix { Bullet, Numbered } val transformed = selected.split('\n').mapIndexed { index, line -> when (prefix) { Prefix.Bullet -> "- $line" Prefix.Numbered -> "${index + 1}. $line" } }.joinToString("\n")4.2 进入撤销栈
工具栏按钮(QuickNoteScreen.kt 的quickNote.bulletList/quickNote.numberedList)调用私有函数applyListPrefix:
val result = NoteListEngine.prefixSelectedLines( session.editor.text, area.selectionStart, area.selectionEnd, prefix) quickNoteOnEdt { session.editor.setText(result.text, recordUndo = true) // 关键:进入撤销栈 session.editor.select(result.selectionStart, result.selectionEnd) }setText(recordUndo = true)走atomicDocument(CompoundEdit+UndoManager,上限 400 步),保证「添加列表前缀」这一操作可被一次撤销/重做;随后select()将选区恢复到扩展后的整行范围,方便用户连续加前缀。
4.3 与 Electron 的逐行一致性测试
NoteListEngineTest.kt 用三个用例锚定「选区所在整行」语义:
| 输入 | 操作 | 期望输出 |
|---|---|---|
"alpha\nbeta\ngamma",选区(6,10)(恰为beta行) | Bullet | "alpha\n- beta\ngamma" |
"one\ntwo",选区(0,7)(覆盖两行) | Numbered | "1. one\n2. two" |
| 空文本 | Bullet | "- " |
其中第二个用例证明:多行选区会被逐行编号(1. one\n2. two),而不是只给首行编号。这印证了「覆盖选区所在整行、编号从 1 起」的设计语义。
五、测试证据与未测盲区
5.1 desktopTest 196/196
证据文档给出的结论是desktopTest196/196(0 skipped)。与之直接相关的测试文件包括:
- NoteListEngineTest.kt:列表前缀整行语义、
NoteColors七色与着色条件(NoteColorsTest); - EditorThemeTest.kt:行距倍数抬高 RSTA
lineHeight; - NoteVaultTest.kt:frontmatter 中
color/line_spacing的写入、读取与list()回传; - NoteFrontmatterTest.kt:frontmatter 解析与
clampLineSpacing归一化。
5.2 明确未测项(复现 / 验收时重点关注)
证据文档诚实列出的未测项,可作为后续验收清单:
- IME/列编辑手工窗口:中文输入法与列编辑(
EditorColumnEditPresentation)在真实窗口下的人工验证; - 真实窗口截图:当前证据目录仅含
results.md,尚无窗口级截图佐证; - 托盘权限对话框:macOS 屏幕录制/辅助功能权限弹窗链路;
- 六套 CSS 逐选择器皮肤:Compose 主题与旧版六套皮肤按选择器逐一比对的皮肤层验证;
- 三平台安装/升级/卸载:Windows/macOS/Linux 三端安装包生命周期尚未覆盖。
六、受影响范围与已知限制
设计文档「受影响范围」一节还记录了两个必须向用户说明的边界:
- JSON Vault 树无 color 字段:
JsonVault的VaultEntry与NoteVault不同,JSON 工具树不读取 frontmatter color,NoteVault.snapshot()中解析 color 的逻辑仅作用于随手记文件(txt/md/json/java/js/ts/py/xml/yaml/yml/sql等NoteFrontmatter.noteExtensions白名单);JSON Vault 节点保持默认样式; - 行距基数差异:如前文 3.4 所述,Compose 端以 RSTA 默认行高为基数、1.0 不改默认值,与 Electron 的
1.65em基数不同,但倍数关系一致。
七、结论
DIFF-036 与配套证据文档展示了 MooToolnext-compose一项典型的「领域逻辑可测、渲染层桥接 Swing」功能对齐模式:颜色、行距、列表前缀三种能力都被抽取为无 UI 依赖的领域模块(NoteColors、NoteFrontmatter、NoteListEngine),通过 196/196 的desktopTest在无窗口环境下验证与 Electron 的语义一致,再由EditorBuffer/VaultTreeList等薄渲染层落地。对于开发者而言,若要在升级 RSyntaxTextArea 后回归此功能,请优先重跑 EditorThemeTest.kt 的行距用例,并按 5.2 节的未测清单补齐手工验收。
- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
相关推荐
MooTool Next Compose 随手记工具栏对齐:笔记颜色、行距与列表前缀的实现解析(DIFF-036)
MooTool Next Compose 随手记工具栏对齐:笔记颜色、行距与列表前缀的实现解析(DIFF 036) 本篇文章以 MooTool 下一代桌面端(
桌面应用开发工具MooTool Next Compose 随手记文档库:树选中同步、行级脏标记与状态栏路径对齐实战
MooTool Next Compose 随手记文档库:树选中同步、行级脏标记与状态栏路径对齐实战 导读 在 MooTool 的 Electron 版与 Com
桌面应用开发工具MooTool Compose 随手记历史恢复行为对齐:移除 restored notice 的设计与实现解析
MooTool Compose 随手记历史恢复行为对齐:移除 restored notice 的设计与实现解析 MooTool 是面向开发者的工具集项目,其 n
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考