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/src/fileOpenSettings.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* dsh-vscode-mode host — 文件链接打开工具 + 快捷键的持久化设置。
|
|
3
3
|
* 依赖守卫:schemastery 动态加载;@deepseek-ai/dsh-settings 仅作 legacy 探测——
|
|
4
4
|
* rc 线导出 installSettingsSection free function,0.1.2-alpha 起移除(改由 settings
|
|
5
|
-
* 服务的 installSection
|
|
5
|
+
* 服务的 installSection 方法承载),0.1.7 再移除 installSection(设置并入 profile
|
|
6
|
+
* 插件 Config schema,见 runSettingsInstall 四策略:legacy/service/forms/none)。
|
|
6
7
|
* 缺失/任一策略失败时插件仍可加载(fileOpenTool 降级为配置值,compat 报告可见),
|
|
7
8
|
* 全程 try/catch,不产生未捕获 rejection。
|
|
8
|
-
* 作者 ddj 2026年08月24号 / 2026年08月26号 / 2026年09月02号
|
|
9
|
+
* 作者 ddj 2026年08月24号 / 2026年08月26号 / 2026年09月02号 / 2026年09月22号
|
|
9
10
|
*/
|
|
10
11
|
import { createRequire } from 'node:module'
|
|
11
12
|
import { pathToFileURL } from 'node:url'
|
|
@@ -15,6 +16,7 @@ import { EDITOR_LIMIT_DEFAULT } from './shared/editorLimit.js'
|
|
|
15
16
|
import { INTEGRATION_BASE_DEFAULT } from './shared/integration.js'
|
|
16
17
|
import { TORTOISE_DIR_DEFAULT } from './shared/svn.js'
|
|
17
18
|
import type { AiConfigPatch, AiConfigView } from './shared/ai.js'
|
|
19
|
+
import { DEFAULT_NATIVE_CSV } from './shared/nativeOpen.js'
|
|
18
20
|
import { log } from './log.js'
|
|
19
21
|
|
|
20
22
|
export const FILE_OPEN_SETTINGS_NS = 'dsh-vscode-mode'
|
|
@@ -170,11 +172,12 @@ export function loadSettingsDeps(importFn: (specifier: string) => Promise<unknow
|
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
/** 设置 section 安装策略(版本适配机制的观测值)。 */
|
|
173
|
-
export type SettingsInstallStrategy = 'legacy' | 'service' | 'none' | 'unknown'
|
|
175
|
+
export type SettingsInstallStrategy = 'legacy' | 'service' | 'forms' | 'none' | 'unknown'
|
|
174
176
|
|
|
175
177
|
const INSTALL_UNMOUNTED = '设置 section 尚未装配'
|
|
176
178
|
const INSTALL_LEGACY = 'rc 线:dsh-settings.installSettingsSection'
|
|
177
179
|
const INSTALL_SERVICE = '0.1.2-alpha 线:settings 服务 installSection'
|
|
180
|
+
const INSTALL_FORMS = '0.1.7 线:设置并入插件 Config schema(SettingsForms)'
|
|
178
181
|
const INSTALL_NONE = '两路均不可用:设置持久化降级为配置值'
|
|
179
182
|
|
|
180
183
|
interface ObservedInstall { strategy: SettingsInstallStrategy; note: string }
|
|
@@ -201,15 +204,54 @@ export function resetSettingsInstallObserved(): void {
|
|
|
201
204
|
observedInstall = { strategy: 'unknown', note: INSTALL_UNMOUNTED }
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
/**
|
|
208
|
+
* 判定设置写冲突错误(0.1.7 SettingsConflictError:code 常量 / 构造器名双通道)。
|
|
209
|
+
* @author ddj 2026年09月22号
|
|
210
|
+
* @param error 捕获到的错误
|
|
211
|
+
* @returns 是否为 revision 冲突
|
|
212
|
+
*/
|
|
213
|
+
function isConflictError(error: unknown): boolean {
|
|
214
|
+
if (!error || typeof error !== 'object') return false
|
|
215
|
+
const e = error as { code?: unknown; name?: unknown }
|
|
216
|
+
return e.code === 'SETTINGS_CONFLICT' || e.name === 'SettingsConflictError'
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 0.1.7 forms 策略:订阅 settings/document-updated 事件,目标 namespace 变化时
|
|
221
|
+
* 把 describe 最新值推给 hooks(setSource + onChange),替代旧 installSection 的
|
|
222
|
+
* 变更回调;disposer 经 ctx.effect 随插件 fiber 卸载(热重载不泄漏)。
|
|
223
|
+
* @author ddj 2026年09月22号
|
|
224
|
+
* @param ctx DSH host 上下文
|
|
225
|
+
* @param provider settings 服务(SettingsForms)
|
|
226
|
+
* @param ns 目标设置命名空间
|
|
227
|
+
* @param hooks 设置源绑定与变更回调
|
|
228
|
+
*/
|
|
229
|
+
function watchDocUpdates(ctx: Ctx, provider: SettingsProvider, ns: string, hooks: { setSource: (source: () => unknown) => void; onChange: () => void }): void {
|
|
230
|
+
try {
|
|
231
|
+
const bus = ctx as { on?: (name: string, handler: (updatedNs: unknown, revision: unknown) => void) => unknown; effect?: (fn: () => unknown) => unknown }
|
|
232
|
+
if (typeof bus.on !== 'function') return
|
|
233
|
+
const off = bus.on('settings/document-updated', (updatedNs: unknown) => {
|
|
234
|
+
if (updatedNs !== ns) return
|
|
235
|
+
hooks.setSource(() => readSectionValue(provider, ns))
|
|
236
|
+
hooks.onChange()
|
|
237
|
+
})
|
|
238
|
+
if (typeof bus.effect === 'function' && typeof off === 'function') bus.effect(() => off)
|
|
239
|
+
} catch (error) {
|
|
240
|
+
log.warn('settings/document-updated 订阅失败:' + String(error))
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
204
244
|
/**
|
|
205
245
|
* 设置 section 版本自适应安装(核心策略分派)。
|
|
206
246
|
* legacy:dsh-settings 仍导出 installSettingsSection(rc 线)→ 原样调用,行为与旧版一致。
|
|
207
247
|
* service:该导出已移除(0.1.2-alpha 起)→ 经 ctx.inject(['settings']) 走服务方法
|
|
208
248
|
* provider.installSection(owner, ns, schema, entry, hooks)(等义封装,含
|
|
209
|
-
* base 层与 fiber
|
|
249
|
+
* base 层与 fiber 卸载回退)。
|
|
250
|
+
* forms:0.1.7 起 installSection 移除,设置并入 profile 插件 Config(SettingsForms
|
|
251
|
+
* 的 describe+update 在场即成立)→ 不装 section,改订阅 document-updated。
|
|
210
252
|
* none:两条路由都不存在 → 仅记录并告警,调用方按配置值运行。
|
|
211
253
|
* 全程不抛错、不产生未捕获 rejection。
|
|
212
|
-
* @author ddj 2026年09月02号
|
|
254
|
+
* @author ddj 2026年09月02号(2026年09月22号 增补 forms 策略)
|
|
213
255
|
* @param ctx DSH host 上下文(inject 可选探测)
|
|
214
256
|
* @param ns 设置命名空间
|
|
215
257
|
* @param schema schemastery schema
|
|
@@ -253,6 +295,13 @@ export async function runSettingsInstall(
|
|
|
253
295
|
const provider = typeof sc.get === 'function' ? sc.get('settings') : sc.settings
|
|
254
296
|
const install = (provider as { installSection?: unknown } | undefined)?.installSection
|
|
255
297
|
if (typeof install !== 'function') {
|
|
298
|
+
// 0.1.7:installSection 已移除;SettingsForms(describe+update)在场走 forms 策略
|
|
299
|
+
const forms = provider as SettingsProvider | undefined
|
|
300
|
+
if (typeof forms?.describe === 'function' && typeof forms?.update === 'function') {
|
|
301
|
+
recordInstall('forms', INSTALL_FORMS)
|
|
302
|
+
watchDocUpdates(ctx, forms, ns, hooks)
|
|
303
|
+
return
|
|
304
|
+
}
|
|
256
305
|
log.warn('settings 服务无 installSection(DSH 版本 API 变化),section ' + ns + ' 降级为配置值')
|
|
257
306
|
recordInstall('none', INSTALL_NONE)
|
|
258
307
|
return
|
|
@@ -286,6 +335,101 @@ function keybindingsShape(z: SettingsDeps['z']): Record<string, unknown> {
|
|
|
286
335
|
return shape
|
|
287
336
|
}
|
|
288
337
|
|
|
338
|
+
/**
|
|
339
|
+
* 设置节 describe 项的形状校验:value 须为普通对象。
|
|
340
|
+
* ns 在两代语义不同(旧=自装 section 名,0.1.7=profile entry id),同名撞车时
|
|
341
|
+
* 以形状兜底防误命中非设置数据;0.1.7 下本插件 entry 的 Config 值与旧 section
|
|
342
|
+
* 值同为设置键对象,两形状天然兼容。
|
|
343
|
+
* @author ddj 2026年09月22号
|
|
344
|
+
* @param value describe 项的 value 字段
|
|
345
|
+
* @returns 是否具备设置节形状
|
|
346
|
+
*/
|
|
347
|
+
function isSectionShaped(value: unknown): boolean {
|
|
348
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** describe 项最小形状(读写工具共用)。 */
|
|
352
|
+
export interface SectionDescriptor { ns?: string; value?: unknown; revision?: number }
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 按 ns + 形状校验查找设置节(host 读路径统一入口)。
|
|
356
|
+
* @author ddj 2026年09月22号
|
|
357
|
+
* @param provider settings 服务(可空)
|
|
358
|
+
* @param ns 设置命名空间
|
|
359
|
+
* @returns 命中的 describe 项;未命中返回 undefined
|
|
360
|
+
*/
|
|
361
|
+
export function sectionOf(provider: SettingsProvider | undefined, ns: string): SectionDescriptor | undefined {
|
|
362
|
+
const items = provider?.describe?.({ redactSecrets: true })
|
|
363
|
+
if (!items) return undefined
|
|
364
|
+
return items.find((item) => item.ns === ns && isSectionShaped(item.value))
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** 读设置节存储值(未就绪/形状不符返回 undefined)。 */
|
|
368
|
+
function readSectionValue(provider: SettingsProvider | undefined, ns: string): unknown {
|
|
369
|
+
return sectionOf(provider, ns)?.value
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** 设置写入结果(ok=false 时带原因;conflict=true 表示经冲突重试后成功)。 */
|
|
373
|
+
export interface SectionWriteResult { ok: boolean; conflict?: boolean; error?: string }
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 带冲突自愈的设置写入:0.1.7 SettingsConflictError(revision 拒写)时重读
|
|
377
|
+
* revision 重试一次;其余错误与服务缺失一律结构化返回,绝不抛未捕获异常。
|
|
378
|
+
* @author ddj 2026年09月22号
|
|
379
|
+
* @param provider settings 服务(可空)
|
|
380
|
+
* @param ns 设置命名空间
|
|
381
|
+
* @param patch 写入字段
|
|
382
|
+
* @param expectedRevision 读侧携带的 revision(缺省用 describe 最新值)
|
|
383
|
+
* @returns 写入结果
|
|
384
|
+
*/
|
|
385
|
+
export async function updateSection(provider: SettingsProvider | undefined, ns: string, patch: object, expectedRevision?: number): Promise<SectionWriteResult> {
|
|
386
|
+
if (!provider?.update) return { ok: false, error: 'settings 服务无 update' }
|
|
387
|
+
const first = expectedRevision ?? sectionOf(provider, ns)?.revision
|
|
388
|
+
try {
|
|
389
|
+
await provider.update(ns, patch, first)
|
|
390
|
+
return { ok: true }
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (!isConflictError(error)) return { ok: false, error: String(error) }
|
|
393
|
+
}
|
|
394
|
+
const fresh = sectionOf(provider, ns)?.revision
|
|
395
|
+
try {
|
|
396
|
+
await provider.update(ns, patch, fresh)
|
|
397
|
+
return { ok: true, conflict: true }
|
|
398
|
+
} catch (error) {
|
|
399
|
+
return { ok: false, conflict: true, error: String(error) }
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* 构建设置 section schema(install 路径与插件 Config 声明共用,保证两代形状同源)。
|
|
405
|
+
* 字段集 = fileOpenTool/keybindings/sidebarMinWidth/maxOpenEditors/integrationBaseUrl/
|
|
406
|
+
* aiInline/aiProvider/aiModel/aiEffort/svnPath/tortoisePath/nativeOpenExts,全部带默认值
|
|
407
|
+
* (Config 启动校验在 undefined/空配置下自动填充,rc/alpha 两代 cordis 均通过)。
|
|
408
|
+
* @author ddj 2026年09月22号
|
|
409
|
+
* @param z schemastery 命名空间(静态 import 或动态加载均可)
|
|
410
|
+
* @returns schemastery object schema
|
|
411
|
+
*/
|
|
412
|
+
export function buildSettingsSchema(z: SettingsDeps['z']): unknown {
|
|
413
|
+
return z.object({
|
|
414
|
+
fileOpenTool: z.string().default(FILE_OPEN_DEFAULT),
|
|
415
|
+
keybindings: z.object(keybindingsShape(z)).default({ ...KEYBINDING_DEFAULTS }),
|
|
416
|
+
sidebarMinWidth: z.number().default(300),
|
|
417
|
+
// 页签数量上限(0 = 不限制;超限时淘汰最久未使用的页签,固定页签除外)
|
|
418
|
+
maxOpenEditors: z.number().default(EDITOR_LIMIT_DEFAULT),
|
|
419
|
+
integrationBaseUrl: z.string().default(INTEGRATION_BASE_DEFAULT),
|
|
420
|
+
// AI 内联补全(默认关;provider/model 空 = 自动路由;effort 空 = 跟随模型默认)
|
|
421
|
+
aiInline: z.boolean().default(AI_CONFIG_DEFAULT.enabled),
|
|
422
|
+
aiProvider: z.string().default(AI_CONFIG_DEFAULT.provider),
|
|
423
|
+
aiModel: z.string().default(AI_CONFIG_DEFAULT.model),
|
|
424
|
+
aiEffort: z.string().default(AI_CONFIG_DEFAULT.effort),
|
|
425
|
+
// SVN 能力:svn CLI 覆盖(空 = PATH)与 TortoiseSVN 目录(Windows 过渡增强)
|
|
426
|
+
svnPath: z.string().default(''),
|
|
427
|
+
tortoisePath: z.string().default(TORTOISE_DIR_DEFAULT),
|
|
428
|
+
// 原生打开范围(逗号分隔后缀;默认 = 让位清单并集,csv/tsv 决策见 shared/nativeOpen.ts)
|
|
429
|
+
nativeOpenExts: z.string().default(DEFAULT_NATIVE_CSV),
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
|
|
289
433
|
function normalizeValue(value: unknown): string {
|
|
290
434
|
if (typeof value !== 'string' || value.trim() === '') return FILE_OPEN_DEFAULT
|
|
291
435
|
return value.trim()
|
|
@@ -337,27 +481,12 @@ export async function installOpenSettingsSection(
|
|
|
337
481
|
/* 装载失败按缺失处理 */
|
|
338
482
|
}
|
|
339
483
|
if (!deps) return false
|
|
340
|
-
const schema = deps.z
|
|
341
|
-
fileOpenTool: deps.z.string().default(FILE_OPEN_DEFAULT),
|
|
342
|
-
keybindings: deps.z.object(keybindingsShape(deps.z)).default({ ...KEYBINDING_DEFAULTS }),
|
|
343
|
-
sidebarMinWidth: deps.z.number().default(300),
|
|
344
|
-
// 页签数量上限(0 = 不限制;超限时淘汰最久未使用的页签,固定页签除外)
|
|
345
|
-
maxOpenEditors: deps.z.number().default(EDITOR_LIMIT_DEFAULT),
|
|
346
|
-
integrationBaseUrl: deps.z.string().default(INTEGRATION_BASE_DEFAULT),
|
|
347
|
-
// AI 内联补全(默认关;provider/model 空 = 自动路由;effort 空 = 跟随模型默认)
|
|
348
|
-
aiInline: deps.z.boolean().default(AI_CONFIG_DEFAULT.enabled),
|
|
349
|
-
aiProvider: deps.z.string().default(AI_CONFIG_DEFAULT.provider),
|
|
350
|
-
aiModel: deps.z.string().default(AI_CONFIG_DEFAULT.model),
|
|
351
|
-
aiEffort: deps.z.string().default(AI_CONFIG_DEFAULT.effort),
|
|
352
|
-
// SVN 能力:svn CLI 覆盖(空 = PATH)与 TortoiseSVN 目录(Windows 过渡增强)
|
|
353
|
-
svnPath: deps.z.string().default(''),
|
|
354
|
-
tortoisePath: deps.z.string().default(TORTOISE_DIR_DEFAULT),
|
|
355
|
-
})
|
|
484
|
+
const schema = buildSettingsSchema(deps.z)
|
|
356
485
|
const strategy = await runSettingsInstall(ctx, ns, schema, entry, {
|
|
357
486
|
setSource: (source) => hooks.setSource(source as () => FileOpenSettings),
|
|
358
487
|
onChange: hooks.onChange,
|
|
359
488
|
}, loader)
|
|
360
|
-
return strategy === 'legacy' || strategy === 'service'
|
|
489
|
+
return strategy === 'legacy' || strategy === 'service' || strategy === 'forms'
|
|
361
490
|
}
|
|
362
491
|
|
|
363
492
|
/** AI 配置脏值读取(settings 未就绪时回退默认)。 */
|
|
@@ -385,8 +514,7 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
385
514
|
let provider: SettingsProvider | undefined
|
|
386
515
|
const notify = (value: unknown): void => { current = normalizeValue(value); onChange(current) }
|
|
387
516
|
const syncRevision = (): void => {
|
|
388
|
-
|
|
389
|
-
revision = descriptor?.revision
|
|
517
|
+
revision = sectionOf(provider, FILE_OPEN_SETTINGS_NS)?.revision
|
|
390
518
|
}
|
|
391
519
|
const setSource = (source: () => FileOpenSettings): void => notify(source().fileOpenTool)
|
|
392
520
|
const settingsChange = (): void => syncRevision()
|
|
@@ -394,8 +522,7 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
394
522
|
/** AI 配置当前值(settings 未就绪回退默认)。 */
|
|
395
523
|
let aiCurrent: AiConfigView = { ...AI_CONFIG_DEFAULT }
|
|
396
524
|
const aiSync = (): void => {
|
|
397
|
-
const
|
|
398
|
-
const stored = descriptor?.value
|
|
525
|
+
const stored = readSectionValue(provider, FILE_OPEN_SETTINGS_NS)
|
|
399
526
|
aiCurrent = stored !== undefined ? aiValueOf(stored) : aiCurrent
|
|
400
527
|
}
|
|
401
528
|
|
|
@@ -409,7 +536,7 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
409
536
|
}
|
|
410
537
|
/** 读取 settings 存储值(describe 未就绪返回 undefined)。 */
|
|
411
538
|
const storedValue = (): unknown => {
|
|
412
|
-
return provider
|
|
539
|
+
return readSectionValue(provider, FILE_OPEN_SETTINGS_NS)
|
|
413
540
|
}
|
|
414
541
|
|
|
415
542
|
void installOpenSettingsSection(ctx, FILE_OPEN_SETTINGS_NS, {
|
|
@@ -438,31 +565,27 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
438
565
|
get revision() { return revision },
|
|
439
566
|
update: async (value: string, expectedRevision?: number): Promise<void> => {
|
|
440
567
|
const next = normalizeValue(value)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
const stored = descriptor?.value as { fileOpenTool?: unknown } | undefined
|
|
568
|
+
const result = await updateSection(provider, FILE_OPEN_SETTINGS_NS, { fileOpenTool: next }, expectedRevision)
|
|
569
|
+
if (!result.ok) log.warn('fileOpenTool 设置写入失败:' + (result.error ?? '未知原因'))
|
|
570
|
+
const stored = readSectionValue(provider, FILE_OPEN_SETTINGS_NS) as { fileOpenTool?: unknown } | undefined
|
|
445
571
|
notify(stored?.fileOpenTool ?? next)
|
|
446
572
|
syncRevision()
|
|
447
573
|
},
|
|
448
574
|
ai: () => aiCurrent,
|
|
449
575
|
svn: () => svnCurrent,
|
|
450
576
|
aiUpdate: async (patch: AiConfigPatch, expectedRevision?: number): Promise<AiConfigView> => {
|
|
451
|
-
if (!provider?.update) {
|
|
452
|
-
// settings 不可用:内存态生效(重启回落默认),保持与 fileOpenTool 的降级语义一致
|
|
453
|
-
if (patch.enabled !== undefined) aiCurrent.enabled = patch.enabled
|
|
454
|
-
if (patch.provider !== undefined) aiCurrent.provider = patch.provider
|
|
455
|
-
if (patch.model !== undefined) aiCurrent.model = patch.model
|
|
456
|
-
if (patch.effort !== undefined) aiCurrent.effort = patch.effort
|
|
457
|
-
return aiCurrent
|
|
458
|
-
}
|
|
459
577
|
const stored = { ...aiCurrent }
|
|
460
578
|
const body: Record<string, unknown> = {}
|
|
461
579
|
if (patch.enabled !== undefined) { stored.enabled = patch.enabled; body.aiInline = patch.enabled }
|
|
462
580
|
if (patch.provider !== undefined) { stored.provider = patch.provider; body.aiProvider = patch.provider }
|
|
463
581
|
if (patch.model !== undefined) { stored.model = patch.model; body.aiModel = patch.model }
|
|
464
582
|
if (patch.effort !== undefined) { stored.effort = patch.effort; body.aiEffort = patch.effort }
|
|
465
|
-
await provider
|
|
583
|
+
const result = await updateSection(provider, FILE_OPEN_SETTINGS_NS, body, expectedRevision)
|
|
584
|
+
if (!result.ok) {
|
|
585
|
+
// settings 不可用/写入失败:内存态生效(重启回落默认),与 fileOpenTool 降级语义一致
|
|
586
|
+
aiCurrent = stored
|
|
587
|
+
return aiCurrent
|
|
588
|
+
}
|
|
466
589
|
aiSync()
|
|
467
590
|
return aiCurrent
|
|
468
591
|
},
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,9 @@ import { newSearcher } from './search/orchestrator.js'
|
|
|
14
14
|
import { newContentSearcher } from './search/content.js'
|
|
15
15
|
import { installIsolation } from './mcpIsolation.js'
|
|
16
16
|
import { cwdOf } from './registry.js'
|
|
17
|
-
import { setupOpenSettings } from './fileOpenSettings.js'
|
|
17
|
+
import { setupOpenSettings, buildSettingsSchema } from './fileOpenSettings.js'
|
|
18
|
+
import type { SettingsDeps } from './fileOpenSettings.js'
|
|
19
|
+
import z from '@deepseek-ai/schemastery'
|
|
18
20
|
import { shellMenuLifecycle } from './integrate.js'
|
|
19
21
|
import { disposeIndex } from './treeIndex.js'
|
|
20
22
|
import { sweepTreeCache } from './paths.js'
|
|
@@ -36,6 +38,18 @@ import type { Ctx } from './store.js'
|
|
|
36
38
|
export const name = "dsh-vscode-mode"
|
|
37
39
|
export const inject = ['sessions', 'fs', 'webServer', 'loader', 'tools', 'workspaceRegistry', 'agents']
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* 插件 Config schema(cordis 从 module 导出读 `plugin.Config` 做启动校验;
|
|
43
|
+
* DSH 0.1.7 起设置页按此 schema 自动生成表单,设置值 = profile 插件配置)。
|
|
44
|
+
* 字段集与 installOpenSettingsSection 的 section schema 同源(buildSettingsSchema),
|
|
45
|
+
* 全字段带默认值——undefined/空配置经 schemastery 校验自动填充(rc/alpha 两代
|
|
46
|
+
* cordis 均读该导出,实测 4.0.0-rc.8 与 0.1.7 行为一致,校验必过)。
|
|
47
|
+
* 运行时解析:@deepseek-ai/schemastery 经插件自身 node_modules(link/dev/npm 安装
|
|
48
|
+
* 三形态均在 peer/dev 依赖面内,先于宿主树命中)。
|
|
49
|
+
* @author ddj 2026年09月22号
|
|
50
|
+
*/
|
|
51
|
+
export const Config = buildSettingsSchema(z as unknown as SettingsDeps['z'])
|
|
52
|
+
|
|
39
53
|
/**
|
|
40
54
|
* 装配插件:挂事件监听、注册路由、安装兼容层。
|
|
41
55
|
* @author ddj 2026年08月20号
|
package/src/lsp/client.ts
CHANGED
|
@@ -62,6 +62,9 @@ export function createLspClient(
|
|
|
62
62
|
workspaceSymbol: false,
|
|
63
63
|
hover: false,
|
|
64
64
|
semanticTokens: false,
|
|
65
|
+
completion: false,
|
|
66
|
+
completionResolve: false,
|
|
67
|
+
signatureHelp: false,
|
|
65
68
|
semanticTokenTypes: [],
|
|
66
69
|
semanticTokenModifiers: [],
|
|
67
70
|
}
|
|
@@ -164,6 +167,8 @@ export function createLspClient(
|
|
|
164
167
|
documentSymbolProvider?: unknown
|
|
165
168
|
workspaceSymbolProvider?: unknown
|
|
166
169
|
hoverProvider?: unknown
|
|
170
|
+
completionProvider?: { resolveProvider?: unknown; triggerCharacters?: unknown }
|
|
171
|
+
signatureHelpProvider?: { triggerCharacters?: unknown; retriggerCharacters?: unknown }
|
|
167
172
|
semanticTokensProvider?: {
|
|
168
173
|
legend?: { tokenTypes?: unknown; tokenModifiers?: unknown }
|
|
169
174
|
}
|
|
@@ -185,6 +190,22 @@ export function createLspClient(
|
|
|
185
190
|
references: { dynamicRegistration: false },
|
|
186
191
|
documentSymbol: { dynamicRegistration: false },
|
|
187
192
|
hover: { dynamicRegistration: false },
|
|
193
|
+
// 补全能力声明:snippetSupport 让服务器回 InsertAsSnippet 片段;
|
|
194
|
+
// resolveSupport.properties 声明我方支持 completionItem/resolve 惰性补全
|
|
195
|
+
// (EmmyLua 据此把 documentation 挪到 resolve 阶段,列表载荷显著变小)。
|
|
196
|
+
completion: {
|
|
197
|
+
dynamicRegistration: false,
|
|
198
|
+
contextSupport: true,
|
|
199
|
+
completionItem: {
|
|
200
|
+
snippetSupport: true,
|
|
201
|
+
documentationFormat: ['markdown', 'plaintext'],
|
|
202
|
+
resolveSupport: { properties: ['documentation', 'detail', 'additionalTextEdits'] },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
signatureHelp: {
|
|
206
|
+
dynamicRegistration: false,
|
|
207
|
+
signatureInformation: { documentationFormat: ['markdown', 'plaintext'] },
|
|
208
|
+
},
|
|
188
209
|
semanticTokens: {
|
|
189
210
|
dynamicRegistration: false,
|
|
190
211
|
requests: { range: false, full: { delta: false } },
|
|
@@ -205,6 +226,9 @@ export function createLspClient(
|
|
|
205
226
|
workspaceSymbol: Boolean(caps.workspaceSymbolProvider),
|
|
206
227
|
hover: Boolean(caps.hoverProvider),
|
|
207
228
|
semanticTokens: Boolean(caps.semanticTokensProvider),
|
|
229
|
+
completion: Boolean(caps.completionProvider),
|
|
230
|
+
completionResolve: Boolean(caps.completionProvider?.resolveProvider),
|
|
231
|
+
signatureHelp: Boolean(caps.signatureHelpProvider),
|
|
208
232
|
semanticTokenTypes: Array.isArray(legend?.tokenTypes)
|
|
209
233
|
? legend.tokenTypes.filter((item): item is string => typeof item === 'string')
|
|
210
234
|
: [...LSP_SEMANTIC_TOKEN_TYPES],
|
package/src/lsp/rpc.ts
CHANGED
|
@@ -436,6 +436,63 @@ export function createLspRpc(deps: LspRpcDeps): { handlers: Partial<RpcHandlerMa
|
|
|
436
436
|
}
|
|
437
437
|
},
|
|
438
438
|
|
|
439
|
+
'edrv.lsp.completion': async (args) => {
|
|
440
|
+
const lang = langOfPath(args.path)
|
|
441
|
+
if (!lang) return { ok: true, completions: undefined }
|
|
442
|
+
const sc = await rootOf(args.sessionId)
|
|
443
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
444
|
+
try {
|
|
445
|
+
const server = serverOf(sc.root, lang)
|
|
446
|
+
if (!server) return { ok: true, completions: undefined }
|
|
447
|
+
const completions = await server.completion(
|
|
448
|
+
wsPath(sc.root, args.path),
|
|
449
|
+
args.position.line,
|
|
450
|
+
args.position.character,
|
|
451
|
+
args.context,
|
|
452
|
+
)
|
|
453
|
+
if (!completions) return { ok: true, completions: undefined }
|
|
454
|
+
const truncated = completions.items.length > 500
|
|
455
|
+
return {
|
|
456
|
+
ok: true,
|
|
457
|
+
completions: truncated ? { ...completions, items: completions.items.slice(0, 500), truncated: true } : completions,
|
|
458
|
+
}
|
|
459
|
+
} catch (error) {
|
|
460
|
+
return { ok: false, error: 'LSP 补全查询失败:' + String(error) }
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
'edrv.lsp.resolveCompletion': async (args) => {
|
|
465
|
+
const lang = langOfPath(args.path)
|
|
466
|
+
if (!lang) return { ok: true, item: undefined }
|
|
467
|
+
const sc = await rootOf(args.sessionId)
|
|
468
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
469
|
+
try {
|
|
470
|
+
const server = serverOf(sc.root, lang)
|
|
471
|
+
if (!server) return { ok: true, item: args.item }
|
|
472
|
+
const item = await server.resolveCompletion(wsPath(sc.root, args.path), args.item)
|
|
473
|
+
return { ok: true, item: item ?? undefined }
|
|
474
|
+
} catch (error) {
|
|
475
|
+
return { ok: false, error: 'LSP 补全解析失败:' + String(error) }
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
'edrv.lsp.signatureHelp': async (args) => {
|
|
480
|
+
const lang = langOfPath(args.path)
|
|
481
|
+
if (!lang) return { ok: true, signatureHelp: undefined }
|
|
482
|
+
const sc = await rootOf(args.sessionId)
|
|
483
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
484
|
+
try {
|
|
485
|
+
const server = serverOf(sc.root, lang)
|
|
486
|
+
if (!server) return { ok: true, signatureHelp: undefined }
|
|
487
|
+
return {
|
|
488
|
+
ok: true,
|
|
489
|
+
signatureHelp: (await server.signatureHelp(wsPath(sc.root, args.path), args.position.line, args.position.character)) ?? undefined,
|
|
490
|
+
}
|
|
491
|
+
} catch (error) {
|
|
492
|
+
return { ok: false, error: 'LSP 签名帮助查询失败:' + String(error) }
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
|
|
439
496
|
'edrv.lsp.semanticTokens': async (args) => {
|
|
440
497
|
const lang = langOfPath(args.path)
|
|
441
498
|
if (!lang) return { ok: true, tokens: undefined }
|