dsh-vscode-mode 0.7.0 → 0.8.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 +23 -6
- package/assets/icon.svg +7 -0
- package/cordis.patch.yml +12 -0
- package/lib/client.js +1167 -188
- package/lib/client.js.map +1 -1
- package/lib/index.js +845 -96
- package/lib/index.js.map +1 -1
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +3 -1
- package/src/client/compat.ts +134 -4
- package/src/client/imagePreview.ts +76 -2
- package/src/client/index.ts +65 -36
- package/src/client/monaco/lsp/lspClient.ts +46 -0
- package/src/client/monaco/lsp/providers.ts +245 -1
- package/src/client/nativeOpenStore.ts +91 -0
- package/src/client/officialSidebar.ts +11 -45
- package/src/client/outline/index.ts +1 -1
- package/src/client/pdf/pdfPanel.ts +18 -0
- package/src/client/settingsContext.ts +6 -4
- package/src/client/sidebar/panels/FileExplorer.ts +1 -1
- package/src/client/sidebar/panels/SvnPanel.ts +1 -1
- package/src/client/sidebar/panels/index.ts +1 -1
- package/src/client/styles/editor.css +5 -3
- package/src/client/svnStore.ts +6 -1
- package/src/client/ui/EditorView.ts +117 -18
- package/src/client/ui/McpSettings.ts +30 -0
- package/src/client/ui/PerfSettings.ts +4 -1
- package/src/client/ui/icons.ts +72 -0
- package/src/compat.ts +2 -2
- package/src/dshVersion.ts +2 -1
- package/src/fileOpenSettings.ts +163 -40
- package/src/index.ts +15 -1
- package/src/lsp/client.ts +24 -0
- package/src/lsp/rpc.ts +57 -0
- package/src/lsp/server.ts +272 -2
- package/src/perf.ts +90 -4
- package/src/routes.ts +13 -1
- package/src/rpc.ts +96 -34
- package/src/shared/lsp.ts +79 -0
- package/src/shared/nativeOpen.ts +91 -0
- package/src/shared/rpc.ts +146 -1
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 DSH 0.1.5+ 官方右侧 Sidebar 与对话同屏)+ 命令栏(Ctrl+Shift+P)与可扩展指令系统 + VS Code 兼容代码片段(.code-snippets 全局/项目,IntelliSense 展开)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ SVN 集成(侧栏变更面板/日志弹窗/并排差异)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车",
|
|
5
|
+
"icon": "assets/icon.svg",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"dsh",
|
|
7
8
|
"dsh-plugin",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
"files": [
|
|
36
37
|
"lib",
|
|
37
38
|
"assets",
|
|
39
|
+
"locale",
|
|
38
40
|
"skills",
|
|
39
41
|
"src",
|
|
40
42
|
"unity",
|
package/src/client/compat.ts
CHANGED
|
@@ -10,21 +10,97 @@ export const PLUGIN_NAME = 'dsh-vscode-mode'
|
|
|
10
10
|
/** 外部侧栏插件名(dsh-web-ui-all 家族的文件打开能力提供方)。 */
|
|
11
11
|
export const SIDEBAR_PLUGIN = 'dsh-better-sidebar'
|
|
12
12
|
|
|
13
|
-
/**
|
|
13
|
+
/** 设置值的已知业务字段(各设置桥透传的 namespace 内容)。 */
|
|
14
|
+
export interface SettingsValueLike {
|
|
15
|
+
fileOpenTool?: unknown
|
|
16
|
+
keybindings?: Record<string, string>
|
|
17
|
+
sidebarMinWidth?: unknown
|
|
18
|
+
maxOpenEditors?: unknown
|
|
19
|
+
/** 原生打开范围(逗号分隔后缀;0.1.7 批次新增,旧版 undefined → 默认集)。 */
|
|
20
|
+
nativeOpenExts?: unknown
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 设置 scope 的跨组件上下文形状(与设置桥绑定的最小面:settingsScope / configForms 适配后同形)。 */
|
|
14
24
|
export interface SettingsScopeLike {
|
|
15
|
-
getSnapshot: () => { status?: string; value?:
|
|
25
|
+
getSnapshot: () => { status?: string; value?: SettingsValueLike; writable?: boolean }
|
|
16
26
|
subscribe: (listener: () => void) => () => void
|
|
17
27
|
set: (field: string, value: unknown) => Promise<void>
|
|
18
28
|
}
|
|
19
29
|
|
|
30
|
+
/** DSH 0.1.7+ configForms 服务最小面(0.1.6 无此服务,探测必须可降级)。 */
|
|
31
|
+
interface ConfigFormsService {
|
|
32
|
+
get?: (entryId: string) => unknown
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** DSH 0.1.7+ 单个 ConfigForm 最小面(适配 SettingsScopeLike 所需子集)。 */
|
|
36
|
+
interface ConfigFormLike {
|
|
37
|
+
getSnapshot: () => { status?: string; value?: unknown; writable?: boolean }
|
|
38
|
+
subscribe: (listener: () => void) => () => void
|
|
39
|
+
set: (field: string, value: unknown) => Promise<unknown>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 形状守卫:判断 configForms.get 返回值是否具备适配所需的最小面。
|
|
44
|
+
* @author ddj 2026年09月22号
|
|
45
|
+
* @param form configForms.get(entryId) 的返回值
|
|
46
|
+
* @returns 是否为可用 ConfigForm
|
|
47
|
+
*/
|
|
48
|
+
function isConfigForm(form: unknown): form is ConfigFormLike {
|
|
49
|
+
if (!form || typeof form !== 'object') return false
|
|
50
|
+
const candidate = form as Partial<ConfigFormLike>
|
|
51
|
+
return typeof candidate.getSnapshot === 'function'
|
|
52
|
+
&& typeof candidate.subscribe === 'function'
|
|
53
|
+
&& typeof candidate.set === 'function'
|
|
54
|
+
}
|
|
55
|
+
|
|
20
56
|
/**
|
|
21
|
-
*
|
|
57
|
+
* 将 DSH 0.1.7 ConfigForm 适配为既有 SettingsScopeLike(调用方零改动)。
|
|
58
|
+
* status/value/writable 直接透传('ready'|'loading'|'unavailable' 与既有 status 词汇一致);
|
|
59
|
+
* set 丢弃返回的 boolean(避免给未 catch 的既有调用方新增拒绝;form.set 自身 reject 仍传导)。
|
|
60
|
+
* @author ddj 2026年09月22号
|
|
61
|
+
* @param form configForms.get(PLUGIN_NAME) 返回的表单
|
|
62
|
+
* @returns 适配后的设置 scope
|
|
63
|
+
*/
|
|
64
|
+
function adaptConfigForm(form: ConfigFormLike): SettingsScopeLike {
|
|
65
|
+
return {
|
|
66
|
+
getSnapshot: () => {
|
|
67
|
+
const snap = form.getSnapshot()
|
|
68
|
+
return { status: snap.status, value: snap.value as SettingsValueLike | undefined, writable: snap.writable }
|
|
69
|
+
},
|
|
70
|
+
subscribe: (listener) => form.subscribe(listener),
|
|
71
|
+
set: async (field, value) => { await form.set(field, value) },
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 探测 DSH 0.1.7+ configForms 官方新设置桥(entryId = PLUGIN_NAME)。
|
|
77
|
+
* 0.1.6 无该服务、ctx.get/表单 get 抛错或形状不符时返回 undefined,交由原两级桥降级。
|
|
78
|
+
* @author ddj 2026年09月22号
|
|
79
|
+
* @param ctx 客户端服务上下文
|
|
80
|
+
* @returns 适配后的设置 scope 或 undefined
|
|
81
|
+
*/
|
|
82
|
+
function bindConfigForm(ctx: { get: (name: string) => unknown }): SettingsScopeLike | undefined {
|
|
83
|
+
try {
|
|
84
|
+
const forms = ctx.get('configForms') as ConfigFormsService | undefined
|
|
85
|
+
const form = forms?.get?.(PLUGIN_NAME)
|
|
86
|
+
if (!isConfigForm(form)) return undefined
|
|
87
|
+
return adaptConfigForm(form)
|
|
88
|
+
} catch {
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 设置桥优先序:configForms(DSH 0.1.7+ 官方新桥)→ webUiSettings(linxin666 兼容桥,
|
|
95
|
+
* 旧版现网优先级不变)→ settingsScope(DSH 0.1.6 官方旧桥)→ 无。
|
|
22
96
|
* 桥绑定抛错时降级到下一级,不让单个桥异常拖垮设置装配。
|
|
23
|
-
* @author ddj 2026年08月24号
|
|
97
|
+
* @author ddj 2026年08月24号(2026年09月22号 增补 configForms 桥)
|
|
24
98
|
* @param ctx 客户端服务上下文
|
|
25
99
|
* @returns 激活的桥名称与绑定 scope
|
|
26
100
|
*/
|
|
27
101
|
export function pickSettingsBinder(ctx: { get: (name: string) => unknown }): { service: string; scope: SettingsScopeLike | undefined } {
|
|
102
|
+
const configForm = bindConfigForm(ctx)
|
|
103
|
+
if (configForm) return { service: 'configForms', scope: configForm }
|
|
28
104
|
for (const service of ['webUiSettings', 'settingsScope']) {
|
|
29
105
|
try {
|
|
30
106
|
const binder = ctx.get(service) as { bind?: (spec: { namespace: string }) => SettingsScopeLike } | undefined
|
|
@@ -37,6 +113,60 @@ export function pickSettingsBinder(ctx: { get: (name: string) => unknown }): { s
|
|
|
37
113
|
return { service: 'none', scope: undefined }
|
|
38
114
|
}
|
|
39
115
|
|
|
116
|
+
/** 设置桥形状(探测 + 晚到有界重试 + 就绪通知;调用方按需取用)。 */
|
|
117
|
+
export interface SettingsBridge {
|
|
118
|
+
scope: () => SettingsScopeLike | undefined
|
|
119
|
+
service: () => string
|
|
120
|
+
whenReady: (listener: () => void) => () => void
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 设置桥装配(带晚到有界重试):设置桥服务一律不进 inject(0.1.6 settingsScope /
|
|
125
|
+
* 0.1.7 configForms 互斥,写死任一桥名都会在另一版本停等),因此提供方可能晚于本插件
|
|
126
|
+
* apply 就绪——构造时探测一次,未命中则按 schedule 节奏有界重试,命中即通知 whenReady
|
|
127
|
+
* 订阅者(重挂设置同步 effect);重试用尽保持未就绪(与旧行为一致,不无限轮询)。
|
|
128
|
+
* @author ddj 2026年09月22号
|
|
129
|
+
* @param ctx 客户端服务上下文
|
|
130
|
+
* @param options schedule 注入(缺省不重试=旧行为);attempts/intervalMs 重试节奏
|
|
131
|
+
* @returns 设置桥
|
|
132
|
+
*/
|
|
133
|
+
export function settingsBridge(
|
|
134
|
+
ctx: { get: (name: string) => unknown },
|
|
135
|
+
options?: { schedule?: (fn: () => void, ms: number) => void; attempts?: number; intervalMs?: number },
|
|
136
|
+
): SettingsBridge {
|
|
137
|
+
let hit = pickSettingsBinder(ctx)
|
|
138
|
+
let attemptsLeft = options?.attempts ?? 0
|
|
139
|
+
const interval = options?.intervalMs ?? 2000
|
|
140
|
+
const waiters: Array<() => void> = []
|
|
141
|
+
const notify = (): void => {
|
|
142
|
+
for (const listener of waiters.splice(0)) {
|
|
143
|
+
try { listener() } catch { /* 单个订阅者异常不拖垮其余 */ }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const retry = (): void => {
|
|
147
|
+
if (hit.scope || attemptsLeft <= 0 || !options?.schedule) return
|
|
148
|
+
attemptsLeft -= 1
|
|
149
|
+
options.schedule(() => {
|
|
150
|
+
hit = pickSettingsBinder(ctx)
|
|
151
|
+
if (hit.scope) { notify(); return }
|
|
152
|
+
retry()
|
|
153
|
+
}, interval)
|
|
154
|
+
}
|
|
155
|
+
retry()
|
|
156
|
+
return {
|
|
157
|
+
scope: () => hit.scope,
|
|
158
|
+
service: () => hit.service,
|
|
159
|
+
whenReady: (listener) => {
|
|
160
|
+
if (hit.scope) { listener(); return () => {} }
|
|
161
|
+
waiters.push(listener)
|
|
162
|
+
return () => {
|
|
163
|
+
const at = waiters.indexOf(listener)
|
|
164
|
+
if (at >= 0) waiters.splice(at, 1)
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
40
170
|
/** slot 注册描述(对齐 ctx.slots.register 的形状)。 */
|
|
41
171
|
export interface SlotSpec {
|
|
42
172
|
name: string
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-vscode-mode client —
|
|
3
|
-
* 纯函数、无 DOM 依赖,便于单测;MIME 表收敛在 shared/rpc.ts(host base64
|
|
2
|
+
* dsh-vscode-mode client — 图片文件判定、data URL 组装与缩放纯逻辑(编辑区图片预览面板用)。
|
|
3
|
+
* 纯函数、无 DOM 依赖,便于单测;MIME 表收敛在 shared/rpc.ts(host base64 响应同源);
|
|
4
|
+
* 缩放:步进/clamp 为纯函数,页签记忆为模块级 Map(会话+路径键,EditorView 卸载清空、不持久化)。
|
|
4
5
|
* @author ddj 2026年09月08号
|
|
5
6
|
*/
|
|
6
7
|
import { IMAGE_MIME } from '../shared/rpc.js'
|
|
@@ -43,3 +44,76 @@ export function isSvgPath(path: string): boolean {
|
|
|
43
44
|
export function dataUrlOf(base64: string, mime: string): string {
|
|
44
45
|
return 'data:' + (mime || 'application/octet-stream') + ';base64,' + base64
|
|
45
46
|
}
|
|
47
|
+
|
|
48
|
+
/** 图片缩放下限(25%,低于此档不再缩小)。 */
|
|
49
|
+
export const ZOOM_MIN = 0.25
|
|
50
|
+
|
|
51
|
+
/** 图片缩放上限(400%,高于此档不再放大)。 */
|
|
52
|
+
export const ZOOM_MAX = 4
|
|
53
|
+
|
|
54
|
+
/** 图片缩放步进(10%/档,−/+ 按钮每点一次的变化量)。 */
|
|
55
|
+
export const ZOOM_STEP = 0.1
|
|
56
|
+
|
|
57
|
+
/** 页签缩放记忆表:键 = zoomKeyOf(会话, 路径),值 = 显式缩放或 null(适应宽度)。 */
|
|
58
|
+
const zoomMem: Map<string, number | null> = new Map()
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 图片缩放收敛:clamp 到 25%–400% 并按整百分比取整(消除浮点步进噪声)。
|
|
62
|
+
* @author ddj 2026年09月22号
|
|
63
|
+
* @param zoom 目标缩放(非有限数值按 100% 处理)
|
|
64
|
+
* @returns 收敛后的缩放比例
|
|
65
|
+
*/
|
|
66
|
+
export function clampZoom(zoom: number): number {
|
|
67
|
+
const value = Number.isFinite(zoom) ? zoom : 1
|
|
68
|
+
return Math.round(Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, value)) * 100) / 100
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 图片缩放步进:按 10%/档变化并 clamp(dir 为负即缩小,其余按放大处理)。
|
|
73
|
+
* @author ddj 2026年09月22号
|
|
74
|
+
* @param base 起始缩放(适应宽度态由调用方实测当前渲染缩放传入)
|
|
75
|
+
* @param dir 方向:< 0 缩小,≥ 0 放大
|
|
76
|
+
* @returns 步进后的缩放比例
|
|
77
|
+
*/
|
|
78
|
+
export function zoomStepOf(base: number, dir: number): number {
|
|
79
|
+
return clampZoom(base + (dir < 0 ? -ZOOM_STEP : ZOOM_STEP))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 页签缩放记忆键(会话 + 路径;NUL 分隔,防止两段拼接撞键)。
|
|
84
|
+
* @author ddj 2026年09月22号
|
|
85
|
+
* @param sessionId 会话 id
|
|
86
|
+
* @param path 图片路径
|
|
87
|
+
* @returns 记忆键
|
|
88
|
+
*/
|
|
89
|
+
export function zoomKeyOf(sessionId: string | null | undefined, path: string | null | undefined): string {
|
|
90
|
+
return String(sessionId ?? '') + '\u0000' + String(path ?? '')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 写入页签缩放记忆(null = 适应宽度;会话级生命周期,不进 localStorage)。
|
|
95
|
+
* @author ddj 2026年09月22号
|
|
96
|
+
* @param key zoomKeyOf 产出的记忆键
|
|
97
|
+
* @param zoom 记忆缩放(null = 适应宽度)
|
|
98
|
+
*/
|
|
99
|
+
export function rememberZoom(key: string, zoom: number | null): void {
|
|
100
|
+
zoomMem.set(key, zoom)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 读取页签缩放记忆(无记忆返回 null,调用方按初始适应宽度处理)。
|
|
105
|
+
* @author ddj 2026年09月22号
|
|
106
|
+
* @param key zoomKeyOf 产出的记忆键
|
|
107
|
+
* @returns 记忆缩放或 null
|
|
108
|
+
*/
|
|
109
|
+
export function recallZoom(key: string): number | null {
|
|
110
|
+
return zoomMem.get(key) ?? null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 清空全部页签缩放记忆(EditorView 卸载时调用)。
|
|
115
|
+
* @author ddj 2026年09月22号
|
|
116
|
+
*/
|
|
117
|
+
export function clearZoomMem(): void {
|
|
118
|
+
zoomMem.clear()
|
|
119
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -29,9 +29,10 @@ import { installOpenPathRouter, vscodeOpener, autoValue } from './openPathRouter
|
|
|
29
29
|
import { patchRemoteOpen, probeRemoteOpen } from './remoteOpenRouter.js'
|
|
30
30
|
import { setupExtOpen } from './externalOpen.js'
|
|
31
31
|
import { SettingsContext } from './settingsContext.js'
|
|
32
|
-
import { SIDEBAR_PLUGIN,
|
|
32
|
+
import { SIDEBAR_PLUGIN, registerSlotSafely, settingsBridge } from './compat.js'
|
|
33
33
|
import { detectSidebarService, installSideEditor, setEnsureSideEditor, SIDEBAR_INSTALL_CMD } from './sidebarBridge.js'
|
|
34
|
-
import { detectOfficial, installOfficial, registerOfficialFileClaim, OFFICIAL_TAB_KIND, OFFICIAL_TAB_TITLE, isEditorTabActive, restoreEditorTab } from './officialSidebar.js'
|
|
34
|
+
import { detectOfficial, installOfficial, registerOfficialFileClaim, OFFICIAL_TAB_KIND, OFFICIAL_TAB_TITLE, isEditorTabActive, restoreEditorTab, buildFileAddress } from './officialSidebar.js'
|
|
35
|
+
import { setNativeCsv, setNativeOpenHandler, resetNativeOpen } from './nativeOpenStore.js'
|
|
35
36
|
import { SideEditorTab } from './ui/SideEditorTab.js'
|
|
36
37
|
import { OfficialSideTab } from './ui/OfficialSideTab.js'
|
|
37
38
|
import { createClaimRouter } from './ui/ClaimRouter.js'
|
|
@@ -64,7 +65,9 @@ import type { CompatAdapter } from '../shared/compat.js'
|
|
|
64
65
|
// ⚠️ inject 只列必需服务:webUiSettings 是 @linxin666/dsh-client-ui-web-ui-settings 提供的
|
|
65
66
|
// 可选兼容桥(非官方服务),列入 inject 会让未装该桥的部署启动时 entry 永久 pending,
|
|
66
67
|
// web boot 直接失败('1 entry did not activate')。兼容层用 ctx.get 运行时探测 + 降级,不靠 inject。
|
|
67
|
-
|
|
68
|
+
// 设置桥服务同理不进 inject(DSH 0.1.7 移除 settingsScope、新增 configForms,0.1.6 反之;
|
|
69
|
+
// 写死任一桥名都会在另一版本停等)——设置桥由 compat.pickSettingsBinder 运行时探测三级降级。
|
|
70
|
+
export const inject = ['slots', 'timer', 'locale', 'connection', 'remote', 'workspaces', 'sessions', 'conversation']
|
|
68
71
|
|
|
69
72
|
/** 指令桥装配幂等标记(同一 document 重复 apply 只装配一次,避免重复键位监听)。 */
|
|
70
73
|
let commandsMounted = false
|
|
@@ -155,8 +158,9 @@ export function apply(ctx: any): void {
|
|
|
155
158
|
return subscribeScope(ctx, () => schedule(check, 0))
|
|
156
159
|
}, 'vscode-mode: editor tab restore')
|
|
157
160
|
const originalOpenPath = workspaces?.openPath
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
// 设置桥:服务不进 inject(0.1.6 settingsScope / 0.1.7 configForms 桥名互斥,写死任一
|
|
162
|
+
// 都会在另一版本停等)→ compat.settingsBridge 探测 + 晚到 15×2s 有界重试,命中后重挂同步
|
|
163
|
+
const bridge = settingsBridge(ctx, { schedule, attempts: 15, intervalMs: 2000 })
|
|
160
164
|
let selected = autoValue('auto')
|
|
161
165
|
/** 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)是否已安装(compatSummary 展示用)。 */
|
|
162
166
|
let remoteOpenInstalled = false
|
|
@@ -175,7 +179,7 @@ export function apply(ctx: any): void {
|
|
|
175
179
|
|
|
176
180
|
/** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
|
|
177
181
|
const compatSummary = (): CompatAdapter[] => [
|
|
178
|
-
{ name: '设置桥', active:
|
|
182
|
+
{ name: '设置桥', active: bridge.scope() !== undefined, note: bridge.scope() !== undefined ? '使用 ' + bridge.service() + ' 桥' : '未绑定设置服务(fileOpenTool 持久化不可用,晚到重试窗口内自愈)' },
|
|
179
183
|
{ name: '文件打开路由(workspaces.openPath)', active: Boolean(workspaces?.openPath), note: 'vscode 打开器优先,失败回退系统打开(0.1.2 及更早 DSH 的对话文件链接主路径)' },
|
|
180
184
|
{ name: '会话文件链接路由(remote.session.openWorkspacePath)', active: remoteOpenInstalled, note: '0.1.3+ 对话文件引用/产物打开优先走插件打开器,失败回退系统打开' },
|
|
181
185
|
{ name: '侧边栏打开器(' + SIDEBAR_PLUGIN + ')', active: registry.get(SIDEBAR_PLUGIN) !== undefined, note: registry.get(SIDEBAR_PLUGIN) !== undefined ? '已注册(优先级 80)' : '未检测到侧边栏打开能力' },
|
|
@@ -241,35 +245,47 @@ export function apply(ctx: any): void {
|
|
|
241
245
|
claimDisposer = null
|
|
242
246
|
}
|
|
243
247
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
248
|
+
// 设置同步(fileOpenTool / 快捷键 / 侧宽 / 页签上限):桥可能晚于本插件 apply 就绪
|
|
249
|
+
// (设置桥服务不进 inject),首次命中即挂两条 effect;晚到经 bridge.whenReady 重挂一次
|
|
250
|
+
// (settingsBridge 15×2s 有界重试内命中才会触发,用尽后保持旧行为不轮询)。
|
|
251
|
+
let settingsSyncMounted = false
|
|
252
|
+
const mountSettingsSyncs = (): void => {
|
|
253
|
+
if (settingsSyncMounted) return
|
|
254
|
+
const scope = bridge.scope()
|
|
255
|
+
if (!scope) return
|
|
256
|
+
settingsSyncMounted = true
|
|
257
|
+
ctx.effect(() => {
|
|
258
|
+
const sync = (): void => {
|
|
259
|
+
const snapshot = scope.getSnapshot()
|
|
260
|
+
if (snapshot.status === 'loading') return
|
|
261
|
+
selected = autoValue(snapshot.value?.fileOpenTool)
|
|
262
|
+
syncFileClaim()
|
|
263
|
+
window.dispatchEvent(new CustomEvent('edrv:file-open-tool-change', { detail: { value: selected } }))
|
|
264
|
+
}
|
|
265
|
+
sync()
|
|
266
|
+
return scope.subscribe(sync)
|
|
267
|
+
}, 'vscode-mode: file opener setting sync')
|
|
268
|
+
// 快捷键配置同步:设置提交后立即刷新键位匹配(编辑器/QuickOpen 按事件时读取)
|
|
269
|
+
// 同一订阅里顺带同步侧边栏最小宽度(sidebarMinWidth)→ 派发 edrv:sidebar-min-width 通知编辑器重夹
|
|
270
|
+
// 以及页签上限(maxOpenEditors)→ 派发 edrv:max-open-editors 通知编辑器复算淘汰
|
|
271
|
+
ctx.effect(() => {
|
|
272
|
+
const sync = (): void => {
|
|
273
|
+
const snapshot = scope.getSnapshot()
|
|
274
|
+
if (snapshot.status === 'loading') return
|
|
275
|
+
keybindingsApply(snapshot.value?.keybindings)
|
|
276
|
+
const minW = sidebarMinApply(snapshot.value?.sidebarMinWidth)
|
|
277
|
+
window.dispatchEvent(new CustomEvent('edrv:sidebar-min-width', { detail: { value: minW } }))
|
|
278
|
+
const limit = editorLimitApply(snapshot.value?.maxOpenEditors)
|
|
279
|
+
window.dispatchEvent(new CustomEvent('edrv:max-open-editors', { detail: { value: limit } }))
|
|
280
|
+
// 原生打开范围同拍推送(nativeOpenStore 解析缓存;旧版无该字段 → 保持默认集)
|
|
281
|
+
setNativeCsv(snapshot.value?.nativeOpenExts)
|
|
282
|
+
}
|
|
283
|
+
sync()
|
|
284
|
+
return scope.subscribe(sync)
|
|
285
|
+
}, 'vscode-mode: keybindings setting sync')
|
|
286
|
+
}
|
|
287
|
+
mountSettingsSyncs()
|
|
288
|
+
if (!settingsSyncMounted) ctx.effect(() => bridge.whenReady(mountSettingsSyncs), 'vscode-mode: settings bridge wait')
|
|
273
289
|
if (workspaces?.openPath) {
|
|
274
290
|
ctx.effect(() => installOpenPathRouter({
|
|
275
291
|
workspaces,
|
|
@@ -371,6 +387,17 @@ export function apply(ctx: any): void {
|
|
|
371
387
|
// 官方打开器(下拉「官方侧边栏」选项,动态出现;openResource 能力缺失时跳过)+ 按当前设置同步 file 地址认领
|
|
372
388
|
if (typeof official.service.openResource === 'function') {
|
|
373
389
|
ctx.effect(() => registry.register(officialSidebarOpener(official.service)), 'vscode-mode: official sidebar opener')
|
|
390
|
+
// 原生打开 handler:文件树 openFile 命中 nativeOpenExts 时经 openResource 落官方渲染器
|
|
391
|
+
//(claim 的 canOpen 同步让位该范围,否则地址又被认领转发回本插件);形态撤下时解绑
|
|
392
|
+
ctx.effect(() => {
|
|
393
|
+
setNativeOpenHandler((path) => {
|
|
394
|
+
const scope = readSessionScope(ctx)
|
|
395
|
+
if (!scope.sessionId) return false
|
|
396
|
+
official.service.openResource?.(buildFileAddress(path, scope.sessionId))
|
|
397
|
+
return typeof official.service.openResource === 'function'
|
|
398
|
+
})
|
|
399
|
+
return () => setNativeOpenHandler(null)
|
|
400
|
+
}, 'vscode-mode: native open handler')
|
|
374
401
|
}
|
|
375
402
|
syncFileClaim()
|
|
376
403
|
}
|
|
@@ -449,7 +476,7 @@ export function apply(ctx: any): void {
|
|
|
449
476
|
id: 'vscode-mode',
|
|
450
477
|
order: 30,
|
|
451
478
|
label: 'VSCodeMode',
|
|
452
|
-
}, () => React.createElement(SettingsContext.Provider, { value:
|
|
479
|
+
}, () => React.createElement(SettingsContext.Provider, { value: bridge.scope() }, React.createElement(McpSettings, { openerRegistry: registry, compatSummary })))
|
|
453
480
|
|
|
454
481
|
// 卸载收尾(G4,DSH 0.1.6-alpha.2 起支持运行时卸载/重载):
|
|
455
482
|
// 注销挂在存活的 window.monaco 上的全部 Monaco provider 并复位模块状态。
|
|
@@ -459,5 +486,7 @@ export function apply(ctx: any): void {
|
|
|
459
486
|
try { disposeLaunchJson() } catch { /* 同上 */ }
|
|
460
487
|
try { disposeAiInline() } catch { /* 同上 */ }
|
|
461
488
|
try { disposeLsp() } catch { /* 同上 */ }
|
|
489
|
+
// 原生打开范围复位回默认集(handler 解绑已由其自身 ctx.effect disposer 承担)
|
|
490
|
+
try { resetNativeOpen() } catch { /* 同上 */ }
|
|
462
491
|
}, 'vscode-mode: monaco providers teardown')
|
|
463
492
|
}
|
|
@@ -171,6 +171,52 @@ export async function fetchHover(path, text, position) {
|
|
|
171
171
|
return res.hover || null
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
/**
|
|
175
|
+
* 查询补全列表(列表阶段不带 documentation,由 resolve 补全)。
|
|
176
|
+
* @author ddj 2026年09月22号
|
|
177
|
+
* @param path 工作区相对路径
|
|
178
|
+
* @param text 文档全文(未保存内容必须进服务器,否则按旧文本解析)
|
|
179
|
+
* @param position Monaco 位置(1-based)
|
|
180
|
+
* @param context LSP CompletionContext(触发类型/触发字符)
|
|
181
|
+
* @returns { items, incomplete };失败或空时 null
|
|
182
|
+
*/
|
|
183
|
+
export async function fetchCompletions(path, text, position, context) {
|
|
184
|
+
await syncDoc(path, text, true)
|
|
185
|
+
const args = { path, position: monoToLsp(position.lineNumber, position.column) }
|
|
186
|
+
if (context) args.context = context
|
|
187
|
+
const res = await queryLsp('查询补全', 'edrv.lsp.completion', args)
|
|
188
|
+
if (!res || !res.completions) return null
|
|
189
|
+
return res.completions
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 惰性补全单个候选项(completionItem/resolve)。
|
|
194
|
+
* @author ddj 2026年09月22号
|
|
195
|
+
* @param path 工作区相对路径
|
|
196
|
+
* @param item host 归一化的补全项(含 data)
|
|
197
|
+
* @returns 补全后的条目;失败时回落原条目
|
|
198
|
+
*/
|
|
199
|
+
export async function resolveCompletion(path, item) {
|
|
200
|
+
const res = await queryLsp('解析补全', 'edrv.lsp.resolveCompletion', { path, item })
|
|
201
|
+
if (!res || !res.item) return item
|
|
202
|
+
return res.item
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 查询签名帮助。
|
|
207
|
+
* @author ddj 2026年09月22号
|
|
208
|
+
* @param path 工作区相对路径
|
|
209
|
+
* @param text 文档全文
|
|
210
|
+
* @param position Monaco 位置(1-based)
|
|
211
|
+
* @returns 归一化签名帮助;无签名时 null
|
|
212
|
+
*/
|
|
213
|
+
export async function fetchSignatureHelp(path, text, position) {
|
|
214
|
+
await syncDoc(path, text, true)
|
|
215
|
+
const res = await queryLsp('查询签名', 'edrv.lsp.signatureHelp', { path, position: monoToLsp(position.lineNumber, position.column) })
|
|
216
|
+
if (!res) return null
|
|
217
|
+
return res.signatureHelp || null
|
|
218
|
+
}
|
|
219
|
+
|
|
174
220
|
/** 查询当前文档的 semantic tokens(host 已归一化 legend 与 delta data)。 */
|
|
175
221
|
export async function fetchSemanticTokens(path, text) {
|
|
176
222
|
await syncDoc(path, text, true)
|