dsh-vscode-mode 0.1.52 → 0.1.53
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 +71 -0
- package/assets/shell/dsh-open.cs +182 -0
- package/assets/shell/dsh-open.ps1 +52 -0
- package/assets/shell/dsh-open.sh +70 -0
- package/assets/shell/dsh-whale.ico +0 -0
- package/lib/client.js +718 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +1086 -5
- package/lib/index.js.map +1 -1
- package/package.json +2 -1
- package/src/client/chooseDialog.ts +70 -0
- package/src/client/externalOpen.ts +130 -0
- package/src/client/index.ts +4 -0
- package/src/client/openFlow.ts +330 -0
- package/src/client/ui/IntegrationSettings.ts +243 -0
- package/src/client/ui/McpSettings.ts +2 -0
- package/src/externalHandoff.ts +70 -0
- package/src/fileOpenSettings.ts +10 -2
- package/src/index.ts +3 -0
- package/src/integrate.ts +576 -0
- package/src/rpc.ts +90 -3
- package/src/shared/externalOpen.ts +62 -0
- package/src/shared/integration.ts +73 -0
- package/src/shared/rpc.ts +26 -3
- package/src/unityBridge.ts +234 -0
- package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +267 -0
- package/unity/com.dsh.editor/Editor/DshCodeEditor.cs.meta +11 -0
- package/unity/com.dsh.editor/Editor/com.dsh.editor.asmdef +15 -0
- package/unity/com.dsh.editor/Editor/com.dsh.editor.asmdef.meta +7 -0
- package/unity/com.dsh.editor/Editor.meta +8 -0
- package/unity/com.dsh.editor/README.md +33 -0
- package/unity/com.dsh.editor/README.md.meta +7 -0
- package/unity/com.dsh.editor/package.json +7 -0
- package/unity/com.dsh.editor/package.json.meta +7 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — 「系统集成」设置面板(跨平台)。
|
|
4
|
+
* 右键菜单:Windows=HKCU 三类键(reg)/ Linux=Nautilus 脚本 + Dolphin 服务菜单(文件写入)/
|
|
5
|
+
* macOS=Automator 快速操作(一键复制配方手动创建,自动检测/移除)。
|
|
6
|
+
* 深链基址来自设置 integrationBaseUrl(settings.set 持久化)。
|
|
7
|
+
* Unity 集成:登记项目根 + 一键安装/更新内嵌包到 <项目>/Packages/com.dsh.editor。
|
|
8
|
+
* 作者 ddj 2026-09-07
|
|
9
|
+
*/
|
|
10
|
+
import React from 'react'
|
|
11
|
+
import { rpc } from '../rpc.js'
|
|
12
|
+
import { SettingsContext } from '../settingsContext.js'
|
|
13
|
+
|
|
14
|
+
/** launcher 状态文案。 */
|
|
15
|
+
function launcherText(launcher) {
|
|
16
|
+
if (!launcher?.present) return '未安装'
|
|
17
|
+
return launcher.compiled ? '已编译 exe(无闪窗)' : '已安装'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 注册条目状态文案(跨平台 entries)。 */
|
|
21
|
+
function entriesText(entries) {
|
|
22
|
+
if (!entries?.length) return '本平台无自动注册入口'
|
|
23
|
+
return entries.map((entry) => entry.label + (entry.registered ? ' ✓' : ' ✗')).join(' · ')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** macOS Automator 快速操作配方(复制到剪贴板,用户按步骤创建)。 */
|
|
27
|
+
function automatorRecipe(launcherPath) {
|
|
28
|
+
return [
|
|
29
|
+
'macOS Finder 快速操作(Automator)配置步骤:',
|
|
30
|
+
'1. Automator → 新建 → 快速操作',
|
|
31
|
+
'2. 「工作流程接收当前」选:文件或文件夹;位于:任何应用程序',
|
|
32
|
+
'3. 添加动作「运行 Shell 脚本」,Shell 选 /bin/bash,传递输入选「作为参数」',
|
|
33
|
+
'4. 脚本内容替换为:',
|
|
34
|
+
' exec "' + launcherPath + '" "$@"',
|
|
35
|
+
'5. 保存为:在 DSH 文件编辑中打开',
|
|
36
|
+
'完成后在 Finder 右键 → 快速操作/服务 中出现「在 DSH 文件编辑中打开」。',
|
|
37
|
+
].join('\n')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 单个 Unity 项目行。 */
|
|
41
|
+
function ProjectRow({ project, busy, onInstall, onRemove }) {
|
|
42
|
+
const versionText = project.missingDir
|
|
43
|
+
? '目录不存在'
|
|
44
|
+
: project.error
|
|
45
|
+
? project.error
|
|
46
|
+
: project.installedVersion
|
|
47
|
+
? '已装 v' + project.installedVersion + (project.upToDate ? '(最新)' : '(可更新)')
|
|
48
|
+
: '未安装'
|
|
49
|
+
const installable = !project.missingDir && !project.error
|
|
50
|
+
return React.createElement('div', { className: 'vsm-general-row', key: project.path },
|
|
51
|
+
React.createElement('span', null,
|
|
52
|
+
project.title,
|
|
53
|
+
React.createElement('small', { className: 'vsm-devform-note' }, ' ' + project.path + ' · ' + versionText)),
|
|
54
|
+
React.createElement('div', { className: 'vsm-devform-actions' },
|
|
55
|
+
React.createElement('button', {
|
|
56
|
+
className: 'vsm-primary vsm-small',
|
|
57
|
+
disabled: !installable || busy === 'install:' + project.path,
|
|
58
|
+
title: project.upToDate ? '重新安装(整目录替换)' : '安装/更新到该 Unity 项目',
|
|
59
|
+
onClick: () => onInstall(project.path),
|
|
60
|
+
}, busy === 'install:' + project.path ? '安装中…' : project.installedVersion ? (project.upToDate ? '重装' : '更新') : '安装'),
|
|
61
|
+
React.createElement('button', { disabled: busy === 'remove:' + project.path, onClick: () => onRemove(project.path) }, '移除登记'),
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 平台提示条(注册语义差异说明)。 */
|
|
67
|
+
function PlatformNote({ platform, registered }) {
|
|
68
|
+
if (platform === 'win32') return null
|
|
69
|
+
const text = platform === 'linux'
|
|
70
|
+
? 'Linux 注册覆盖 GNOME Files(Nautilus)与 KDE Dolphin 两个主流文件管理器(文件写入即注册,无需管理员);其他文件管理器暂未支持。'
|
|
71
|
+
: 'macOS:点「复制 Automator 配方」按提示创建快速操作(约 1 分钟),创建后此处自动显示 ✓;「移除注册」仅删除引用本插件 launcher 的快速操作。'
|
|
72
|
+
return React.createElement('small', { className: 'vsm-devform-msg' }, text)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 系统集成面板(挂在 VSCodeMode 设置「通用」页底部)。
|
|
77
|
+
* @author ddj 2026年09月07号
|
|
78
|
+
*/
|
|
79
|
+
export function IntegrationSettings() {
|
|
80
|
+
const settings = React.useContext(SettingsContext)
|
|
81
|
+
const [status, setStatus] = React.useState(null)
|
|
82
|
+
const [statusError, setStatusError] = React.useState('')
|
|
83
|
+
const [unity, setUnity] = React.useState(null)
|
|
84
|
+
const [error, setError] = React.useState('')
|
|
85
|
+
const [message, setMessage] = React.useState('')
|
|
86
|
+
const [busy, setBusy] = React.useState('')
|
|
87
|
+
const [baseUrl, setBaseUrl] = React.useState('')
|
|
88
|
+
const [baseDraft, setBaseDraft] = React.useState(null)
|
|
89
|
+
const [rootDraft, setRootDraft] = React.useState('')
|
|
90
|
+
|
|
91
|
+
React.useEffect(() => {
|
|
92
|
+
let alive = true
|
|
93
|
+
Promise.all([rpc('edrv.integration.status', {}), rpc('edrv.unity.list', {})]).then(([st, ul]) => {
|
|
94
|
+
if (!alive) return
|
|
95
|
+
if (st.ok) { setStatus(st); setBaseUrl(st.baseUrl); setStatusError('') } else setStatusError(String(st.error))
|
|
96
|
+
if (ul.ok) setUnity(ul)
|
|
97
|
+
}).catch((e) => { if (alive) setError(String(e)) })
|
|
98
|
+
return () => { alive = false }
|
|
99
|
+
}, [])
|
|
100
|
+
|
|
101
|
+
const platform = status?.platform ?? ''
|
|
102
|
+
const onWin = platform === 'win32'
|
|
103
|
+
const onLinux = platform === 'linux'
|
|
104
|
+
const onMac = platform === 'darwin'
|
|
105
|
+
const anyRegistered = (status?.entries ?? []).some((entry) => entry.registered && !entry.manual)
|
|
106
|
+
|
|
107
|
+
const refreshUnity = () => rpc('edrv.unity.list', {}).then((result) => { if (result.ok) setUnity(result) })
|
|
108
|
+
|
|
109
|
+
/** 注册/移除右键菜单(Windows/Linux;macOS 注册按钮替换为配方复制)。 */
|
|
110
|
+
const runMenu = (key, method, done) => {
|
|
111
|
+
setBusy(key); setError(''); setMessage('')
|
|
112
|
+
rpc(method, {}).then((result) => {
|
|
113
|
+
if (!result.ok) throw new Error(result.error)
|
|
114
|
+
setStatus(result)
|
|
115
|
+
setMessage(done)
|
|
116
|
+
}).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(''))
|
|
117
|
+
}
|
|
118
|
+
const register = () => runMenu('register', 'edrv.integration.register',
|
|
119
|
+
onLinux ? '已注册:在 Nautilus / Dolphin 中右键文件或文件夹即可看到「在 DSH 文件编辑中打开」(首次出现可能需重启文件管理器)。' : '右键菜单已注册:右键文件/文件夹 →「在 DSH 文件编辑中打开」。')
|
|
120
|
+
const unregister = () => runMenu('unregister', 'edrv.integration.unregister', '右键菜单注册已移除。')
|
|
121
|
+
|
|
122
|
+
/** 复制 macOS Automator 配方(含 launcher 实际路径)。 */
|
|
123
|
+
const copyRecipe = () => {
|
|
124
|
+
const text = automatorRecipe(status?.launcher?.path ?? '$HOME/.dsh/dsh-vscode-mode/shell/dsh-open.sh')
|
|
125
|
+
navigator.clipboard?.writeText(text)
|
|
126
|
+
.then(() => setMessage('配方已复制,粘贴到备忘录按步骤操作即可。'))
|
|
127
|
+
.catch(() => setMessage(text))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** 保存深链基址(settings.set 持久化;已注册时提示重新注册刷新 launcher ini)。 */
|
|
131
|
+
const saveBase = () => {
|
|
132
|
+
const next = String(baseDraft ?? '').trim()
|
|
133
|
+
if (!next) return
|
|
134
|
+
if (!settings?.set) { setError('设置服务不可用'); return }
|
|
135
|
+
setBusy('base'); setError(''); setMessage('')
|
|
136
|
+
settings.set('integrationBaseUrl', next)
|
|
137
|
+
.then(() => { setBaseUrl(next); setMessage('深链基址已保存;如已注册右键菜单,请点「注册」刷新 launcher 配置。') })
|
|
138
|
+
.catch((e) => setError(String(e)))
|
|
139
|
+
.finally(() => setBusy(''))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 登记 Unity 项目(校验 Assets/ProjectSettings)。 */
|
|
143
|
+
const addProject = () => {
|
|
144
|
+
setBusy('add'); setError(''); setMessage('')
|
|
145
|
+
rpc('edrv.unity.add', { path: rootDraft.trim() }).then((result) => {
|
|
146
|
+
if (!result.ok) throw new Error(result.error)
|
|
147
|
+
setRootDraft('')
|
|
148
|
+
setMessage('项目已登记,点「安装」把包复制进项目 Packages/ 目录。')
|
|
149
|
+
return refreshUnity()
|
|
150
|
+
}).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(''))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 一键安装/更新(整目录替换内嵌包)。 */
|
|
154
|
+
const installProject = (path) => {
|
|
155
|
+
setBusy('install:' + path); setError(''); setMessage('')
|
|
156
|
+
rpc('edrv.unity.install', { path }).then((result) => {
|
|
157
|
+
if (!result.ok) throw new Error(result.error)
|
|
158
|
+
const project = result.project
|
|
159
|
+
setMessage('已安装/更新:' + project.title + '(v' + project.installedVersion + ')。Unity 已打开时切回窗口即生效。')
|
|
160
|
+
return refreshUnity()
|
|
161
|
+
}).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(''))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 移除登记(不删除已安装的包目录)。 */
|
|
165
|
+
const removeProject = (path) => {
|
|
166
|
+
if (!window.confirm('移除该项目的登记?(不删除已安装的包目录)')) return
|
|
167
|
+
setBusy('remove:' + path); setError(''); setMessage('')
|
|
168
|
+
rpc('edrv.unity.remove', { path })
|
|
169
|
+
.then(refreshUnity)
|
|
170
|
+
.catch((e) => setError(String(e)))
|
|
171
|
+
.finally(() => setBusy(''))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 复制包源路径(手动 Add package from disk 兜底)。 */
|
|
175
|
+
const copySource = () => {
|
|
176
|
+
const text = unity?.sourcePath ?? ''
|
|
177
|
+
if (!text) return
|
|
178
|
+
navigator.clipboard?.writeText(text)
|
|
179
|
+
.then(() => setMessage('已复制包源路径:' + text))
|
|
180
|
+
.catch(() => setMessage('包源路径:' + text))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return React.createElement('section', { className: 'vsm-panel' },
|
|
184
|
+
React.createElement('h3', { className: 'vsm-panel-title' }, '系统集成'),
|
|
185
|
+
React.createElement('div', { className: 'vsm-panel-body' },
|
|
186
|
+
React.createElement('p', { style: { margin: '0 0 8px' } },
|
|
187
|
+
'把 DSH 编辑器接入系统入口:文件管理器右键菜单(Windows/Linux/macOS)+ Unity 外部脚本编辑器(UPM 内嵌包,自动发现,无需改 manifest.json)。'),
|
|
188
|
+
error && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error),
|
|
189
|
+
message && React.createElement('small', { className: 'vsm-devform-msg' }, message),
|
|
190
|
+
status && !onWin && React.createElement(PlatformNote, { platform, registered: anyRegistered }),
|
|
191
|
+
React.createElement('label', { className: 'vsm-general-row' },
|
|
192
|
+
React.createElement('span', null, 'DSH 服务地址'),
|
|
193
|
+
React.createElement('input', {
|
|
194
|
+
value: baseDraft ?? baseUrl,
|
|
195
|
+
placeholder: 'http://127.0.0.1:3080',
|
|
196
|
+
onChange: (event) => setBaseDraft(event.target.value),
|
|
197
|
+
onKeyDown: (event) => { if (event.key === 'Enter') saveBase() },
|
|
198
|
+
}),
|
|
199
|
+
React.createElement('button', { disabled: busy === 'base' || baseDraft === null, onClick: saveBase }, '保存'),
|
|
200
|
+
),
|
|
201
|
+
React.createElement('label', { className: 'vsm-general-row' },
|
|
202
|
+
React.createElement('span', null,
|
|
203
|
+
'右键菜单 ',
|
|
204
|
+
React.createElement('small', { className: 'vsm-devform-note' },
|
|
205
|
+
status
|
|
206
|
+
? entriesText(status.entries) + ' · launcher:' + launcherText(status.launcher)
|
|
207
|
+
: statusError ? '状态获取失败:' + statusError : '读取中…')),
|
|
208
|
+
React.createElement('div', { className: 'vsm-devform-actions' },
|
|
209
|
+
(onWin || onLinux) && React.createElement('button', { className: 'vsm-primary vsm-small', disabled: busy === 'register', onClick: register }, busy === 'register' ? '注册中…' : '注册'),
|
|
210
|
+
onMac && React.createElement('button', { className: 'vsm-primary vsm-small', onClick: copyRecipe }, '复制 Automator 配方'),
|
|
211
|
+
status && React.createElement('button', { disabled: busy === 'unregister' || (!anyRegistered && !onMac), onClick: unregister }, '移除注册'),
|
|
212
|
+
),
|
|
213
|
+
),
|
|
214
|
+
React.createElement('div', { className: 'vsm-general-row', style: { alignItems: 'flex-start' } },
|
|
215
|
+
React.createElement('span', null,
|
|
216
|
+
'Unity 一键安装 ',
|
|
217
|
+
React.createElement('small', { className: 'vsm-devform-note' },
|
|
218
|
+
unity ? '包 v' + (unity.sourceVersion ?? '?') + ' · ' + unity.sourcePath : '读取中…')),
|
|
219
|
+
React.createElement('div', { className: 'vsm-devform-actions' },
|
|
220
|
+
React.createElement('button', { onClick: copySource }, '复制包源路径'),
|
|
221
|
+
),
|
|
222
|
+
),
|
|
223
|
+
(unity?.projects ?? []).map((project) => React.createElement(ProjectRow, {
|
|
224
|
+
key: project.path,
|
|
225
|
+
project,
|
|
226
|
+
busy,
|
|
227
|
+
onInstall: installProject,
|
|
228
|
+
onRemove: removeProject,
|
|
229
|
+
})),
|
|
230
|
+
React.createElement('label', { className: 'vsm-general-row' },
|
|
231
|
+
React.createElement('span', null, '添加 Unity 项目'),
|
|
232
|
+
React.createElement('input', {
|
|
233
|
+
value: rootDraft,
|
|
234
|
+
placeholder: '项目根目录(含 Assets 与 ProjectSettings)',
|
|
235
|
+
onChange: (event) => setRootDraft(event.target.value),
|
|
236
|
+
onKeyDown: (event) => { if (event.key === 'Enter') addProject() },
|
|
237
|
+
}),
|
|
238
|
+
React.createElement('button', { className: 'vsm-primary vsm-small', disabled: busy === 'add' || !rootDraft.trim(), onClick: addProject }, busy === 'add' ? '校验中…' : '添加'),
|
|
239
|
+
),
|
|
240
|
+
React.createElement('small', null, '安装 = 复制到 <项目>/Packages/com.dsh.editor(内嵌包);更新 = 整目录替换;卸载 = 删除该目录。Unity 打开时切回窗口自动刷新生效。插件卸载/重载时会自动清理并恢复右键菜单注册(「移除注册」后不再恢复)。'),
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
}
|
|
@@ -14,6 +14,7 @@ import { normalizeSidebarMinWidth, SIDEBAR_MIN_DEFAULT } from '../sidebarMin.js'
|
|
|
14
14
|
import { KeybindingsPanel } from './KeybindingsPanel.js'
|
|
15
15
|
import { LspSettings } from './LspSettings.js'
|
|
16
16
|
import { PerfSettings } from './PerfSettings.js'
|
|
17
|
+
import { IntegrationSettings } from './IntegrationSettings.js'
|
|
17
18
|
|
|
18
19
|
const EMPTY = { serverName: '', transport: 'stdio', command: '', args: '', cwd: '', url: '', headers: '' }
|
|
19
20
|
|
|
@@ -219,6 +220,7 @@ function GeneralSettings({ registry }) {
|
|
|
219
220
|
React.createElement('small', null, '180–560 px;拖拽低于该宽度自动隐藏')),
|
|
220
221
|
),
|
|
221
222
|
),
|
|
223
|
+
React.createElement(IntegrationSettings, null),
|
|
222
224
|
)
|
|
223
225
|
}
|
|
224
226
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — 深链移交(避免重复打开 DSH 页面)。
|
|
3
|
+
* launcher 投递待打开请求(edrv.external.handoff)→ 已打开页面 3s 轮询领取(edrv.external.pending)
|
|
4
|
+
* 并就地执行打开规则;15s 内无页面轮询过 = 无活跃页面 → launcher 回退打开新页。
|
|
5
|
+
* token 防多实例串扰;TTL 兜底防陈旧悬挂。模块级状态(host 单进程单例)。
|
|
6
|
+
* 作者 ddj 2026-09-08
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'node:crypto'
|
|
9
|
+
|
|
10
|
+
/** 一次外部打开请求(launcher 投递)。 */
|
|
11
|
+
export interface ExternalOpenRequest {
|
|
12
|
+
paths: string[]
|
|
13
|
+
line?: number
|
|
14
|
+
column?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 待打开 TTL:超时未领取即丢弃(launcher 回退已自行处理)。 */
|
|
18
|
+
const PENDING_TTL_MS = 60_000
|
|
19
|
+
/** 活跃页面判定窗口:窗口内有过 pending 轮询 = 有页面在。 */
|
|
20
|
+
const PRESENCE_WINDOW_MS = 15_000
|
|
21
|
+
|
|
22
|
+
let pending: (ExternalOpenRequest & { token: string; at: number }) | null = null
|
|
23
|
+
let lastPollAt = 0
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 投递一次外部打开请求;返回活跃页面数(0 = launcher 应回退开新页)与移交 token。
|
|
27
|
+
* @author ddj 2026年09月08号
|
|
28
|
+
* @param req 打开请求
|
|
29
|
+
* @returns 活跃页面数与 token
|
|
30
|
+
*/
|
|
31
|
+
export function handoffOpen(req: ExternalOpenRequest): { clients: number; token: string } {
|
|
32
|
+
const token = randomUUID()
|
|
33
|
+
pending = { ...req, token, at: Date.now() }
|
|
34
|
+
return { token, clients: clientsAlive() ? 1 : 0 }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 页面轮询:刷新活跃态并领取待打开请求(取走即清,多页面仅一方执行)。
|
|
39
|
+
* @author ddj 2026年09月08号
|
|
40
|
+
* @returns 待打开请求或 null
|
|
41
|
+
*/
|
|
42
|
+
export function pollPending(): ExternalOpenRequest | null {
|
|
43
|
+
lastPollAt = Date.now()
|
|
44
|
+
if (!pending) return null
|
|
45
|
+
if (Date.now() - pending.at > PENDING_TTL_MS) {
|
|
46
|
+
pending = null
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
const taken: ExternalOpenRequest = { paths: pending.paths, line: pending.line, column: pending.column }
|
|
50
|
+
pending = null
|
|
51
|
+
return taken
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 查询移交是否已被页面领取(take=true 时未领取则取走清除,供 launcher 回退开新页)。
|
|
56
|
+
* @author ddj 2026年09月08号
|
|
57
|
+
* @param token 移交 token
|
|
58
|
+
* @param take 未领取时是否取走清除
|
|
59
|
+
* @returns delivered=true = 已有页面领取(或已被更新的请求取代)
|
|
60
|
+
*/
|
|
61
|
+
export function pendingState(token: string, take: boolean): { delivered: boolean } {
|
|
62
|
+
const delivered = pending === null || pending.token !== token
|
|
63
|
+
if (take && !delivered) pending = null
|
|
64
|
+
return { delivered }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 活跃页面判定。 */
|
|
68
|
+
function clientsAlive(): boolean {
|
|
69
|
+
return Date.now() - lastPollAt <= PRESENCE_WINDOW_MS
|
|
70
|
+
}
|
package/src/fileOpenSettings.ts
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Ctx } from './store.js'
|
|
11
11
|
import { KEYBINDING_DEFAULTS } from './shared/keybindings.js'
|
|
12
|
+
import { INTEGRATION_BASE_DEFAULT } from './shared/integration.js'
|
|
12
13
|
|
|
13
14
|
export const FILE_OPEN_SETTINGS_NS = 'dsh-vscode-mode'
|
|
14
15
|
export const FILE_OPEN_DEFAULT = 'auto'
|
|
15
|
-
export interface FileOpenSettings { fileOpenTool: string }
|
|
16
|
+
export interface FileOpenSettings { fileOpenTool: string; integrationBaseUrl: string }
|
|
16
17
|
export interface FileOpenSettingsState { value: string; revision?: number; update: (value: string, expectedRevision?: number) => Promise<void> }
|
|
17
18
|
|
|
18
19
|
type SettingsProvider = {
|
|
@@ -182,6 +183,12 @@ function configValue(config: unknown): string {
|
|
|
182
183
|
return normalizeValue((config as { fileOpenTool?: unknown } | undefined)?.fileOpenTool)
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
/** 配置/设置里的深链基址(缺省/非法回退默认值)。 */
|
|
187
|
+
function baseValueOf(config: unknown): string {
|
|
188
|
+
const raw = (config as { integrationBaseUrl?: unknown } | undefined)?.integrationBaseUrl
|
|
189
|
+
return typeof raw === 'string' && raw.trim() ? raw.trim() : INTEGRATION_BASE_DEFAULT
|
|
190
|
+
}
|
|
191
|
+
|
|
185
192
|
/**
|
|
186
193
|
* 安装设置 section(版本自适应;任一策略不可用返回 false 不抛错)。
|
|
187
194
|
* @author ddj 2026年08月24号 / 2026年09月02号
|
|
@@ -210,6 +217,7 @@ export async function installOpenSettingsSection(
|
|
|
210
217
|
fileOpenTool: deps.z.string().default(FILE_OPEN_DEFAULT),
|
|
211
218
|
keybindings: deps.z.object(keybindingsShape(deps.z)).default({ ...KEYBINDING_DEFAULTS }),
|
|
212
219
|
sidebarMinWidth: deps.z.number().default(300),
|
|
220
|
+
integrationBaseUrl: deps.z.string().default(INTEGRATION_BASE_DEFAULT),
|
|
213
221
|
})
|
|
214
222
|
const strategy = await runSettingsInstall(ctx, ns, schema, entry, {
|
|
215
223
|
setSource: (source) => hooks.setSource(source as () => FileOpenSettings),
|
|
@@ -238,7 +246,7 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
238
246
|
const setSource = (source: () => FileOpenSettings): void => notify(source().fileOpenTool)
|
|
239
247
|
const settingsChange = (): void => syncRevision()
|
|
240
248
|
|
|
241
|
-
void installOpenSettingsSection(ctx, FILE_OPEN_SETTINGS_NS, { fileOpenTool: current }, { setSource, onChange: settingsChange })
|
|
249
|
+
void installOpenSettingsSection(ctx, FILE_OPEN_SETTINGS_NS, { fileOpenTool: current, integrationBaseUrl: baseValueOf(config) }, { setSource, onChange: settingsChange })
|
|
242
250
|
ctx.inject?.(['settings'], (settingsCtx: Ctx) => {
|
|
243
251
|
provider = settingsCtx.get('settings')
|
|
244
252
|
syncRevision()
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { installIsolation } from './mcpIsolation.js'
|
|
|
15
15
|
import { dropFileIndex } from './workspace.js'
|
|
16
16
|
import { cwdOf } from './registry.js'
|
|
17
17
|
import { setupOpenSettings } from './fileOpenSettings.js'
|
|
18
|
+
import { shellMenuLifecycle } from './integrate.js'
|
|
18
19
|
import { disposeIndex } from './treeIndex.js'
|
|
19
20
|
import { sweepTreeCache } from './paths.js'
|
|
20
21
|
import { PLUGIN_NAME, buildReport } from './compat.js'
|
|
@@ -71,6 +72,8 @@ export function apply(ctx: Ctx, config?: unknown): void {
|
|
|
71
72
|
|
|
72
73
|
registerRoutes(ctx, config, (method, args) => handleRpc(ctx, registry, method, args, searcher, contentSearcher, lspHandlers), (warning) => warnings.push(warning))
|
|
73
74
|
installIsolation(ctx)
|
|
75
|
+
// 系统集成生命周期:启动自动恢复右键菜单注册(marker 存在时);插件卸载/reload 清理注册痕迹
|
|
76
|
+
ctx.effect(() => shellMenuLifecycle(ctx))
|
|
74
77
|
// 启动清理缓存目录:非当前 schema / 超保留期 / 未知残留(best-effort,不阻塞装配)
|
|
75
78
|
void sweepTreeCache()
|
|
76
79
|
void logCompatSummary(ctx, warnings)
|