dsh-vscode-mode 0.1.32 → 0.1.36

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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/lib/client.js +1229 -124
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +1959 -52
  5. package/lib/index.js.map +1 -1
  6. package/package.json +2 -2
  7. package/src/client/events.ts +11 -0
  8. package/src/client/index.ts +15 -2
  9. package/src/client/monaco/lsp/index.ts +30 -0
  10. package/src/client/monaco/lsp/lspClient.ts +192 -0
  11. package/src/client/monaco/lsp/providers.ts +229 -0
  12. package/src/client/outline/OutlinePanel.tsx +1 -1
  13. package/src/client/outline/lspSource.ts +49 -0
  14. package/src/client/outline/parse.ts +31 -1
  15. package/src/client/outline/types.ts +2 -0
  16. package/src/client/paths.ts +2 -0
  17. package/src/client/state/workspaceFoldCache.ts +95 -0
  18. package/src/client/styles/editor.css +15 -0
  19. package/src/client/styles/mcp.css +50 -12
  20. package/src/client/ui/EditorView.ts +55 -1
  21. package/src/client/ui/KeybindingsPanel.ts +8 -5
  22. package/src/client/ui/LspSettings.ts +274 -0
  23. package/src/client/ui/McpSettings.ts +112 -49
  24. package/src/client/ui/PerfSettings.ts +145 -34
  25. package/src/fileOpenSettings.ts +1 -0
  26. package/src/index.ts +20 -2
  27. package/src/lsp/client.ts +205 -0
  28. package/src/lsp/config.ts +107 -0
  29. package/src/lsp/extmgr.ts +208 -0
  30. package/src/lsp/jsonrpc.ts +86 -0
  31. package/src/lsp/manager.ts +133 -0
  32. package/src/lsp/providers.ts +235 -0
  33. package/src/lsp/rpc.ts +334 -0
  34. package/src/lsp/server.ts +346 -0
  35. package/src/lsp/settings.ts +44 -0
  36. package/src/lsp/transport.ts +143 -0
  37. package/src/lsp/uri.ts +62 -0
  38. package/src/lsp/zip.ts +82 -0
  39. package/src/rpc.ts +11 -2
  40. package/src/shared/lsp.ts +138 -0
  41. package/src/shared/rpc.ts +33 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * dsh-vscode-mode host — LSP 配置设置 section 安装(动态依赖,缺失不抛错)。
3
+ * ns:dsh-vscode-mode.languageServers;每语言 { enabled, command, path }。
4
+ * 作者 ddj 2026-08-27
5
+ */
6
+ import type { Ctx } from '../store.js'
7
+ import { LSP_LANGUAGES, LSP_SETTINGS_NS, type LspConfig } from './config.js'
8
+ import { loadSettingsDeps, type SettingsDeps } from '../fileOpenSettings.js'
9
+
10
+ /**
11
+ * 安装 LSP 设置 section。
12
+ * @author ddj 2026年08月27号
13
+ * @param ctx DSH 上下文
14
+ * @param entry 初始配置
15
+ * @returns 是否成功安装
16
+ */
17
+ export async function installLspSettingsSection(ctx: Ctx, entry: LspConfig): Promise<boolean> {
18
+ const deps = await loadSettingsDeps()
19
+ if (!deps) return false
20
+ try {
21
+ // schemastery 无 .optional(),对象字段默认即可选;.default 必给避免 undefined 覆盖
22
+ const z = deps.z
23
+ const langShape: Record<string, unknown> = {}
24
+ for (const lang of LSP_LANGUAGES) {
25
+ langShape[lang] = z.object({
26
+ enabled: z.boolean(),
27
+ command: z.string(),
28
+ path: z.string(),
29
+ }).default({})
30
+ }
31
+ const schema = z.object(langShape).default({})
32
+ deps.installSettingsSection(ctx, LSP_SETTINGS_NS, schema, entry, {
33
+ setSource: () => {},
34
+ onChange: () => {},
35
+ })
36
+ return true
37
+ } catch (error) {
38
+ // 设置 section 安装失败不致命:降级为无设置 UI,LSP 核心功能不受影响
39
+ console.error('[dsh-vscode-mode] LSP 设置 section 安装失败:' + String(error))
40
+ return false
41
+ }
42
+ }
43
+
44
+ export type { SettingsDeps }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * dsh-vscode-mode host — LSP 语言服务器进程传输层。
3
+ * 基于 node:child_process.spawn(stdio pipe 双向流)。刻意不用 DSH ctx.subprocess
4
+ * 抽象:LSP 需要持续写 stdin + 流式读 stdout,而该抽象 stdin 不可写、输出为一次性收集。
5
+ * 作者 ddj 2026-08-27
6
+ */
7
+ import { spawn, type ChildProcess } from 'node:child_process'
8
+ import { createFrameParser } from './jsonrpc.js'
9
+
10
+ const STDERR_CAP = 256 << 10
11
+
12
+ export interface TransportSpec {
13
+ argv: string[]
14
+ cwd?: string
15
+ env?: Record<string, string>
16
+ }
17
+
18
+ export interface Transport {
19
+ /** 写一行帧到 stdin(自动加换行)。 */
20
+ write(chunk: Buffer): boolean
21
+ /** 推送 stdout 解析出的原始消息(内部由帧解析器调用)。 */
22
+ onMessage: ((message: unknown) => void) | null
23
+ /** stderr 收集缓冲(有界,供诊断)。 */
24
+ stderr(): string
25
+ /** 进程退出/崩溃回调(code/signal)。 */
26
+ onExit: ((code: number | null, signal: string | null) => void) | null
27
+ /** 优雅关闭后强杀(Windows 下连带进程树)。 */
28
+ dispose(): void
29
+ /** 是否仍存活。 */
30
+ readonly alive: boolean
31
+ readonly pid: number | undefined
32
+ }
33
+
34
+ const running = new Set<ChildProcess>()
35
+
36
+ /** 当前仍在运行的 LSP 子进程(供 disposeAll / 诊断)。 */
37
+ export function liveChildren(): ReadonlySet<ChildProcess> {
38
+ return running
39
+ }
40
+
41
+ /**
42
+ * 启动语言服务器进程。
43
+ * @author ddj 2026年08月27号
44
+ * @param spec 启动参数(argv 数组,不经 shell,Windows 引号安全)
45
+ * @param onMessage stdout 消息回调
46
+ * @param logger 诊断日志(可选)
47
+ * @returns 传输句柄
48
+ */
49
+ export function spawnServer(spec: TransportSpec, onMessage: (message: unknown) => void, logger?: (line: string) => void): Transport {
50
+ const { argv, cwd, env } = spec
51
+ const child = spawn(argv[0], argv.slice(1), {
52
+ cwd,
53
+ env: { ...process.env, ...env },
54
+ stdio: ['pipe', 'pipe', 'pipe'],
55
+ windowsHide: true,
56
+ })
57
+ running.add(child)
58
+ let stderrBuf = Buffer.alloc(0)
59
+ let alive = true
60
+ const parser = createFrameParser()
61
+
62
+ child.stderr.on('data', (chunk: Buffer) => {
63
+ if (stderrBuf.length < STDERR_CAP) {
64
+ stderrBuf = Buffer.concat([stderrBuf, chunk], Math.min(stderrBuf.length + chunk.length, STDERR_CAP))
65
+ }
66
+ logger?.(chunk.toString('utf8'))
67
+ })
68
+
69
+ let exited = false
70
+ const emitExit = (code: number | null, signal: string | null): void => {
71
+ if (exited) return
72
+ exited = true
73
+ alive = false
74
+ running.delete(child)
75
+ transport.onExit?.(code, signal)
76
+ }
77
+ child.on('error', (error) => {
78
+ logger?.('spawn error: ' + String(error))
79
+ emitExit(null, null)
80
+ })
81
+ child.on('exit', (code, signal) => emitExit(code, signal))
82
+ child.on('close', (code, signal) => emitExit(code, signal))
83
+
84
+ const transport: Transport = {
85
+ write(chunk: Buffer): boolean {
86
+ if (!alive || !child.stdin.writable) return false
87
+ child.stdin.write(chunk)
88
+ return true
89
+ },
90
+ onMessage,
91
+ stderr(): string {
92
+ return stderrBuf.toString('utf8')
93
+ },
94
+ onExit: null,
95
+ dispose(): void {
96
+ if (!alive) return
97
+ // 先 SIGTERM 让服务器有机会清理,1.5s 后强杀进程树
98
+ child.kill()
99
+ const timer = setTimeout(() => {
100
+ if (!alive) return
101
+ alive = false
102
+ running.delete(child)
103
+ try {
104
+ if (process.platform === 'win32') {
105
+ spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' })
106
+ } else {
107
+ child.kill('SIGKILL')
108
+ }
109
+ } catch (error) {
110
+ logger?.('force kill error: ' + String(error))
111
+ }
112
+ }, 1500)
113
+ timer.unref?.()
114
+ },
115
+ get alive() {
116
+ return alive
117
+ },
118
+ get pid() {
119
+ return child.pid
120
+ },
121
+ }
122
+ // stdout 帧解析 → onMessage(LSP 响应/通知;onMessage 由 client 接管后动态生效)
123
+ child.stdout.on('data', (chunk: Buffer) => {
124
+ for (const message of parser.push(chunk)) transport.onMessage?.(message)
125
+ })
126
+ return transport
127
+ }
128
+
129
+ /** 强杀全部运行中的 LSP 子进程(插件卸载/退出用)。 */
130
+ export function disposeAllServers(): void {
131
+ for (const child of running) {
132
+ try {
133
+ if (process.platform === 'win32' && child.pid) {
134
+ spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' })
135
+ } else {
136
+ child.kill('SIGKILL')
137
+ }
138
+ } catch (error) {
139
+ /* 忽略单个清理失败 */
140
+ }
141
+ }
142
+ running.clear()
143
+ }
package/src/lsp/uri.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * dsh-vscode-mode host — 路径 ↔ file:// URI 转换(纯函数,node 可测)。
3
+ * LSP 的 uri 字段必须是 file:// 绝对 URI;客户端 Monaco 用 edrv:///<encodeURI(path)>。
4
+ * 作者 ddj 2026-08-27
5
+ */
6
+ import { fileURLToPath, pathToFileURL } from 'node:url'
7
+ import { isAbsolute, resolve as resolvePath } from 'node:path'
8
+
9
+ /**
10
+ * 绝对路径 → file:// URI(Windows 盘符保留:D:/x → file:///D:/x)。
11
+ * @author ddj 2026年08月27号
12
+ * @param absPath 绝对路径(正/反斜杠均可)
13
+ * @returns file:// URI
14
+ */
15
+ export function pathToFileUri(absPath: string): string {
16
+ const normalized = absPath.replace(/\\/g, '/')
17
+ const drive = /^([A-Za-z]:)(?:\/(.*))?$/.exec(normalized)
18
+ if (drive) {
19
+ const tail = drive[2] ? '/' + drive[2].split('/').map(encodeURIComponent).join('/') : ''
20
+ return 'file:///' + drive[1] + tail
21
+ }
22
+ return pathToFileURL(normalized).toString()
23
+ }
24
+
25
+ /**
26
+ * file:// URI → 绝对路径(Windows file:///C:/x → C:\x)。
27
+ * @author ddj 2026年08月27号
28
+ * @param uri file:// URI
29
+ * @returns 绝对路径
30
+ */
31
+ export function fileUriToPath(fileUri: string): string {
32
+ try {
33
+ return fileURLToPath(fileUri)
34
+ } catch (error) {
35
+ return String(error)
36
+ }
37
+ }
38
+
39
+ /**
40
+ * 将(可能相对的)路径解析为绝对路径后转 file:// URI。
41
+ * @author ddj 2026年08月27号
42
+ * @param path 相对或绝对路径
43
+ * @param cwd 解析相对路径的基准(可选)
44
+ * @returns file:// URI
45
+ */
46
+ export function resolveToFileUri(path: string, cwd?: string): string {
47
+ const abs = isAbsolute(path) ? path : cwd ? resolvePath(cwd, path) : resolvePath(path)
48
+ return pathToFileUri(abs)
49
+ }
50
+
51
+ /**
52
+ * 判定绝对路径是否在给定根目录(含)内。
53
+ * @author ddj 2026年08月27号
54
+ * @param root 根目录绝对路径
55
+ * @param target 目标绝对路径
56
+ * @returns 是否在根内
57
+ */
58
+ export function isInside(root: string, target: string): boolean {
59
+ const r = root.replace(/\\/g, '/').replace(/\/+$/, '') + '/'
60
+ const t = target.replace(/\\/g, '/')
61
+ return t === r.slice(0, -1) || t.startsWith(r)
62
+ }
package/src/lsp/zip.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * dsh-vscode-mode host — 纯 Node ZIP 解压(零依赖,跨平台)。
3
+ * 供 extmgr 解包 VSIX 使用;只实现 VSIX 需要的子集:
4
+ * 读取 EOCD + 中央目录,支持 stored(0)/deflate(8),忽略 zip64(VSIX < 4GB)。
5
+ * 作者 ddj 2026-08-27
6
+ */
7
+ import { inflateRawSync } from 'node:zlib'
8
+
9
+ /** 单条解压结果:相对路径 + 内容(目录条目 path 以 / 结尾)。 */
10
+ export interface ZipEntry {
11
+ path: string
12
+ data: Buffer
13
+ isDirectory: boolean
14
+ }
15
+
16
+ const EOCD_SIG = 0x06054b50
17
+ const CDIR_SIG = 0x02014b50
18
+ const LHDR_SIG = 0x04034b50
19
+
20
+ /**
21
+ * 解析 ZIP 缓冲区 → 条目列表(不解压内容,仅元数据)。
22
+ * @author ddj 2026年08月27号
23
+ * @param buf zip 字节
24
+ * @returns 条目元数据
25
+ */
26
+ export function zipEntries(buf: Buffer): { name: string; method: number; compressed: number; uncompressed: number; localOffset: number }[] {
27
+ const eocd = findEocd(buf)
28
+ if (eocd < 0) throw new Error('不是有效的 ZIP 文件(找不到 EOCD)')
29
+ const total = buf.readUInt16LE(eocd + 10)
30
+ let offset = buf.readUInt32LE(eocd + 16)
31
+ const out: { name: string; method: number; compressed: number; uncompressed: number; localOffset: number }[] = []
32
+ for (let i = 0; i < total; i++) {
33
+ if (buf.readUInt32LE(offset) !== CDIR_SIG) throw new Error('ZIP 中央目录损坏')
34
+ const method = buf.readUInt16LE(offset + 10)
35
+ const compressed = buf.readUInt32LE(offset + 20)
36
+ const uncompressed = buf.readUInt32LE(offset + 24)
37
+ const nameLen = buf.readUInt16LE(offset + 28)
38
+ const extraLen = buf.readUInt16LE(offset + 30)
39
+ const commentLen = buf.readUInt16LE(offset + 32)
40
+ const localOffset = buf.readUInt32LE(offset + 42)
41
+ const name = buf.subarray(offset + 46, offset + 46 + nameLen).toString('utf8')
42
+ out.push({ name, method, compressed, uncompressed, localOffset })
43
+ offset += 46 + nameLen + extraLen + commentLen
44
+ }
45
+ return out
46
+ }
47
+
48
+ /**
49
+ * 解压 ZIP → 条目列表(目录条目含尾部 /)。
50
+ * @author ddj 2026年08月27号
51
+ * @param buf zip 字节
52
+ * @returns 条目(含内容)
53
+ */
54
+ export function unzip(buf: Buffer): ZipEntry[] {
55
+ const metas = zipEntries(buf)
56
+ const out: ZipEntry[] = []
57
+ for (const meta of metas) {
58
+ const lh = meta.localOffset
59
+ if (buf.readUInt32LE(lh) !== LHDR_SIG) throw new Error('ZIP 本地头损坏:' + meta.name)
60
+ const nameLen = buf.readUInt16LE(lh + 26)
61
+ const extraLen = buf.readUInt16LE(lh + 28)
62
+ const dataStart = lh + 30 + nameLen + extraLen
63
+ const isDir = /\/$/.test(meta.name)
64
+ let data: Buffer = Buffer.alloc(0)
65
+ if (!isDir && meta.compressed > 0) {
66
+ const raw = buf.subarray(dataStart, dataStart + meta.compressed)
67
+ data = meta.method === 8 ? Buffer.from(inflateRawSync(raw)) : meta.method === 0 ? Buffer.from(raw) : Buffer.alloc(0)
68
+ }
69
+ out.push({ path: meta.name, data, isDirectory: isDir })
70
+ }
71
+ return out
72
+ }
73
+
74
+ /** 定位 EOCD(从尾部向前扫,容忍尾部注释)。 */
75
+ function findEocd(buf: Buffer): number {
76
+ const maxScan = Math.min(buf.length, 65557)
77
+ for (let i = buf.length - 22; i >= buf.length - maxScan; i--) {
78
+ if (i < 0) break
79
+ if (buf.readUInt32LE(i) === EOCD_SIG) return i
80
+ }
81
+ return -1
82
+ }
package/src/rpc.ts CHANGED
@@ -216,8 +216,16 @@ async function latestPatchBackup(patchPath: string): Promise<string | undefined>
216
216
  }
217
217
 
218
218
  /** 各方法 handler 表(类型由 shared/rpc 的 RpcHandlerMap 约束)。 */
219
- export function buildHandlers(ctx: Ctx, registry: Registry, searcher = newSearcher(ctx), contentSearcher = newContentSearcher(ctx)): RpcHandlerMap {
219
+ export function buildHandlers(
220
+ ctx: Ctx,
221
+ registry: Registry,
222
+ searcher = newSearcher(ctx),
223
+ contentSearcher = newContentSearcher(ctx),
224
+ lspHandlers?: Partial<RpcHandlerMap>,
225
+ ): RpcHandlerMap {
220
226
  return {
227
+ // edrv.lsp.* 由 createLspRpc 一次性提供(tracker 跨请求保留),这里并入。
228
+ ...((lspHandlers ?? {}) as RpcHandlerMap),
221
229
  'edrv.list': async (args) => {
222
230
  const sc = await requireSession(ctx, args.sessionId)
223
231
  if ('err' in sc) return { ok: false, error: sc.err }
@@ -664,8 +672,9 @@ export async function handleRpc<M extends RpcMethod>(
664
672
  args: RpcRequestMap[M],
665
673
  searcher = newSearcher(ctx),
666
674
  contentSearcher = newContentSearcher(ctx),
675
+ lspHandlers?: Partial<RpcHandlerMap>,
667
676
  ): Promise<RpcResult<M>> {
668
- const handlers = buildHandlers(ctx, registry, searcher, contentSearcher)
677
+ const handlers = buildHandlers(ctx, registry, searcher, contentSearcher, lspHandlers)
669
678
  const handler = handlers[method]
670
679
  if (!handler) return { ok: false, error: '未知方法: ' + String(method) } as RpcResult<M>
671
680
  return handler(args)
@@ -0,0 +1,138 @@
1
+ /**
2
+ * dsh-vscode-mode shared — 语言服务器 RPC 契约(双面,禁 node/react)。
3
+ * LSP 请求/响应的 JSON 载荷形状 + 纯函数辅助(Monaco↔LSP 坐标换算等)。
4
+ * 只描述"过 RPC 的载荷",宿主与语言服务器之间的帧级交互在 host 侧 src/lsp/ 内。
5
+ * 作者 ddj 2026-08-27
6
+ */
7
+
8
+ /** LSP Position(0-based:line 行号从 0,character UTF-16 列从 0)。 */
9
+ export interface LspPosition {
10
+ line: number
11
+ character: number
12
+ }
13
+
14
+ /** LSP Range(start/end 均 0-based,包含 start、排除 end)。 */
15
+ export interface LspRange {
16
+ start: LspPosition
17
+ end: LspPosition
18
+ }
19
+
20
+ /** LSP Location:uri 为 file:// 绝对路径;root 用于还原为工作区相对路径。 */
21
+ export interface LspLocation {
22
+ uri: string
23
+ range: LspRange
24
+ root?: string
25
+ }
26
+
27
+ /** 跳转结果(definition 常返回 LocationLink,归一化为 targetUri + targetRange + 可选 originSelectionRange)。 */
28
+ export interface LspLocationLink {
29
+ targetUri: string
30
+ targetRange: LspRange
31
+ targetSelectionRange: LspRange
32
+ originSelectionRange?: LspRange
33
+ }
34
+
35
+ /** 归一化后的符号(documentSymbol/workspaceSymbol 统一形状)。kind 为 LSP SymbolKind 数值。 */
36
+ export interface LspSymbol {
37
+ name: string
38
+ kind: number
39
+ range: LspRange
40
+ selectionRange: LspRange
41
+ detail?: string
42
+ containerName?: string
43
+ children?: LspSymbol[]
44
+ }
45
+
46
+ /** hover 内容(markdown/纯文本行数组)。 */
47
+ export interface LspHover {
48
+ contents: string[]
49
+ range?: LspRange
50
+ }
51
+
52
+ /** LSP 服务器能力子集:本插件会用到的方法。 */
53
+ export interface LspServerCapabilities {
54
+ definition: boolean
55
+ references: boolean
56
+ documentSymbol: boolean
57
+ workspaceSymbol: boolean
58
+ hover: boolean
59
+ }
60
+
61
+ /** 服务器进程/会话状态。 */
62
+ export type LspServerPhase =
63
+ | 'idle' // 未启动(惰性)
64
+ | 'starting' // 启动中(initialize 握手)
65
+ | 'ready' // 就绪
66
+ | 'indexing' // 工作区索引中
67
+ | 'unavailable' // 连续失败,停止自动重启
68
+ | 'stopped' // 已停止(禁用/卸载/会话结束)
69
+
70
+ /** 单语言服务器状态(edrv.lsp.status 载荷元素)。 */
71
+ export interface LspServerStatus {
72
+ languageId: string
73
+ source: 'extension' | 'discover' | 'manual' | 'none'
74
+ phase: LspServerPhase
75
+ version?: string
76
+ reason?: string // unavailable/不可用原因(缺 dotnet、找不到服务器等)
77
+ progress?: number // LSP 工作区解析进度(0–100,未知时省略)
78
+ progressMessage?: string
79
+ root?: string // 绑定工作区根(相对显示)
80
+ }
81
+
82
+ /**
83
+ * Monaco 位置(1-based lineNumber/column)→ LSP Position(0-based)。
84
+ * @author ddj 2026年08月27号
85
+ * @param lineNumber Monaco 行(≥1)
86
+ * @param column Monaco 列(≥1)
87
+ * @returns LSP 0-based position
88
+ */
89
+ export function monoToLsp(lineNumber: number, column: number): LspPosition {
90
+ return { line: Math.max(0, lineNumber - 1), character: Math.max(0, column - 1) }
91
+ }
92
+
93
+ /**
94
+ * LSP Position(0-based)→ Monaco 位置(1-based)。
95
+ * @author ddj 2026年08月27号
96
+ * @param pos LSP position
97
+ * @returns { lineNumber, column }(均 ≥1)
98
+ */
99
+ export function lspToMono(pos: LspPosition): { lineNumber: number; column: number } {
100
+ return { lineNumber: Math.max(1, pos.line + 1), column: Math.max(1, pos.character + 1) }
101
+ }
102
+
103
+ /** file:// URI → 路径(仅做规范化解码;Windows file:///C:/... 保留)。 */
104
+ export function fileUriToPath(uri: string): string {
105
+ if (!uri) return ''
106
+ const rest = uri.replace(/^file:\/\//, '')
107
+ const decoded = decodeURIComponent(rest)
108
+ return decoded.replace(/^\/([A-Za-z]:)/, '$1') // file:///C:/x → C:/x
109
+ }
110
+
111
+ /** 已装扩展信息(edrv.lsp.ext.list 载荷元素)。 */
112
+ export interface LspExtInfo {
113
+ id: string // publisher.name
114
+ namespace: string
115
+ name: string
116
+ version: string
117
+ displayName?: string
118
+ description?: string
119
+ }
120
+
121
+ /** Open VSX 市场搜索结果项(edrv.lsp.ext.market 载荷元素)。 */
122
+ export interface LspMarketItem {
123
+ namespace: string
124
+ name: string
125
+ id: string
126
+ version: string
127
+ displayName?: string
128
+ description?: string
129
+ download?: string
130
+ }
131
+
132
+ /** 有更新的已装扩展(edrv.lsp.ext.updates 载荷元素)。 */
133
+ export interface LspExtUpdate {
134
+ id: string
135
+ current: string
136
+ latest: string
137
+ displayName?: string
138
+ }
package/src/shared/rpc.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  } from './types.js'
15
15
  import type { MpcConfig, MpcProject, MpcProjectSaveInput, MpcServer } from './mcp.js'
16
16
  import type { CompatReport, DevFormInfo } from './compat.js'
17
+ import type { LspExtInfo, LspExtUpdate, LspHover, LspLocation, LspMarketItem, LspPosition, LspServerStatus, LspSymbol } from './lsp.js'
17
18
 
18
19
  /** webServer 精确路由。 */
19
20
  export const RPC_PATH = '/edrv/rpc'
@@ -134,6 +135,22 @@ export interface RpcRequestMap {
134
135
  'vscode.devFormGet': {}
135
136
  'vscode.devFormSet': { enabled: boolean; path?: string }
136
137
  'compat': {}
138
+ 'edrv.lsp.status': { sessionId?: string }
139
+ 'edrv.lsp.sync': { sessionId?: string; path: string; text: string; version: number }
140
+ 'edrv.lsp.close': { sessionId?: string; path: string }
141
+ 'edrv.lsp.definition': { sessionId?: string; path: string; position: LspPosition }
142
+ 'edrv.lsp.references': { sessionId?: string; path: string; position: LspPosition; includeDeclaration?: boolean }
143
+ 'edrv.lsp.documentSymbol': { sessionId?: string; path: string }
144
+ 'edrv.lsp.workspaceSymbol': { sessionId?: string; query: string }
145
+ 'edrv.lsp.hover': { sessionId?: string; path: string; position: LspPosition }
146
+ 'edrv.lsp.configGet': {}
147
+ 'edrv.lsp.configUpdate': { languageId: string; enabled?: boolean; command?: string; path?: string }
148
+ 'edrv.lsp.ext.list': {}
149
+ 'edrv.lsp.ext.install': { vsixPath?: string; namespace?: string; name?: string; version?: string }
150
+ 'edrv.lsp.ext.uninstall': { id: string }
151
+ 'edrv.lsp.ext.update': { id: string }
152
+ 'edrv.lsp.ext.updates': {}
153
+ 'edrv.lsp.ext.market': { query: string; size?: number }
137
154
  'edrv.perf.inventory': { sessionId?: string }
138
155
  'edrv.perf.sessionSize': { sessionId?: string; cwd: string }
139
156
  'edrv.perf.movePlan': { workspaceKey?: string; sessionIds?: string[]; minBytes?: number; olderThanDays?: number }
@@ -180,6 +197,22 @@ export interface RpcOkMap {
180
197
  'vscode.devFormGet': { devForm: DevFormInfo }
181
198
  'vscode.devFormSet': { devForm: DevFormInfo; restart: boolean }
182
199
  'compat': { report: CompatReport }
200
+ 'edrv.lsp.status': { servers: LspServerStatus[] }
201
+ 'edrv.lsp.sync': object
202
+ 'edrv.lsp.close': object
203
+ 'edrv.lsp.definition': { locations: LspLocation[]; truncated?: boolean }
204
+ 'edrv.lsp.references': { locations: LspLocation[]; truncated?: boolean }
205
+ 'edrv.lsp.documentSymbol': { symbols: LspSymbol[] }
206
+ 'edrv.lsp.workspaceSymbol': { symbols: LspSymbol[]; truncated?: boolean }
207
+ 'edrv.lsp.hover': { hover?: LspHover }
208
+ 'edrv.lsp.configGet': { config: Record<string, unknown> }
209
+ 'edrv.lsp.configUpdate': { config: Record<string, unknown> }
210
+ 'edrv.lsp.ext.list': { extensions: LspExtInfo[] }
211
+ 'edrv.lsp.ext.install': { extension: LspExtInfo; updated?: boolean }
212
+ 'edrv.lsp.ext.uninstall': object
213
+ 'edrv.lsp.ext.update': { extension: LspExtInfo; updated: boolean }
214
+ 'edrv.lsp.ext.updates': { updates: LspExtUpdate[] }
215
+ 'edrv.lsp.ext.market': { extensions: LspMarketItem[] }
183
216
  'edrv.perf.inventory': { workspaces: PerfWorkspace[]; sessions: PerfSession[]; totals: PerfTotals; activeIds: string[] }
184
217
  'edrv.perf.sessionSize': { bytes: number; exists: boolean }
185
218
  'edrv.perf.movePlan': { items: PerfMoveItem[]; reclaimedBytes: number }