dsh-vscode-mode 0.1.62 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/lib/client.js +1554 -87
- package/lib/client.js.map +1 -1
- package/lib/index.js +9 -2
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/client/commandBridge.ts +83 -0
- package/src/client/commandGlobals.ts +13 -0
- package/src/client/commandPaletteStore.ts +165 -0
- package/src/client/commandRegistry.ts +144 -0
- package/src/client/commandSearch.ts +89 -0
- package/src/client/editorModelState.ts +30 -0
- package/src/client/index.ts +55 -4
- package/src/client/keybindings.ts +71 -15
- package/src/client/monaco/loader.ts +3 -3
- package/src/client/monaco/theme.ts +313 -7
- package/src/client/officialSidebar.ts +45 -0
- package/src/client/outline/index.ts +4 -3
- package/src/client/sidebar/SidebarView.ts +16 -1
- package/src/client/sidebar/panels/FileExplorer.ts +58 -4
- package/src/client/sidebar/panels/index.ts +12 -10
- package/src/client/sidebar/types.ts +2 -2
- package/src/client/styles/editor.css +47 -28
- package/src/client/ui/ClaimRouter.ts +96 -0
- package/src/client/ui/CommandPalette.ts +158 -0
- package/src/client/ui/ConversationDiffDock.ts +17 -9
- package/src/client/ui/EditorView.ts +153 -15
- package/src/client/ui/KeybindingsPanel.ts +1 -1
- package/src/client/ui/OfficialSideTab.ts +8 -9
- package/src/client/ui/QuickOpen.ts +11 -3
- package/src/client/ui/commandCatalog.ts +179 -0
- package/src/client-primitives.d.ts +37 -0
- package/src/shared/keybindings.ts +10 -1
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
* dsh-vscode-mode client — 快捷键配置模块(解析/匹配/状态同步)。
|
|
3
3
|
* 纯逻辑(parseChord/parseChords/formatChord/matchEvent/chordFromEvent/normalizeKey)不依赖 DOM,可单测;
|
|
4
4
|
* 模块状态由 settings 订阅驱动(client/index.ts 调 keybindingsApply)。
|
|
5
|
-
* 键位语义:Ctrl 与 Cmd
|
|
6
|
-
*
|
|
5
|
+
* 键位语义:Ctrl 与 Cmd 互认(延续 Ctrl+P/Ctrl+B 捕获行为)。
|
|
6
|
+
* 命令目录(COMMANDS)派生自指令目录 commandCatalog(指令 = 单一数据源,设置页自动跟随);
|
|
7
|
+
* 另支持运行时键位表(第三方命令 register 时声明,不落设置 schema,注销即失效)。
|
|
8
|
+
* 作者 ddj 2026年08月26号 / 2026年09月10号
|
|
7
9
|
*/
|
|
8
10
|
import React from 'react'
|
|
9
11
|
import { KEYBINDING_DEFAULTS, normalizeKeybindings } from '../shared/keybindings.js'
|
|
12
|
+
import { BRIDGE_COMMANDS, EDITOR_COMMANDS, showCommandsDef } from './ui/commandCatalog.js'
|
|
13
|
+
import { log } from './log.js'
|
|
10
14
|
|
|
11
15
|
/** 解析后的键位(修饰符 + 规范化主键)。 */
|
|
12
16
|
export interface Binding {
|
|
@@ -19,22 +23,39 @@ export interface Binding {
|
|
|
19
23
|
|
|
20
24
|
/** 命令目录(设置页展示标签;执行按目录序先匹配先执行,冲突键位确定性)。 */
|
|
21
25
|
export const COMMANDS: Array<{ id: string; label: string }> = [
|
|
22
|
-
{ id:
|
|
23
|
-
{ id: 'edrv.
|
|
24
|
-
{ id:
|
|
25
|
-
{ id: 'edrv.searchInFiles', label: '在工作区中搜索' },
|
|
26
|
-
{ id: 'edrv.navigateBack', label: '后退(导航历史)' },
|
|
27
|
-
{ id: 'edrv.navigateForward', label: '前进(导航历史)' },
|
|
26
|
+
...EDITOR_COMMANDS.map((command) => ({ id: command.id, label: command.label })),
|
|
27
|
+
{ id: 'edrv.showCommands', label: '显示所有命令' },
|
|
28
|
+
...BRIDGE_COMMANDS.map((command) => ({ id: command.id, label: command.label })),
|
|
28
29
|
]
|
|
29
30
|
|
|
31
|
+
/** 命令栏命令定义(键位自检与目录展示共用;run 不参与键位逻辑)。 */
|
|
32
|
+
function paletteCommand(): { id: string; label: string; keybinding?: string } {
|
|
33
|
+
return showCommandsDef(() => {})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 目录与默认键位表一致性自检(仅告警不中断):新指令漏写共享表时第一时间可见。
|
|
38
|
+
* @author ddj 2026年09月10号
|
|
39
|
+
*/
|
|
40
|
+
function checkDefaultsDrift(): void {
|
|
41
|
+
for (const command of [...EDITOR_COMMANDS, paletteCommand()]) {
|
|
42
|
+
if (!command.keybinding) continue
|
|
43
|
+
if (KEYBINDING_DEFAULTS[command.id] === command.keybinding) continue
|
|
44
|
+
log.warn('指令默认键位与共享表不一致:' + command.id
|
|
45
|
+
+ ' 目录=' + command.keybinding + ' 共享表=' + String(KEYBINDING_DEFAULTS[command.id]))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
const MODIFIERS: Record<string, 'ctrl' | 'shift' | 'alt' | 'meta'> = {
|
|
31
50
|
ctrl: 'ctrl', cmd: 'meta', meta: 'meta', shift: 'shift', alt: 'alt',
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
let current: Record<string, string> = { ...KEYBINDING_DEFAULTS }
|
|
35
|
-
|
|
54
|
+
const runtime = new Map<string, Binding[]>()
|
|
36
55
|
const listeners = new Set<() => void>()
|
|
37
56
|
|
|
57
|
+
checkDefaultsDrift()
|
|
58
|
+
|
|
38
59
|
/**
|
|
39
60
|
* 应用设置快照(与默认值合并;未知 id 丢弃;空对象 = 全部默认)。
|
|
40
61
|
* 每个命令可含多候选键位(`|` 分隔),任一命中即触发。
|
|
@@ -43,8 +64,6 @@ const listeners = new Set<() => void>()
|
|
|
43
64
|
*/
|
|
44
65
|
export function keybindingsApply(raw: unknown): void {
|
|
45
66
|
current = { ...KEYBINDING_DEFAULTS, ...normalizeKeybindings(raw) }
|
|
46
|
-
parsed = {}
|
|
47
|
-
for (const id of Object.keys(current)) parsed[id] = parseChords(current[id])
|
|
48
67
|
for (const listener of listeners) {
|
|
49
68
|
try { listener() } catch { /* 监听器异常不影响其他订阅 */ }
|
|
50
69
|
}
|
|
@@ -62,24 +81,61 @@ export function subscribeKeybindings(listener: () => void): () => void {
|
|
|
62
81
|
}
|
|
63
82
|
|
|
64
83
|
/**
|
|
65
|
-
*
|
|
66
|
-
* @author ddj 2026年
|
|
84
|
+
* 注册运行时键位(第三方命令;优先于设置值,注销即失效)。
|
|
85
|
+
* @author ddj 2026年09月10号
|
|
86
|
+
* @param id 命令 id
|
|
87
|
+
* @param chord 键位弦(空/非法按未绑定处理)
|
|
88
|
+
* @returns 注销函数(幂等)
|
|
89
|
+
*/
|
|
90
|
+
export function addRuntimeKeybinding(id: string, chord: string): () => void {
|
|
91
|
+
runtime.set(id, parseChords(chord))
|
|
92
|
+
notifyKeybindings()
|
|
93
|
+
return () => removeRuntimeKeybinding(id)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 移除运行时键位。
|
|
98
|
+
* @author ddj 2026年09月10号
|
|
99
|
+
* @param id 命令 id
|
|
100
|
+
*/
|
|
101
|
+
export function removeRuntimeKeybinding(id: string): void {
|
|
102
|
+
if (!runtime.delete(id)) return
|
|
103
|
+
notifyKeybindings()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** 通知键位订阅者(异常隔离)。 */
|
|
107
|
+
function notifyKeybindings(): void {
|
|
108
|
+
for (const listener of listeners) {
|
|
109
|
+
try { listener() } catch { /* 监听器异常不影响其他订阅 */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 当前命令的键位弦(运行时键位优先;未绑定/空 → null)。
|
|
115
|
+
* @author ddj 2026年08月26号 / 2026年09月10号
|
|
67
116
|
* @param id 命令 id
|
|
68
117
|
* @returns 键位弦或 null
|
|
69
118
|
*/
|
|
70
119
|
export function chordOf(id: string): string | null {
|
|
120
|
+
if (runtime.has(id)) {
|
|
121
|
+
const chords = runtime.get(id) ?? []
|
|
122
|
+
return chords.length ? chords.map(formatChord).join('|') : null
|
|
123
|
+
}
|
|
71
124
|
const chord = current[id]
|
|
72
125
|
return typeof chord === 'string' && chord.trim() !== '' ? chord : null
|
|
73
126
|
}
|
|
74
127
|
|
|
75
128
|
/**
|
|
76
129
|
* 当前命令的解析键位集合(未绑定/非法 → 空数组;含多候选)。
|
|
77
|
-
* @author ddj 2026年08月26号
|
|
130
|
+
* @author ddj 2026年08月26号 / 2026年09月10号
|
|
78
131
|
* @param id 命令 id
|
|
79
132
|
* @returns 解析键位数组(可能为空)
|
|
80
133
|
*/
|
|
81
134
|
export function bindingsOf(id: string): Binding[] {
|
|
82
|
-
|
|
135
|
+
const override = runtime.get(id)
|
|
136
|
+
if (override) return override
|
|
137
|
+
const chord = current[id]
|
|
138
|
+
return typeof chord === 'string' ? parseChords(chord) : []
|
|
83
139
|
}
|
|
84
140
|
|
|
85
141
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 迁移自原 src/client/index.ts 的 MONACO_BASE/LANG_BY_EXT/langOf/loadMonaco,语义不改。
|
|
5
5
|
* 作者 ddj 2026-08-20
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { applyOfficial, registerThemes } from './theme.js'
|
|
8
8
|
|
|
9
9
|
export const MONACO_BASE = '/edrv/vendor/monaco/vs'
|
|
10
10
|
let monacoPromise = null
|
|
@@ -102,10 +102,10 @@ export function loadMonaco(onProgress) {
|
|
|
102
102
|
publishStage('core', MONACO_STAGES.core.progress, MONACO_STAGES.core.message)
|
|
103
103
|
window.require(['vs/editor/editor.main'], () => {
|
|
104
104
|
publishStage('ready', MONACO_STAGES.ready.progress, MONACO_STAGES.ready.message)
|
|
105
|
-
//
|
|
105
|
+
// 主题:注册现役双套(幂等)+ 应用跟随官方的令牌主题(令牌缺失时回落现役)
|
|
106
106
|
try {
|
|
107
107
|
registerThemes(window.monaco)
|
|
108
|
-
|
|
108
|
+
applyOfficial(window.monaco)
|
|
109
109
|
} catch (error) { /* 主题失败不阻塞编辑器 */ }
|
|
110
110
|
resolve(window.monaco)
|
|
111
111
|
}, (err) => fail(new Error('Monaco 模块加载失败:' + String(err))))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
|
-
* dsh-vscode-mode client — Monaco
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 作者 ddj 2026-08-28
|
|
3
|
+
* dsh-vscode-mode client — Monaco 主题:官方令牌跟随 + 现役双套回落。
|
|
4
|
+
* 优先级:官方代码/UI 令牌(--shiki-* / --dsw-alias-*,DSH 0.1.5+ 主题体系,
|
|
5
|
+
* 含第三方皮肤)→ edrv-dark / edrv-light 两套内置配色(旧版 DSH 或令牌缺失)。
|
|
6
|
+
* 明暗判定:body[data-ds-dark-theme](官方)→ 根 data-theme(旧皮肤)→ prefers-color-scheme。
|
|
7
|
+
* 作者 ddj 2026-08-28 / 2026-09-10
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export const EDRV_DARK = 'edrv-dark'
|
|
@@ -185,12 +185,14 @@ export function registerThemes(monaco) {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
|
-
* 探测当前 DSH
|
|
189
|
-
* @author ddj 2026年08月28号
|
|
188
|
+
* 探测当前 DSH 明暗(官方 data-ds-dark-theme → 旧皮肤 data-theme → prefers-color-scheme → 暗)。
|
|
189
|
+
* @author ddj 2026年08月28号 / 2026年09月10号
|
|
190
190
|
* @returns 'dark' | 'light'
|
|
191
191
|
*/
|
|
192
192
|
export function detectColorScheme() {
|
|
193
193
|
try {
|
|
194
|
+
const official = document.body?.getAttribute?.('data-ds-dark-theme')
|
|
195
|
+
if (official !== null && official !== undefined && official !== 'false') return 'dark'
|
|
194
196
|
const attr = document.documentElement?.getAttribute?.('data-theme')
|
|
195
197
|
?? document.body?.getAttribute?.('data-theme')
|
|
196
198
|
const value = String(attr ?? '').toLowerCase()
|
|
@@ -227,3 +229,307 @@ export function applyTheme(monaco) {
|
|
|
227
229
|
/* setTheme 失败忽略 */
|
|
228
230
|
}
|
|
229
231
|
}
|
|
232
|
+
|
|
233
|
+
// --region 官方主题跟随(--shiki-* / --dsw-alias-* 令牌)
|
|
234
|
+
|
|
235
|
+
/** 动态官方主题 id 前缀(每次应用带序号重建,保证 Monaco 感知重定义)。 */
|
|
236
|
+
const EDRV_OFFICIAL = 'edrv-official'
|
|
237
|
+
|
|
238
|
+
/** 官方代码配色令牌 → Monaco 规则 token 前缀(粗粒度;未覆盖的规则保留现役语义分层)。 */
|
|
239
|
+
const RULE_TOKEN_MAP = [
|
|
240
|
+
['--shiki-token-comment', ['comment']],
|
|
241
|
+
['--shiki-token-keyword', ['keyword']],
|
|
242
|
+
['--shiki-token-string', ['string']],
|
|
243
|
+
['--shiki-token-string-expression', ['string.escape']],
|
|
244
|
+
['--shiki-token-constant', ['number', 'constant']],
|
|
245
|
+
['--shiki-token-function', ['function', 'method', 'member']],
|
|
246
|
+
['--shiki-token-parameter', ['parameter', 'variable.parameter']],
|
|
247
|
+
['--shiki-token-punctuation', ['delimiter', 'operator', 'punctuation']],
|
|
248
|
+
['--shiki-token-link', ['annotation', 'metatag']],
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 官方 UI 令牌 → Monaco 颜色键(每键给候选令牌,按序取首个可解析值)。
|
|
253
|
+
* 依据运行态实测:--dsw-alias-* 定义在 body 上;--shiki-background/-foreground 未定义,
|
|
254
|
+
* 故代码面底色用 markdown-code-block 近似;selection 不覆盖(沿用现役可见选框)。
|
|
255
|
+
*/
|
|
256
|
+
const COLOR_TOKEN_MAP = [
|
|
257
|
+
[['--dsw-alias-markdown-code-block', '--dsw-alias-bg-layer-2'], ['editor.background', 'editorGutter.background', 'minimap.background']],
|
|
258
|
+
[['--dsw-alias-label-primary', '--shiki-foreground'], ['editor.foreground']],
|
|
259
|
+
[['--dsw-alias-bg-layer-1', '--dsw-alias-bg-layer-3'], ['editorWidget.background', 'editorHoverWidget.background']],
|
|
260
|
+
[['--dsw-alias-label-tertiary'], ['editorLineNumber.foreground']],
|
|
261
|
+
[['--dsw-alias-label-secondary'], ['editorLineNumber.activeForeground']],
|
|
262
|
+
[['--dsw-alias-interactive-bg-hover'], ['editor.lineHighlightBackground', 'editor.inactiveSelectionBackground']],
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
/** 已应用的官方主题序号(模块级计数,保证主题 id 每次唯一)。 */
|
|
266
|
+
let officialSeq = 0
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 读官方 CSS 变量(先 body 后文档根;未定义/空值/未解析 var() 返回 undefined)。
|
|
270
|
+
* 依据运行态实测:DSH 的 --dsw-alias-* 定义在 body 上,仅 --shiki-token-* 在根。
|
|
271
|
+
* @author ddj 2026年09月10号
|
|
272
|
+
* @param name CSS 变量名(含 -- 前缀)
|
|
273
|
+
* @returns 计算样式值,或 undefined
|
|
274
|
+
*/
|
|
275
|
+
export function readCssVar(name) {
|
|
276
|
+
try {
|
|
277
|
+
if (typeof window?.getComputedStyle !== 'function') return undefined
|
|
278
|
+
const hosts = [document?.body, document?.documentElement]
|
|
279
|
+
for (const host of hosts) {
|
|
280
|
+
if (!host) continue
|
|
281
|
+
const raw = window.getComputedStyle(host).getPropertyValue(name)
|
|
282
|
+
const value = String(raw ?? '').trim()
|
|
283
|
+
if (value && !value.startsWith('var(')) return value
|
|
284
|
+
}
|
|
285
|
+
return undefined
|
|
286
|
+
} catch (error) {
|
|
287
|
+
return undefined
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 数值 → 两位十六进制(0-255 夹取)。
|
|
293
|
+
* @author ddj 2026年09月10号
|
|
294
|
+
* @param value 通道值
|
|
295
|
+
* @returns 两位小写十六进制
|
|
296
|
+
*/
|
|
297
|
+
function hex2(value) {
|
|
298
|
+
const text = Math.max(0, Math.min(255, Math.round(Number(value) || 0))).toString(16)
|
|
299
|
+
return text.length === 1 ? '0' + text : text
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* CSS 颜色归一为 Monaco 可用值(#rgb/#rrggbb/#rrggbbaa 原样,rgb()/hsl() 转 hex,其余 undefined)。
|
|
304
|
+
* @author ddj 2026年09月10号
|
|
305
|
+
* @param value CSS 计算样式颜色值
|
|
306
|
+
* @returns Monaco 颜色字符串,或 undefined(不可识别)
|
|
307
|
+
*/
|
|
308
|
+
export function toMonacoColor(value) {
|
|
309
|
+
const text = String(value ?? '').trim().toLowerCase()
|
|
310
|
+
if (!text || text.startsWith('var(')) return undefined
|
|
311
|
+
if (/^#[0-9a-f]{3}$/.test(text)) return '#' + text.slice(1).split('').map((c) => c + c).join('')
|
|
312
|
+
if (/^#[0-9a-f]{6}$/.test(text) || /^#[0-9a-f]{8}$/.test(text)) return text
|
|
313
|
+
return channelColor(text)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* rgb()/rgba()/hsl()/hsla() → Monaco 颜色(兼容逗号与空格/斜杠两种参数语法)。
|
|
318
|
+
* @author ddj 2026年09月10号
|
|
319
|
+
* @param text 小写颜色文本
|
|
320
|
+
* @returns Monaco 颜色,或 undefined(不可识别)
|
|
321
|
+
*/
|
|
322
|
+
function channelColor(text) {
|
|
323
|
+
const body = /^(?:rgba?|hsla?)\(([^)]+)\)$/.exec(text)?.[1]
|
|
324
|
+
if (!body) return undefined
|
|
325
|
+
const parts = body.split(/[\s,/]+/).filter(Boolean)
|
|
326
|
+
if (parts.length < 3) return undefined
|
|
327
|
+
let channels
|
|
328
|
+
if (text.startsWith('hsl')) {
|
|
329
|
+
const h = Number(parts[0])
|
|
330
|
+
const s = percentOf(parts[1])
|
|
331
|
+
const l = percentOf(parts[2])
|
|
332
|
+
if (![h, s, l].every(Number.isFinite)) return undefined
|
|
333
|
+
channels = hslToRgb(h, s, l)
|
|
334
|
+
} else {
|
|
335
|
+
channels = parts.slice(0, 3).map(Number)
|
|
336
|
+
}
|
|
337
|
+
if (channels.some((n) => !Number.isFinite(n))) return undefined
|
|
338
|
+
const alpha = parts.length > 3 ? alphaOf(parts[3]) : 1
|
|
339
|
+
const tail = Number.isFinite(alpha) && alpha < 1 ? hex2(alpha * 255) : ''
|
|
340
|
+
return '#' + channels.map(hex2).join('') + tail
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* 百分数文本 → 数值('5%' → 5,'0.5' → 0.5)。
|
|
345
|
+
* @author ddj 2026年09月10号
|
|
346
|
+
* @param value 参数文本
|
|
347
|
+
* @returns 数值(非数字为 NaN)
|
|
348
|
+
*/
|
|
349
|
+
function percentOf(value) {
|
|
350
|
+
const text = String(value ?? '')
|
|
351
|
+
return text.endsWith('%') ? Number(text.slice(0, -1)) : Number(text)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* alpha 参数归一('5%' → 0.05,'0.5' → 0.5)。
|
|
356
|
+
* @author ddj 2026年09月10号
|
|
357
|
+
* @param value 参数文本
|
|
358
|
+
* @returns 0-1 的 alpha(非数字为 NaN)
|
|
359
|
+
*/
|
|
360
|
+
function alphaOf(value) {
|
|
361
|
+
const text = String(value ?? '')
|
|
362
|
+
return text.endsWith('%') ? Number(text.slice(0, -1)) / 100 : Number(text)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* HSL → RGB 通道(h 度、s/l 为 0-100 百分数)。
|
|
367
|
+
* @author ddj 2026年09月10号
|
|
368
|
+
* @param h 色相(度)
|
|
369
|
+
* @param s 饱和度(0-100)
|
|
370
|
+
* @param l 亮度(0-100)
|
|
371
|
+
* @returns [r, g, b](0-255)
|
|
372
|
+
*/
|
|
373
|
+
function hslToRgb(h, s, l) {
|
|
374
|
+
const hue = ((h % 360) + 360) % 360
|
|
375
|
+
const sat = Math.max(0, Math.min(100, s)) / 100
|
|
376
|
+
const lum = Math.max(0, Math.min(100, l)) / 100
|
|
377
|
+
const c = (1 - Math.abs(2 * lum - 1)) * sat
|
|
378
|
+
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1))
|
|
379
|
+
const m = lum - c / 2
|
|
380
|
+
const table = [[c, x, 0], [x, c, 0], [0, c, x], [0, x, c], [x, 0, c], [c, 0, x]]
|
|
381
|
+
return table[Math.min(5, Math.floor(hue / 60))].map((v) => (v + m) * 255)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 官方令牌 → Monaco 颜色键覆盖表(候选令牌按序取首个可解析值;全缺则不写入,保持现役回落)。
|
|
386
|
+
* @author ddj 2026年09月10号
|
|
387
|
+
* @returns 颜色键 → 颜色值
|
|
388
|
+
*/
|
|
389
|
+
function tokenColors() {
|
|
390
|
+
const out = {}
|
|
391
|
+
for (const entry of COLOR_TOKEN_MAP) {
|
|
392
|
+
const color = firstColor(entry[0])
|
|
393
|
+
if (color === undefined) continue
|
|
394
|
+
for (const key of entry[1]) out[key] = color
|
|
395
|
+
}
|
|
396
|
+
return out
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 候选令牌中首个可解析为 Monaco 颜色的值。
|
|
401
|
+
* @author ddj 2026年09月10号
|
|
402
|
+
* @param names CSS 变量名候选(按优先级)
|
|
403
|
+
* @returns Monaco 颜色,或 undefined(全部缺失/不可解析)
|
|
404
|
+
*/
|
|
405
|
+
function firstColor(names) {
|
|
406
|
+
for (const name of names) {
|
|
407
|
+
const color = toMonacoColor(readCssVar(name))
|
|
408
|
+
if (color !== undefined) return color
|
|
409
|
+
}
|
|
410
|
+
return undefined
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 官方令牌 → Monaco 规则前缀覆盖表(值去掉 # 前缀,Monaco rules 约定)。
|
|
415
|
+
* @author ddj 2026年09月10号
|
|
416
|
+
* @returns token 前缀 → 十六进制色(无 #)
|
|
417
|
+
*/
|
|
418
|
+
function tokenRules() {
|
|
419
|
+
const out = {}
|
|
420
|
+
for (const entry of RULE_TOKEN_MAP) {
|
|
421
|
+
const color = toMonacoColor(readCssVar(entry[0]))
|
|
422
|
+
if (color === undefined) continue
|
|
423
|
+
for (const prefix of entry[1]) out[prefix] = color.replace('#', '')
|
|
424
|
+
}
|
|
425
|
+
return out
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* 现役规则叠加令牌覆盖(前缀命中即换色,未命中保持语义分层)。
|
|
430
|
+
* @author ddj 2026年09月10号
|
|
431
|
+
* @param baseRules 现役规则表(edrv-dark / edrv-light)
|
|
432
|
+
* @param overrides token 前缀 → 颜色
|
|
433
|
+
* @returns 覆盖后的规则表(无覆盖时原样返回)
|
|
434
|
+
*/
|
|
435
|
+
function withOverrides(baseRules, overrides) {
|
|
436
|
+
const prefixes = Object.keys(overrides)
|
|
437
|
+
if (!prefixes.length) return baseRules
|
|
438
|
+
return baseRules.map((rule) => {
|
|
439
|
+
const hit = prefixes.find((p) => rule.token === p || rule.token.startsWith(p + '.'))
|
|
440
|
+
return hit === undefined ? rule : Object.assign({}, rule, { foreground: overrides[hit] })
|
|
441
|
+
})
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 基础规则(token 为空串)前景色跟随官方 editor.foreground。
|
|
446
|
+
* Monaco 的 '' 规则匹配所有未被更具体规则命中的 token,不改它则内置 #1e1e1e 会盖住
|
|
447
|
+
* editor.foreground 令牌色(实测 .mtk1 仍为内置值的原因)。
|
|
448
|
+
* @author ddj 2026年09月10号
|
|
449
|
+
* @param rules 现役规则表
|
|
450
|
+
* @param color 已解析的 editor.foreground(含 #,缺省则不覆盖)
|
|
451
|
+
* @returns 基础规则换色后的规则表
|
|
452
|
+
*/
|
|
453
|
+
export function withBaseForeground(rules, color) {
|
|
454
|
+
if (!color) return rules
|
|
455
|
+
const foreground = String(color).replace('#', '')
|
|
456
|
+
return rules.map((rule) => (rule.token === '' ? Object.assign({}, rule, { foreground }) : rule))
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 构建跟随官方的 Monaco 主题(令牌全缺时 hasTokens=false,调用方回落现役双套)。
|
|
461
|
+
* @author ddj 2026年09月10号
|
|
462
|
+
* @param scheme 明暗('light' 之外一律按暗色)
|
|
463
|
+
* @returns 主题定义(base/rules/colors/hasTokens)
|
|
464
|
+
*/
|
|
465
|
+
export function officialThemeOf(scheme) {
|
|
466
|
+
const dark = scheme !== 'light'
|
|
467
|
+
const colors = tokenColors()
|
|
468
|
+
const rules = tokenRules()
|
|
469
|
+
const layered = withOverrides(dark ? DARK_RULES : LIGHT_RULES, rules)
|
|
470
|
+
return {
|
|
471
|
+
base: dark ? 'vs-dark' : 'vs',
|
|
472
|
+
rules: withBaseForeground(layered, colors['editor.foreground']),
|
|
473
|
+
colors: Object.assign({}, dark ? DARK_COLORS : LIGHT_COLORS, colors),
|
|
474
|
+
hasTokens: Object.keys(colors).length > 0 || Object.keys(rules).length > 0,
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* 应用跟随官方的主题;令牌读不到或定义失败时回落现役双套。
|
|
480
|
+
* @author ddj 2026年09月10号
|
|
481
|
+
* @param monaco Monaco 实例
|
|
482
|
+
* @returns 实际应用的 Monaco 主题 id
|
|
483
|
+
*/
|
|
484
|
+
export function applyOfficial(monaco) {
|
|
485
|
+
if (!monaco?.editor?.setTheme || !monaco?.editor?.defineTheme) return themeNameOf()
|
|
486
|
+
const theme = officialThemeOf(detectColorScheme())
|
|
487
|
+
if (!theme.hasTokens) {
|
|
488
|
+
applyTheme(monaco)
|
|
489
|
+
return themeNameOf()
|
|
490
|
+
}
|
|
491
|
+
officialSeq += 1
|
|
492
|
+
const id = EDRV_OFFICIAL + '-' + officialSeq
|
|
493
|
+
try {
|
|
494
|
+
monaco.editor.defineTheme(id, { base: theme.base, inherit: true, rules: theme.rules, colors: theme.colors })
|
|
495
|
+
monaco.editor.setTheme(id)
|
|
496
|
+
return id
|
|
497
|
+
} catch (error) {
|
|
498
|
+
applyTheme(monaco)
|
|
499
|
+
return themeNameOf()
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 观察官方明暗标记变化(body / 根节点的 data-ds-dark-theme 与 data-theme)。
|
|
505
|
+
* @author ddj 2026年09月10号
|
|
506
|
+
* @param onChange 标记变化回调
|
|
507
|
+
* @returns 停止观察函数(非浏览器环境返回空函数)
|
|
508
|
+
*/
|
|
509
|
+
export function observeScheme(onChange) {
|
|
510
|
+
try {
|
|
511
|
+
if (typeof MutationObserver !== 'function' || !document?.body) return () => {}
|
|
512
|
+
const observer = new MutationObserver(() => onChange())
|
|
513
|
+
const filter = ['data-ds-dark-theme', 'data-theme']
|
|
514
|
+
observer.observe(document.body, { attributes: true, attributeFilter: filter })
|
|
515
|
+
if (document.documentElement) {
|
|
516
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: filter })
|
|
517
|
+
}
|
|
518
|
+
return () => observer.disconnect()
|
|
519
|
+
} catch (error) {
|
|
520
|
+
return () => {}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* 官方主题快照 → 明暗(ThemeRuntime.getTheme() 快照;字段缺失/未知返回 undefined)。
|
|
526
|
+
* @author ddj 2026年09月10号
|
|
527
|
+
* @param snapshot 官方主题快照
|
|
528
|
+
* @returns 'light' | 'dark' | undefined
|
|
529
|
+
*/
|
|
530
|
+
export function schemeOfSnapshot(snapshot) {
|
|
531
|
+
const value = snapshot ?? {}
|
|
532
|
+
const scheme = value.active?.colorScheme ?? value.colorScheme
|
|
533
|
+
return scheme === 'light' || scheme === 'dark' ? scheme : undefined
|
|
534
|
+
}
|
|
535
|
+
// --endregion
|
|
@@ -37,6 +37,8 @@ export const OFFICIAL_FILE_KIND = 'edrvEditorFile'
|
|
|
37
37
|
export interface OfficialTabParams {
|
|
38
38
|
openPath?: string
|
|
39
39
|
focusDiff?: boolean
|
|
40
|
+
/** 目标行号(行引用/工具行跳转透传;缺省仅打开文件)。 */
|
|
41
|
+
line?: number
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/** sidebarRightTabs 服务的最小结构面(结构性探测,不 import 官方类型)。 */
|
|
@@ -97,6 +99,19 @@ export function resolveNavOpen(params: unknown): { path: string | null; focusDif
|
|
|
97
99
|
return { path, focusDiff: value?.focusDiff === true }
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
/**
|
|
103
|
+
* 从官方导航参数安全解析行号(缺字段/坏类型/非正数一律视为未指定)。
|
|
104
|
+
* @author ddj 2026年09月10号
|
|
105
|
+
* @param params openTab/openResource 传入的 params(正文经 navigation.params 读回)
|
|
106
|
+
* @returns 正整数行号,或 undefined
|
|
107
|
+
*/
|
|
108
|
+
export function resolveNavLine(params: unknown): number | undefined {
|
|
109
|
+
const value = (params ?? {}) as OfficialTabParams
|
|
110
|
+
const line = value?.line
|
|
111
|
+
if (typeof line !== 'number' || !Number.isFinite(line) || line <= 0) return undefined
|
|
112
|
+
return Math.floor(line)
|
|
113
|
+
}
|
|
114
|
+
|
|
100
115
|
/** 逐段 component 解码(失败返回 null,对齐官方 parseFileAddress 的容错语义)。 */
|
|
101
116
|
function decodeSegment(raw: string | undefined): string | null {
|
|
102
117
|
if (raw === undefined) return null
|
|
@@ -179,6 +194,36 @@ export function buildFileAddress(path: string, sessionId?: string): string {
|
|
|
179
194
|
return OFFICIAL_FILE_PREFIX + 'session/' + encodeSegment(sessionId) + '/' + relative.split('/').map(encodeSegment).join('/')
|
|
180
195
|
}
|
|
181
196
|
|
|
197
|
+
// --region file 认领转发(文件分页收归编辑器自带页签栏)
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 把 file 打开请求转发进单一编辑器页签(文件分页由编辑器自带页签栏接管,
|
|
201
|
+
* 官方侧栏不再按文件分裂出多套编辑器实例)。
|
|
202
|
+
* @author ddj 2026年09月10号
|
|
203
|
+
* @param service 官方 sidebarRight 服务(缺失/缺 openTab 返回 false)
|
|
204
|
+
* @param path 目标文件路径(空值返回 false)
|
|
205
|
+
* @param line 可选行号(透传给编辑器页签导航参数)
|
|
206
|
+
* @returns 转发是否成功(openTab 抛错返回 false,由调用方决定重试或兜底)
|
|
207
|
+
*/
|
|
208
|
+
export function forwardToEditor(
|
|
209
|
+
service: SidebarRightServiceLike | undefined | null,
|
|
210
|
+
path: string | null,
|
|
211
|
+
line?: number,
|
|
212
|
+
): boolean {
|
|
213
|
+
if (!service || typeof service.openTab !== 'function' || !path) return false
|
|
214
|
+
const params: OfficialTabParams = { openPath: path }
|
|
215
|
+
if (line !== undefined) params.line = line
|
|
216
|
+
try {
|
|
217
|
+
// 官方语义:页类型恒去重,故转发只会聚焦既有编辑器页签,不产生新实例
|
|
218
|
+
service.openTab(OFFICIAL_TAB_KIND, { params })
|
|
219
|
+
return true
|
|
220
|
+
} catch (error) {
|
|
221
|
+
log.warn('file 认领转发进编辑器页签失败(' + String(error) + ')')
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// --endregion
|
|
226
|
+
|
|
182
227
|
/**
|
|
183
228
|
* 注册官方 Tab 正文(keyed slot,key=定义 id;slots.inject 等待 rightbar seat 声明)。
|
|
184
229
|
* @author ddj 2026年09月09号
|
|
@@ -5,19 +5,20 @@
|
|
|
5
5
|
* 作者 ddj 2026-08-27
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react'
|
|
8
|
+
import { IconCodeOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
8
9
|
import { OutlinePanel } from './OutlinePanel.js'
|
|
9
10
|
import type { SidebarPanelDef, SidebarCtx } from '../sidebar/types.js'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 构造大纲面板定义。
|
|
13
|
-
* @author ddj 2026年08月27号
|
|
14
|
-
* @returns 面板定义(无徽标;活动栏图标
|
|
14
|
+
* @author ddj 2026年08月27号 / 2026年09月10号
|
|
15
|
+
* @returns 面板定义(无徽标;活动栏图标 = 官方 IconCodeOutline16)
|
|
15
16
|
*/
|
|
16
17
|
export function createOutlinePanel(): SidebarPanelDef {
|
|
17
18
|
return {
|
|
18
19
|
id: 'outline',
|
|
19
20
|
title: '大纲',
|
|
20
|
-
icon:
|
|
21
|
+
icon: IconCodeOutline16,
|
|
21
22
|
order: 20,
|
|
22
23
|
render: (ctx: SidebarCtx) => React.createElement(OutlinePanel, { ctx }),
|
|
23
24
|
}
|
|
@@ -18,6 +18,21 @@ const SEAM_W = 5
|
|
|
18
18
|
/** 隐藏态拖出面板区的触发阈值(px,拖过即展开并继续实时调宽)。 */
|
|
19
19
|
const PULL_OUT_THRESHOLD = 12
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 活动栏图标内容:面板可给官方图标组件(跟随官方主题),也可给文本/emoji(回落)。
|
|
23
|
+
* @author ddj 2026年09月10号
|
|
24
|
+
* @param icon 面板定义的 icon 字段(组件或文本)
|
|
25
|
+
* @returns 图标元素或文本;组件渲染异常返回 null
|
|
26
|
+
*/
|
|
27
|
+
function railIconEl(icon) {
|
|
28
|
+
if (typeof icon !== 'function') return icon
|
|
29
|
+
try {
|
|
30
|
+
return React.createElement(icon, { size: 16 })
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
/**
|
|
22
37
|
* 侧边栏容器。
|
|
23
38
|
* @param props.registry 面板注册表(list() 提供面板顺序)
|
|
@@ -112,7 +127,7 @@ export function SidebarView(props) {
|
|
|
112
127
|
if (!visible && typeof onShow === 'function') onShow()
|
|
113
128
|
},
|
|
114
129
|
},
|
|
115
|
-
React.createElement('span', { className: 'edrv-rail-icon' }, p.icon),
|
|
130
|
+
React.createElement('span', { className: 'edrv-rail-icon' }, railIconEl(p.icon)),
|
|
116
131
|
count > 0
|
|
117
132
|
? React.createElement('span', { className: 'edrv-rail-badge' }, String(count))
|
|
118
133
|
: null)
|