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
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* host 树索引,命中近乎零成本);挂载/切会话从缓存即时恢复展开树;⟳/edrv:refresh
|
|
6
6
|
* 保留旧条目强制后台重列(force);每次用户发起加载后预取 ≤4 个子目录(排除重型
|
|
7
7
|
* 目录、缓存新鲜跳过、不级联);10s 轻量跟随已展开目录(命中索引,RPC 近零成本)。
|
|
8
|
-
*
|
|
9
|
-
* 作者 ddj 2026-08-26 / 2026-08-27 / 2026-08-31
|
|
8
|
+
* 差异角标/右键菜单/展开状态持久化行为不变;行图标用官方原语(文件夹图标 + 文件类型图标)。
|
|
9
|
+
* 作者 ddj 2026-08-26 / 2026-08-27 / 2026-08-31 / 2026-09-10
|
|
10
10
|
*/
|
|
11
11
|
import React from 'react'
|
|
12
|
+
import { FileTypeIcon, IconFolderClose16, IconFolderOpen16, IconRefreshOutline16, classifyFileType } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
12
13
|
import { rpc } from '../../rpc.js'
|
|
13
14
|
import { ContextMenu } from '../../ui/ContextMenu.js'
|
|
14
15
|
import { buildTreeMenu } from '../contextMenu.js'
|
|
@@ -23,6 +24,58 @@ const FOLLOW_INTERVAL_MS = 10_000
|
|
|
23
24
|
const PREFETCH_MAX = 4
|
|
24
25
|
const PREFETCH_EXCLUDED = new Set(['node_modules', '.git', '.hg', '.svn', '.pnpm', '.pnpm-store'])
|
|
25
26
|
|
|
27
|
+
// --region 行图标(官方原语:目录文件夹图标 + 文件类型图标;缺失时回落纯文本)
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 目录图标元素(官方 IconFolderOpen16/Close16)。
|
|
31
|
+
* @author ddj 2026年09月10号
|
|
32
|
+
* @param isOpen 目录是否展开
|
|
33
|
+
* @returns 图标元素;原语缺失返回 null
|
|
34
|
+
*/
|
|
35
|
+
function dirIconEl(isOpen) {
|
|
36
|
+
const Icon = isOpen ? IconFolderOpen16 : IconFolderClose16
|
|
37
|
+
if (typeof Icon !== 'function') return null
|
|
38
|
+
return React.createElement(Icon, { size: 16, className: 'edrv-tree-icon' })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 文件图标元素(官方 FileTypeIcon + classifyFileType 分类,未知类型为 other)。
|
|
43
|
+
* @author ddj 2026年09月10号
|
|
44
|
+
* @param name 文件名(含扩展名)
|
|
45
|
+
* @returns 图标元素;原语缺失或分类异常返回 null
|
|
46
|
+
*/
|
|
47
|
+
function fileIconEl(name) {
|
|
48
|
+
if (typeof FileTypeIcon !== 'function' || typeof classifyFileType !== 'function') return null
|
|
49
|
+
try {
|
|
50
|
+
return React.createElement(FileTypeIcon, { kind: classifyFileType(String(name ?? '')), size: 16, className: 'edrv-tree-icon' })
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 行图标分派:目录 → 文件夹图标,文件 → 官方类型图标。
|
|
58
|
+
* @author ddj 2026年09月10号
|
|
59
|
+
* @param e 目录条目(name/type)
|
|
60
|
+
* @param isDir 是否目录
|
|
61
|
+
* @param isOpen 目录是否展开
|
|
62
|
+
* @returns 图标元素或 null
|
|
63
|
+
*/
|
|
64
|
+
function rowIconEl(e, isDir, isOpen) {
|
|
65
|
+
return isDir ? dirIconEl(isOpen) : fileIconEl(e?.name)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 刷新按钮图标(官方 IconRefreshOutline16;原语缺失回落文本 ⟳)。
|
|
70
|
+
* @author ddj 2026年09月10号
|
|
71
|
+
* @returns 图标元素或回落文本
|
|
72
|
+
*/
|
|
73
|
+
function refreshIconEl() {
|
|
74
|
+
if (typeof IconRefreshOutline16 !== 'function') return '⟳'
|
|
75
|
+
return React.createElement(IconRefreshOutline16, { size: 14 })
|
|
76
|
+
}
|
|
77
|
+
// --endregion
|
|
78
|
+
|
|
26
79
|
/**
|
|
27
80
|
* 目录树面板主体(SWR:有缓存先渲染,无缓存才显示加载态;加载总在后台)。
|
|
28
81
|
* @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath/fileMenuItems/notify)
|
|
@@ -227,8 +280,9 @@ export function FileExplorer(props) {
|
|
|
227
280
|
},
|
|
228
281
|
React.createElement('span', { className: 'edrv-tree-chev' },
|
|
229
282
|
isDir ? (isOpen ? '▾' : '▸') : ''),
|
|
283
|
+
rowIconEl(e, isDir, isOpen),
|
|
230
284
|
React.createElement('span', { className: 'edrv-tree-name' + (dim ? ' edrv-tree-dim' : '') },
|
|
231
|
-
|
|
285
|
+
e.name),
|
|
232
286
|
(pending > 0
|
|
233
287
|
? React.createElement('span', { className: 'edrv-tree-badge' }, String(pending))
|
|
234
288
|
: null))
|
|
@@ -283,7 +337,7 @@ export function FileExplorer(props) {
|
|
|
283
337
|
React.createElement('span', { className: 'edrv-side-title' }, '资源管理器'),
|
|
284
338
|
React.createElement('span', { className: 'edrv-side-root', title: root || '' }, rootName),
|
|
285
339
|
React.createElement('span', { style: { flex: 1 } }),
|
|
286
|
-
React.createElement('button', { className: 'edrv-side-btn', title: '刷新目录树', onClick: refresh },
|
|
340
|
+
React.createElement('button', { className: 'edrv-side-btn', title: '刷新目录树', onClick: refresh }, refreshIconEl())),
|
|
287
341
|
React.createElement('div', { className: 'edrv-tree' },
|
|
288
342
|
(errorText
|
|
289
343
|
? React.createElement('div', { className: 'edrv-tree-error' },
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
3
|
* dsh-vscode-mode client — 侧边栏面板定义(文件管理 + 搜索 + 规则)。
|
|
4
|
-
*
|
|
4
|
+
* 活动栏图标用官方 UI 原语(跟随 DSH 主题与皮肤);原语缺失时由 SidebarView 回落文本渲染。
|
|
5
|
+
* 作者 ddj 2026-08-26 / 2026-09-03 / 2026-09-10
|
|
5
6
|
*/
|
|
6
7
|
import React from 'react'
|
|
8
|
+
import { IconFolderOpenOutline16, IconListPenOutline16, IconSearchOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
7
9
|
import { FileExplorer } from './FileExplorer.js'
|
|
8
10
|
import { SearchPanel } from './SearchPanel.js'
|
|
9
11
|
import { RulesPanel } from './RulesPanel.js'
|
|
@@ -11,14 +13,14 @@ import type { SidebarPanelDef, SidebarCtx } from '../types.js'
|
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* 构造文件管理面板定义。
|
|
14
|
-
* @author ddj 2026年08月26号
|
|
15
|
-
* @returns 面板定义(含活动栏徽标 =
|
|
16
|
+
* @author ddj 2026年08月26号 / 2026年09月10号
|
|
17
|
+
* @returns 面板定义(含活动栏徽标 = 差异文件总数;图标 = 官方 IconFolderOpenOutline16)
|
|
16
18
|
*/
|
|
17
19
|
export function createFilePanel(): SidebarPanelDef {
|
|
18
20
|
return {
|
|
19
21
|
id: 'explorer',
|
|
20
22
|
title: '文件管理',
|
|
21
|
-
icon:
|
|
23
|
+
icon: IconFolderOpenOutline16,
|
|
22
24
|
order: 10,
|
|
23
25
|
badge: (ctx: SidebarCtx) => (ctx.sum && ctx.sum.totalFiles > 0 ? ctx.sum.totalFiles : null),
|
|
24
26
|
render: (ctx: SidebarCtx) => React.createElement(FileExplorer, { ctx }),
|
|
@@ -27,14 +29,14 @@ export function createFilePanel(): SidebarPanelDef {
|
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* 构造搜索面板定义。
|
|
30
|
-
* @author ddj 2026年08月30号
|
|
31
|
-
* @returns
|
|
32
|
+
* @author ddj 2026年08月30号 / 2026年09月10号
|
|
33
|
+
* @returns 面板定义(无徽标;图标 = 官方 IconSearchOutline16)
|
|
32
34
|
*/
|
|
33
35
|
export function createSearchPanel(): SidebarPanelDef {
|
|
34
36
|
return {
|
|
35
37
|
id: 'search',
|
|
36
38
|
title: '搜索',
|
|
37
|
-
icon:
|
|
39
|
+
icon: IconSearchOutline16,
|
|
38
40
|
order: 15,
|
|
39
41
|
render: (ctx: SidebarCtx) => React.createElement(SearchPanel, { ctx }),
|
|
40
42
|
}
|
|
@@ -42,14 +44,14 @@ export function createSearchPanel(): SidebarPanelDef {
|
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* 构造规则管理面板定义(Codebuddy 规则形态:用户规则/项目规则双 Tab + 开关)。
|
|
45
|
-
* @author ddj 2026年09月03号
|
|
46
|
-
* @returns
|
|
47
|
+
* @author ddj 2026年09月03号 / 2026年09月10号
|
|
48
|
+
* @returns 面板定义(无徽标;图标 = 官方 IconListPenOutline16)
|
|
47
49
|
*/
|
|
48
50
|
export function createRulesPanel(): SidebarPanelDef {
|
|
49
51
|
return {
|
|
50
52
|
id: 'rules',
|
|
51
53
|
title: '规则',
|
|
52
|
-
icon:
|
|
54
|
+
icon: IconListPenOutline16,
|
|
53
55
|
order: 20,
|
|
54
56
|
render: (ctx: SidebarCtx) => React.createElement(RulesPanel, { ctx }),
|
|
55
57
|
}
|
|
@@ -40,8 +40,8 @@ export interface SidebarCtx {
|
|
|
40
40
|
export interface SidebarPanelDef {
|
|
41
41
|
id: string
|
|
42
42
|
title: string
|
|
43
|
-
/**
|
|
44
|
-
icon: string
|
|
43
|
+
/** 活动栏图标:官方图标组件(跟随官方主题)或文本/emoji(回落),横向居中显示。 */
|
|
44
|
+
icon: string | ((props: { size?: number }) => unknown)
|
|
45
45
|
order?: number
|
|
46
46
|
/** 活动栏徽标:返回数字/null;缺省不显示。 */
|
|
47
47
|
badge?: (ctx: SidebarCtx) => number | null
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */
|
|
2
|
-
/*
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/* 主题:跟随官方 --dsw-alias-* 令牌(DSH 明暗与第三方皮肤生效)。
|
|
3
|
+
本作用域不再重定义官方令牌(旧实现曾在此硬覆盖浅色盘,导致插件不跟随 DSH 主题);
|
|
4
|
+
旧环境/缺令牌场景由每条声明的 var(--dsw-alias-*, 回落值) 兜底。
|
|
5
|
+
@author ddj 2026-09-10 */
|
|
6
|
+
[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; }
|
|
5
7
|
|
|
6
8
|
[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }
|
|
7
9
|
[data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }
|
|
@@ -9,7 +11,7 @@
|
|
|
9
11
|
[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }
|
|
10
12
|
[data-edrv-view] .edrv-tab .edrv-tab-star { color: var(--dsw-alias-brand-primary, #4f8cff); font-size: 13px; font-weight: 700; line-height: 1; flex-shrink: 0; } /* 未保存修改星号 @author ddj 2026年09月09号 */
|
|
11
13
|
[data-edrv-view] .edrv-tab .edrv-tab-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 4px; color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; }
|
|
12
|
-
[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: rgba(255,255,255,.12); color: var(--dsw-alias-label-primary, #eee); }
|
|
14
|
+
[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.12)); color: var(--dsw-alias-label-primary, #eee); }
|
|
13
15
|
[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }
|
|
14
16
|
[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }
|
|
15
17
|
[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }
|
|
28
30
|
[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
29
31
|
[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }
|
|
30
|
-
[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: #fff; font-weight: 600; white-space: nowrap; }
|
|
32
|
+
[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; white-space: nowrap; }
|
|
31
33
|
[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }
|
|
32
34
|
[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }
|
|
33
35
|
[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }
|
|
@@ -47,15 +49,15 @@
|
|
|
47
49
|
[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }
|
|
48
50
|
[data-edrv-view] .edrv-chip-btn:disabled { opacity: .35; cursor: default; }
|
|
49
51
|
[data-edrv-view] .edrv-chip-btn:disabled:hover { background: transparent; }
|
|
50
|
-
[data-edrv-view] .edrv-mn-add-line { background: rgba(15,157,88,.10); }
|
|
51
|
-
[data-edrv-view] .edrv-mn-gutter-add { color: #0f9d58 !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }
|
|
52
|
+
[data-edrv-view] .edrv-mn-add-line { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }
|
|
53
|
+
[data-edrv-view] .edrv-mn-gutter-add { color: var(--dsw-alias-state-success-primary, #0f9d58) !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }
|
|
52
54
|
[data-edrv-view] .edrv-mn-gutter-add::before { content: "+"; }
|
|
53
|
-
[data-edrv-view] .edrv-del-zone { background:
|
|
54
|
-
[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: #c0392b; }
|
|
55
|
+
[data-edrv-view] .edrv-del-zone { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); border-top: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); border-bottom: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); box-shadow: inset 0 0 0 1px var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); }
|
|
56
|
+
[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: var(--dsw-alias-state-error-primary, #c0392b); }
|
|
55
57
|
[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }
|
|
56
58
|
[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }
|
|
57
59
|
[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }
|
|
58
|
-
[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }
|
|
60
|
+
[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: var(--dsw-alias-state-error-primary, #d9534f); }
|
|
59
61
|
[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
|
|
60
62
|
[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }
|
|
61
63
|
[data-edrv-view] .edrv-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
@@ -83,9 +85,9 @@
|
|
|
83
85
|
[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }
|
|
84
86
|
[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
85
87
|
[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }
|
|
86
|
-
[data-edrv-view] .edrv-pill-keep { background:
|
|
87
|
-
[data-edrv-view] .edrv-pill-undo { background:
|
|
88
|
-
[data-edrv-view] .edrv-pill-ghost { background: #ffffff; color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }
|
|
88
|
+
[data-edrv-view] .edrv-pill-keep { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-state-success-primary, #0f7a45); border: 1px solid var(--dsw-alias-state-success-primary, rgba(15,157,88,.4)); }
|
|
89
|
+
[data-edrv-view] .edrv-pill-undo { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); color: var(--dsw-alias-state-error-primary, #c0392b); border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }
|
|
90
|
+
[data-edrv-view] .edrv-pill-ghost { background: var(--dsw-alias-bg-layer-1, #ffffff); color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }
|
|
89
91
|
[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }
|
|
90
92
|
[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }
|
|
91
93
|
[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }
|
|
@@ -107,7 +109,7 @@
|
|
|
107
109
|
[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }
|
|
108
110
|
[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }
|
|
109
111
|
[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }
|
|
110
|
-
[data-edrv-view] .edrv-diffrow-stale { border-color: rgba(217,119,6,.5); cursor: default; }
|
|
112
|
+
[data-edrv-view] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-primary, rgba(217,119,6,.5)); cursor: default; }
|
|
111
113
|
[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }
|
|
112
114
|
[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }
|
|
113
115
|
[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }
|
|
@@ -228,8 +230,11 @@
|
|
|
228
230
|
[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }
|
|
229
231
|
[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
230
232
|
[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }
|
|
231
|
-
[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; }
|
|
232
|
-
|
|
233
|
+
[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; }
|
|
234
|
+
/* 树行图标:官方原语(文件夹/文件类型),尺寸与行高对齐 @author ddj 2026-09-10 */
|
|
235
|
+
[data-edrv-view] .edrv-tree-icon { flex-shrink: 0; display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }
|
|
236
|
+
[data-edrv-view] .edrv-tree-row.edrv-tree-active .edrv-tree-icon { color: var(--dsw-alias-label-secondary, #52606d); }
|
|
237
|
+
[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }
|
|
233
238
|
[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }
|
|
234
239
|
[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }
|
|
235
240
|
[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }
|
|
@@ -245,9 +250,9 @@
|
|
|
245
250
|
[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }
|
|
246
251
|
[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
247
252
|
[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }
|
|
248
|
-
[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }
|
|
253
|
+
[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }
|
|
249
254
|
[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }
|
|
250
|
-
[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background:
|
|
255
|
+
[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }
|
|
251
256
|
/* 大纲面板:kind 字形 / detail / 行号 */
|
|
252
257
|
[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }
|
|
253
258
|
[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }
|
|
@@ -295,10 +300,10 @@
|
|
|
295
300
|
[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
296
301
|
[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }
|
|
297
302
|
[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }
|
|
298
|
-
[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: #1f2933; border-radius: 2px; padding: 0 1px; }
|
|
303
|
+
[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: var(--dsw-alias-label-primary-inverted, #1f2933); border-radius: 2px; padding: 0 1px; }
|
|
299
304
|
[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }
|
|
300
|
-
[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background:
|
|
301
|
-
[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid rgba(240,173,78,.45); border-radius: 6px; background:
|
|
305
|
+
[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }
|
|
306
|
+
[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-warn-primary, rgba(240,173,78,.45)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(240,173,78,.12)); color: var(--dsw-alias-state-warn-primary, #b8860b); font-size: 11px; word-break: break-all; }
|
|
302
307
|
|
|
303
308
|
/* include/exclude filter rows (collapsible) */
|
|
304
309
|
[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }
|
|
@@ -333,20 +338,20 @@
|
|
|
333
338
|
[data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
334
339
|
[data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
335
340
|
[data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }
|
|
336
|
-
[data-edrv-view] .edrv-rules-type-always { background: rgba(15,157,88,.15); color: #0b7c46; }
|
|
337
|
-
[data-edrv-view] .edrv-rules-type-auto { background: rgba(79,140,255,.16); color: #3367c9; }
|
|
341
|
+
[data-edrv-view] .edrv-rules-type-always { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.15)); color: var(--dsw-alias-state-success-primary, #0b7c46); }
|
|
342
|
+
[data-edrv-view] .edrv-rules-type-auto { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(79,140,255,.16)); color: var(--dsw-alias-link, #3367c9); }
|
|
338
343
|
[data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }
|
|
339
344
|
[data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }
|
|
340
345
|
[data-edrv-view] .edrv-rules-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }
|
|
341
346
|
[data-edrv-view] .edrv-rules-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }
|
|
342
347
|
[data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); display: inline-flex; align-items: center; justify-content: center; }
|
|
343
348
|
[data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
|
|
344
|
-
[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: #b9c4cc; cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }
|
|
345
|
-
[data-edrv-view] .edrv-rules-switch.on { background: #7c5cff; }
|
|
346
|
-
[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: #fff; transition: left .15s; }
|
|
349
|
+
[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: var(--dsw-alias-border-l2, #b9c4cc); cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }
|
|
350
|
+
[data-edrv-view] .edrv-rules-switch.on { background: var(--dsw-alias-brand-primary, #7c5cff); }
|
|
351
|
+
[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--dsw-alias-bg-layer-1, #fff); transition: left .15s; }
|
|
347
352
|
[data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }
|
|
348
353
|
[data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }
|
|
349
|
-
[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background:
|
|
354
|
+
[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }
|
|
350
355
|
[data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }
|
|
351
356
|
[data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }
|
|
352
357
|
[data-edrv-view] .edrv-rules-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -361,8 +366,22 @@
|
|
|
361
366
|
[data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }
|
|
362
367
|
[data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }
|
|
363
368
|
[data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }
|
|
364
|
-
[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 12px; cursor: pointer; }
|
|
369
|
+
[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 12px; cursor: pointer; }
|
|
365
370
|
[data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }
|
|
366
371
|
|
|
372
|
+
/* 命令栏(Ctrl+Shift+P / F1):body 级 portal 浮层,故不依赖编辑区几何 */
|
|
373
|
+
[data-edrv-view] .edrv-palette-mask { position: fixed; inset: 0; z-index: 190; background: rgba(0, 0, 0, .28); }
|
|
374
|
+
[data-edrv-view] .edrv-palette { position: fixed; top: 12vh; left: 50%; transform: translateX(-50%); z-index: 191; width: min(580px, calc(100vw - 32px)); max-height: 64vh; display: flex; flex-direction: column; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 12px 34px rgba(31, 41, 51, .24); overflow: hidden; }
|
|
375
|
+
[data-edrv-view] .edrv-palette-input { height: 38px; flex: 0 0 auto; padding: 0 12px; border: none; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: transparent; color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; outline: none; }
|
|
376
|
+
[data-edrv-view] .edrv-palette-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 4px; }
|
|
377
|
+
[data-edrv-view] .edrv-palette-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; }
|
|
378
|
+
[data-edrv-view] .edrv-palette-row.edrv-palette-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 157, 88, .10)); }
|
|
379
|
+
[data-edrv-view] .edrv-palette-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
380
|
+
[data-edrv-view] .edrv-palette-cat { flex: 0 0 auto; font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #7b8794); }
|
|
381
|
+
[data-edrv-view] .edrv-palette-key { flex: 0 0 auto; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 10px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); }
|
|
382
|
+
[data-edrv-view] .edrv-palette-empty { padding: 14px 10px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #7b8794); text-align: center; }
|
|
383
|
+
[data-edrv-view] .edrv-palette-notice { flex: 0 0 auto; padding: 6px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-state-warn-primary, #b7791f); }
|
|
384
|
+
|
|
385
|
+
|
|
367
386
|
|
|
368
387
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — ClaimRouter:官方 file 认领页签的中转正文。
|
|
4
|
+
* 官方为每个 `dsh-resource://file/**` 地址开一个认领页签;本组件只做转发:
|
|
5
|
+
* 解析地址 → 送进单一编辑器页签(文件分页由编辑器自带页签栏接管)→ 关闭本中转页签。
|
|
6
|
+
* 转发失败(seat 未就绪等瞬时态)按 3 次 ×150ms 重试;仍失败则就地渲染兜底正文
|
|
7
|
+
* (旧行为:每文件一套编辑器),保证退化路径不白屏。本组件自身不创建 Monaco 实例。
|
|
8
|
+
* 作者 ddj 2026年09月10号
|
|
9
|
+
*/
|
|
10
|
+
import React from 'react'
|
|
11
|
+
import { forwardToEditor, parseOfficialFileAddress, resolveNavLine } from '../officialSidebar.js'
|
|
12
|
+
import { log } from '../log.js'
|
|
13
|
+
|
|
14
|
+
/** 转发尝试次数(含首次)与重试间隔。 */
|
|
15
|
+
const FORWARD_ATTEMPTS = 3
|
|
16
|
+
const FORWARD_DELAY_MS = 150
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 读 slot 框架注入的 tab 记录(hook 缺失或抛错时返回 undefined)。
|
|
20
|
+
* @author ddj 2026年09月10号
|
|
21
|
+
* @param props slot 框架注入的组件 props
|
|
22
|
+
* @returns tab 记录(含 navigation 与 actions),或 undefined
|
|
23
|
+
*/
|
|
24
|
+
function readTab(props) {
|
|
25
|
+
const useTabInfo = props?.useTabInfo
|
|
26
|
+
if (typeof useTabInfo !== 'function') return undefined
|
|
27
|
+
try {
|
|
28
|
+
return useTabInfo()?.tab
|
|
29
|
+
} catch {
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 关闭本中转页签(失败仅告警:目标文件已在编辑器打开,残留页签不阻塞功能)。
|
|
36
|
+
* @author ddj 2026年09月10号
|
|
37
|
+
* @param tab slot 框架注入的 tab 记录
|
|
38
|
+
*/
|
|
39
|
+
function closeTab(tab) {
|
|
40
|
+
try {
|
|
41
|
+
tab?.actions?.close?.()
|
|
42
|
+
} catch (error) {
|
|
43
|
+
log.warn('中转页签关闭失败(' + String(error) + ')')
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 组装 file 认领转发正文。
|
|
49
|
+
* @author ddj 2026年09月10号
|
|
50
|
+
* @param spec 装配依赖(service 官方导航服务 / schedule 延时调度 / fallback 兜底正文)
|
|
51
|
+
* @returns 正文组件(注册进 keyed slot sidebar.right.pane.tab)
|
|
52
|
+
*/
|
|
53
|
+
export function createClaimRouter(spec) {
|
|
54
|
+
const service = spec?.service
|
|
55
|
+
const schedule = spec?.schedule
|
|
56
|
+
const fallback = spec?.fallback
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 中转正文:转发成功即关闭自身;重试耗尽后渲染兜底正文。
|
|
60
|
+
* @param props slot 框架注入(useTabInfo)+ 装配依赖
|
|
61
|
+
* @returns 兜底正文元素,或 null(转发中 / 已转发)
|
|
62
|
+
*/
|
|
63
|
+
function ClaimRouter(props) {
|
|
64
|
+
const tab = readTab(props)
|
|
65
|
+
const navigation = tab?.navigation
|
|
66
|
+
const revision = typeof navigation?.revision === 'number' ? navigation.revision : -1
|
|
67
|
+
const [failed, setFailed] = React.useState(false)
|
|
68
|
+
const doneRef = React.useRef(-1)
|
|
69
|
+
|
|
70
|
+
React.useEffect(() => {
|
|
71
|
+
if (revision === doneRef.current) return
|
|
72
|
+
doneRef.current = revision
|
|
73
|
+
const parsed = parseOfficialFileAddress(navigation?.address)
|
|
74
|
+
if (!parsed) {
|
|
75
|
+
setFailed(true)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
const line = resolveNavLine(navigation?.params)
|
|
79
|
+
let left = FORWARD_ATTEMPTS
|
|
80
|
+
const attempt = () => {
|
|
81
|
+
if (forwardToEditor(service, parsed.path, line)) {
|
|
82
|
+
closeTab(tab)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
left -= 1
|
|
86
|
+
if (left > 0 && typeof schedule === 'function') schedule(attempt, FORWARD_DELAY_MS)
|
|
87
|
+
else setFailed(true)
|
|
88
|
+
}
|
|
89
|
+
attempt()
|
|
90
|
+
}, [revision])
|
|
91
|
+
|
|
92
|
+
if (!failed || typeof fallback !== 'function') return null
|
|
93
|
+
return React.createElement(fallback, props)
|
|
94
|
+
}
|
|
95
|
+
return ClaimRouter
|
|
96
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 命令栏浮层(Ctrl+Shift+P / F1)。
|
|
3
|
+
* 订阅 commandPaletteStore 的开关状态;候选来自注入的指令注册表(window.dsh.edrvCommands),
|
|
4
|
+
* 过滤/排序复用 commandSearch 纯函数;↑↓ 选择、Enter 执行、Esc 或点击遮罩关闭。
|
|
5
|
+
* 浮层经 createPortal 渲染到 body,故不受编辑区三种形态(官方侧栏/better-sidebar/中央页签)影响;
|
|
6
|
+
* 宿主单实例由 store 的 claimPaletteHost 保证(另一个宿主自动返回 null)。
|
|
7
|
+
* 作者 ddj 2026年09月10号
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react'
|
|
10
|
+
import { createPortal } from 'react-dom'
|
|
11
|
+
import { REGISTRY_GLOBAL } from '../commandGlobals.js'
|
|
12
|
+
import { filterCommands } from '../commandSearch.js'
|
|
13
|
+
import { createCommandRegistry } from '../commandRegistry.js'
|
|
14
|
+
import type { CommandRegistry } from '../commandRegistry.js'
|
|
15
|
+
import { chordOf } from '../keybindings.js'
|
|
16
|
+
import {
|
|
17
|
+
claimPaletteHost, closeCommandPalette, isPaletteOpen, registryRef, releasePaletteHost,
|
|
18
|
+
runPaletteCommand, subscribePalette,
|
|
19
|
+
} from '../commandPaletteStore.js'
|
|
20
|
+
import type { CommandDef } from './commandCatalog.js'
|
|
21
|
+
|
|
22
|
+
/** 空注册表(宿主未注入时的安全降级:命令栏可开但无候选)。 */
|
|
23
|
+
let fallbackRegistry: CommandRegistry | null = null
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 读取指令注册表:① 装配期存入的模块引用(首选,不依赖全局)
|
|
27
|
+
* ② window[REGISTRY_GLOBAL] 镜像(第三方/调试场景)③ 空表降级(仅装配异常时)。
|
|
28
|
+
* @author ddj 2026年09月10号
|
|
29
|
+
* @returns 指令注册表
|
|
30
|
+
*/
|
|
31
|
+
function useRegistry(): CommandRegistry {
|
|
32
|
+
const injected = registryRef()
|
|
33
|
+
if (injected) return injected
|
|
34
|
+
const mirrored = typeof window === 'undefined'
|
|
35
|
+
? undefined
|
|
36
|
+
: (window as unknown as Record<string, CommandRegistry | undefined>)[REGISTRY_GLOBAL]
|
|
37
|
+
if (mirrored) return mirrored
|
|
38
|
+
if (!fallbackRegistry) fallbackRegistry = createCommandRegistry()
|
|
39
|
+
return fallbackRegistry
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 命令栏每一行的展示信息。 */
|
|
43
|
+
interface PaletteRow {
|
|
44
|
+
command: CommandDef
|
|
45
|
+
chord: string | null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 组装候选行(先按可用性过滤,再按查询排序)。
|
|
50
|
+
* @author ddj 2026年09月10号
|
|
51
|
+
* @param registry 指令注册表
|
|
52
|
+
* @param query 用户输入
|
|
53
|
+
* @returns 候选行
|
|
54
|
+
*/
|
|
55
|
+
function rowsOf(registry: CommandRegistry, query: string): PaletteRow[] {
|
|
56
|
+
return filterCommands(registry.available(), query)
|
|
57
|
+
.map((command) => ({ command, chord: chordOf(command.id) }))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 命令栏浮层(未展开/非宿主时渲染 null)。
|
|
62
|
+
* @author ddj 2026年09月10号
|
|
63
|
+
* @returns 浮层 React 元素或 null
|
|
64
|
+
*/
|
|
65
|
+
export function CommandPalette(): React.ReactElement | null {
|
|
66
|
+
const open = React.useSyncExternalStore(subscribePalette, isPaletteOpen)
|
|
67
|
+
const registry = useRegistry()
|
|
68
|
+
const [token] = React.useState(() => (claimPaletteHost() ? {} : null))
|
|
69
|
+
const [query, setQuery] = React.useState('')
|
|
70
|
+
const [selected, setSelected] = React.useState(0)
|
|
71
|
+
const [notice, setNotice] = React.useState('')
|
|
72
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null)
|
|
73
|
+
const host = token !== null
|
|
74
|
+
|
|
75
|
+
React.useEffect(() => () => releasePaletteHost(token), [token])
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
if (!open) return undefined
|
|
78
|
+
setQuery('')
|
|
79
|
+
setSelected(0)
|
|
80
|
+
setNotice('')
|
|
81
|
+
const timer = setTimeout(() => inputRef.current?.focus(), 0)
|
|
82
|
+
return () => clearTimeout(timer)
|
|
83
|
+
}, [open])
|
|
84
|
+
|
|
85
|
+
const rows = React.useMemo(
|
|
86
|
+
() => (open && host ? rowsOf(registry, query) : []),
|
|
87
|
+
[open, host, registry, query],
|
|
88
|
+
)
|
|
89
|
+
const active = rows.length ? Math.min(selected, rows.length - 1) : 0
|
|
90
|
+
|
|
91
|
+
React.useEffect(() => {
|
|
92
|
+
if (!open) return
|
|
93
|
+
const node = document.querySelector('.edrv-palette-row.edrv-palette-sel')
|
|
94
|
+
const scroll = (node as HTMLElement | null)?.scrollIntoView
|
|
95
|
+
if (typeof scroll === 'function') scroll.call(node, { block: 'nearest' })
|
|
96
|
+
}, [open, active, rows.length])
|
|
97
|
+
|
|
98
|
+
/** 执行候选行(执行器内部会做可用性校验与异常上报)。 */
|
|
99
|
+
const execute = (row: PaletteRow | undefined): void => {
|
|
100
|
+
if (!row) return
|
|
101
|
+
closeCommandPalette()
|
|
102
|
+
if (!runPaletteCommand(row.command.id)) setNotice('命令未执行:' + row.command.label)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
|
106
|
+
event.stopPropagation()
|
|
107
|
+
if (event.key === 'Escape') {
|
|
108
|
+
event.preventDefault()
|
|
109
|
+
closeCommandPalette()
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
113
|
+
event.preventDefault()
|
|
114
|
+
if (!rows.length) return
|
|
115
|
+
const step = event.key === 'ArrowDown' ? 1 : rows.length - 1
|
|
116
|
+
setSelected((value) => (Math.min(value, rows.length - 1) + step) % rows.length)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
if (event.key === 'Enter') {
|
|
120
|
+
event.preventDefault()
|
|
121
|
+
execute(rows[active])
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!open || !host || typeof document === 'undefined') return null
|
|
126
|
+
const list = rows.length
|
|
127
|
+
? rows.map((row, index) => React.createElement('div', {
|
|
128
|
+
key: row.command.id,
|
|
129
|
+
className: 'edrv-palette-row' + (index === active ? ' edrv-palette-sel' : ''),
|
|
130
|
+
onMouseEnter: () => setSelected(index),
|
|
131
|
+
onClick: () => execute(row),
|
|
132
|
+
},
|
|
133
|
+
React.createElement('span', { className: 'edrv-palette-label' }, row.command.label),
|
|
134
|
+
React.createElement('span', { className: 'edrv-palette-cat' }, row.command.category),
|
|
135
|
+
row.chord ? React.createElement('kbd', { className: 'edrv-palette-key' }, row.chord) : null))
|
|
136
|
+
: React.createElement('div', { className: 'edrv-palette-empty' }, '无匹配命令')
|
|
137
|
+
|
|
138
|
+
const panel = React.createElement('div', { className: 'edrv-palette', 'data-edrv-palette': '1', onKeyDown },
|
|
139
|
+
React.createElement('input', {
|
|
140
|
+
ref: inputRef,
|
|
141
|
+
className: 'edrv-palette-input',
|
|
142
|
+
value: query,
|
|
143
|
+
placeholder: '输入命令名称…(↑↓ 选择 · Enter 执行 · Esc 关闭)',
|
|
144
|
+
spellCheck: false,
|
|
145
|
+
onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
146
|
+
setQuery(event.target.value)
|
|
147
|
+
setSelected(0)
|
|
148
|
+
setNotice('')
|
|
149
|
+
},
|
|
150
|
+
}),
|
|
151
|
+
React.createElement('div', { className: 'edrv-palette-list' }, list),
|
|
152
|
+
notice ? React.createElement('div', { className: 'edrv-palette-notice' }, notice) : null)
|
|
153
|
+
|
|
154
|
+
const overlay = React.createElement('div', { 'data-edrv-view': '1' },
|
|
155
|
+
React.createElement('div', { className: 'edrv-palette-mask', onClick: () => closeCommandPalette() }),
|
|
156
|
+
panel)
|
|
157
|
+
return createPortal(overlay, document.body)
|
|
158
|
+
}
|