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,95 @@
1
+ /**
2
+ * dsh-vscode-mode client — 「性能优化」页工作区栏目展开状态持久化(纯函数,可单测)。
3
+ * 只持久化「已展开」的工作区键集合(默认全部折叠),对齐 explorerCache 的做法:
4
+ * 不缓存会话条目本身,条目由 edrv.perf.inventory 实时返回。
5
+ * 键前缀统一走 paths.ts PathConst(CACHE_KEY.workspaceFold)。
6
+ * 作者 ddj 2026-09-02
7
+ */
8
+ import { CACHE_KEY } from '../paths.js'
9
+
10
+ /** localStorage 键(全局,不按会话隔离)。 */
11
+ const STORE_KEY = CACHE_KEY.workspaceFold
12
+
13
+ /** 展开键容量上限:超出丢弃尾部,防止无限增长撑爆配额。 */
14
+ export const FOLD_CAP = 300
15
+
16
+ /**
17
+ * 归一化工作区键:仅去首尾空白(工作区键非文件系统路径,不做斜杠转换)。
18
+ * @author ddj 2026年09月02号
19
+ * @param key 工作区标识
20
+ * @returns 归一化键,空值返回 ''
21
+ */
22
+ export function normalizeFoldKey(key: string | undefined | null): string {
23
+ return String(key ?? '').trim()
24
+ }
25
+
26
+ /**
27
+ * 裁剪展开键集:去重、丢弃空值、超容量丢弃尾部(保持传入顺序)。
28
+ * @author ddj 2026年09月02号
29
+ * @param expanded 原始展开键集合
30
+ * @returns 规范化后的键数组
31
+ */
32
+ export function sanitizeFolded(expanded: unknown): string[] {
33
+ if (!Array.isArray(expanded)) return []
34
+ const seen = new Set<string>()
35
+ const out: string[] = []
36
+ for (const item of expanded) {
37
+ const key = normalizeFoldKey(typeof item === 'string' ? item : '')
38
+ if (!key || seen.has(key)) continue
39
+ seen.add(key)
40
+ out.push(key)
41
+ if (out.length >= FOLD_CAP) break
42
+ }
43
+ return out
44
+ }
45
+
46
+ /**
47
+ * 序列化展开状态(JSON,带 schema 版本号)。
48
+ * @author ddj 2026年09月02号
49
+ * @param expanded 已展开的项目键集合
50
+ * @returns JSON 字符串
51
+ */
52
+ export function serializeFold(expanded: string[]): string {
53
+ return JSON.stringify({ v: 1, expanded: sanitizeFolded(expanded) })
54
+ }
55
+
56
+ /**
57
+ * 解析缓存文本;损坏/格式不符 → 空集合。
58
+ * @author ddj 2026年09月02号
59
+ * @param text localStorage 原文
60
+ * @returns 已展开键集合
61
+ */
62
+ export function parseFold(text: string | null): string[] {
63
+ if (!text) return []
64
+ try {
65
+ const data = JSON.parse(text)
66
+ if (!data || typeof data !== 'object') return []
67
+ return sanitizeFolded(data.expanded)
68
+ } catch (error) {
69
+ return []
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 读取展开状态(localStorage 不可用 → 空集合)。
75
+ * @author ddj 2026年09月02号
76
+ * @returns 已展开键集合
77
+ */
78
+ export function foldLoad(): string[] {
79
+ try {
80
+ return parseFold(window.localStorage.getItem(STORE_KEY))
81
+ } catch (error) {
82
+ return []
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 写入展开状态(配额满/隐私模式失败静默)。
88
+ * @author ddj 2026年09月02号
89
+ * @param expanded 已展开键集合
90
+ */
91
+ export function foldSave(expanded: string[]): void {
92
+ try {
93
+ window.localStorage.setItem(STORE_KEY, serializeFold(expanded))
94
+ } catch (error) { /* 配额满/隐私模式忽略 */ }
95
+ }
@@ -33,6 +33,13 @@
33
33
  [data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }
34
34
  [data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }
35
35
  [data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }
36
+ [data-edrv-view] .edrv-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }
37
+ [data-edrv-view] .edrv-statusbar .edrv-sp-progress-message { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }
38
+ [data-edrv-view] .edrv-statusbar .edrv-sp-progress { flex: 0 0 auto; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #52606d); }
39
+ [data-edrv-view] .edrv-lsp-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--dsw-alias-label-tertiary, #9aa5b1); }
40
+ [data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }
41
+ [data-edrv-view] .edrv-lsp-status-dot.busy { background: var(--dsw-alias-state-warn-primary, #d97706); animation: edrv-lsp-pulse 1.2s ease-in-out infinite; }
42
+ @keyframes edrv-lsp-pulse { 50% { opacity: .45; } }
36
43
  [data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }
37
44
  [data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }
38
45
  [data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }
@@ -284,3 +291,11 @@
284
291
  [data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }
285
292
  [data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }
286
293
 
294
+ /* LSP 引用结果浮动面板(Shift+F12 / 右键「查找所有引用」) */
295
+ [data-edrv-view] .edrv-refs { position: absolute; right: 12px; top: 40px; z-index: 60; width: 360px; max-height: 60%; display: flex; flex-direction: column; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 8px; background: var(--dsw-alias-bg-panel, #161b22); box-shadow: 0 6px 24px rgba(0,0,0,.4); overflow: hidden; font-size: 12px; }
296
+ [data-edrv-view] .edrv-refs-head { padding: 6px 10px; font-weight: 600; color: var(--dsw-alias-label-primary, #eee); border-bottom: 1px solid var(--dsw-alias-border-l2, #333); background: var(--dsw-alias-bg-layer-1, transparent); }
297
+ [data-edrv-view] .edrv-refs-list { overflow-y: auto; }
298
+ [data-edrv-view] .edrv-refs-row { padding: 4px 10px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #c0c8d0); font-family: var(--ds-font-family-code, ui-monospace, monospace); }
299
+ [data-edrv-view] .edrv-refs-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); color: var(--dsw-alias-label-primary, #fff); }
300
+
301
+
@@ -10,18 +10,36 @@
10
10
  .vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}
11
11
  .vsm-primary:disabled{opacity:.55;cursor:default}
12
12
 
13
- /* 子 Tab:我的 MCP / MCP 市场 */
13
+ /* MCP 管理下的二级子页签:我的 MCP / 项目 MCP / MCP 市场 */
14
+ .vsm-mcp-subhead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:-4px}
15
+ .vsm-mcp-subtabs{display:flex;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#2e2e2e);flex:1}
16
+ .vsm-mcp-subtabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:7px 2px 9px;cursor:pointer;font-size:13px;position:relative}
17
+ .vsm-mcp-subtabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}
18
+ .vsm-mcp-subtabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}
19
+ .vsm-mcp-subtabs button.active::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}
20
+
21
+ /* 顶层 Tab:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性 */
14
22
  .vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}
15
23
  .vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}
16
24
  .vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}
17
25
  .vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}
18
26
  .vsm-mcp-tabs button.active::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}
19
27
 
28
+ /* 统一线框面板:圆角矩形边框圈住各设置分区,提升可读性。
29
+ 用法:<section class="vsm-panel"> + 可选 <h3 class="vsm-panel-title">。 */
30
+ .vsm-panel{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,transparent);overflow:hidden}
31
+ .vsm-panel-pad{padding:14px 16px}
32
+ .vsm-panel-title{margin:0;padding:10px 16px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8);background:var(--dsw-alias-bg-mask-1,#ffffff0a);border-bottom:1px solid var(--dsw-alias-border-l1,#333)}
33
+ .vsm-panel-body{padding:6px 16px}
34
+ .vsm-panel-body>*:first-child{margin-top:8px}
35
+ .vsm-panel-body>*:last-child{margin-bottom:10px}
36
+
20
37
  /* 通用设置 */
21
- .vsm-general-page{display:flex;flex-direction:column;gap:10px;max-width:720px}
38
+ .vsm-general-page{display:flex;flex-direction:column;gap:14px;max-width:720px}
22
39
  .vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}
23
- .vsm-general-page p{margin:0 0 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}
40
+ .vsm-general-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}
24
41
  .vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}
42
+ .vsm-general-row:last-child{border-bottom:0}
25
43
  .vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}
26
44
  .vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}
27
45
 
@@ -94,8 +112,7 @@
94
112
  .vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}
95
113
  .vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}
96
114
 
97
- /* 兼容性小节 */
98
- .vsm-compat-h3{margin:16px 0 2px;font-size:14px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8)}
115
+ /* 兼容性条目(面板标题改用 .vsm-panel-title) */
99
116
  .vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}
100
117
  .vsm-compat-item.ok .vsm-compat-name::before{content:"● ";color:var(--dsw-alias-state-success-primary,#38bd75)}
101
118
  .vsm-compat-item.warn .vsm-compat-name::before{content:"● ";color:var(--dsw-alias-state-warn-primary,#e5b93f)}
@@ -129,8 +146,17 @@
129
146
  .vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}
130
147
  .vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}
131
148
 
149
+ /* 语言服务器(LSP)设置子 Tab */
150
+ .vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}
151
+ .vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}
152
+ .vsm-lsp-form input{height:26px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;outline:none}
153
+ .vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}
154
+ .vsm-lsp-form button{align-self:flex-start;margin-top:2px}
155
+ .vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}
156
+
132
157
  /* 会话性能(PerfSettings 子 Tab) */
133
158
  .vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}
159
+ .vsm-perf-page>.vsm-panel-body>*:first-child{margin-top:0}
134
160
  .vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}
135
161
  .vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}
136
162
  .vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}
@@ -140,8 +166,19 @@
140
166
  .vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}
141
167
  .vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
142
168
  .vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}
169
+ /* 工作区列表分页器 */
170
+ .vsm-perf-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:4px}
171
+ .vsm-perf-pager-text{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}
172
+ .vsm-perf-pager-actions{display:flex;gap:8px}
173
+ .vsm-perf-ws-list{display:flex;flex-direction:column;gap:8px}
143
174
  .vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}
144
- .vsm-perf-ws-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}
175
+ .vsm-perf-ws-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);cursor:pointer;user-select:none}
176
+ .vsm-perf-ws-head:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}
177
+ .vsm-perf-ws-head:focus-visible{outline:1px solid var(--dsw-alias-brand-primary,#6d55ff);outline-offset:-1px}
178
+ .vsm-perf-ws.expanded .vsm-perf-ws-head{border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}
179
+ .vsm-perf-ws-caret{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;flex-shrink:0;transition:transform .15s;display:inline-block}
180
+ .vsm-perf-ws-caret.open{transform:rotate(90deg)}
181
+ .vsm-perf-ws .vsm-perf-ws-name{flex:1}
145
182
  .vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
146
183
  .vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}
147
184
  .vsm-perf-rows{display:flex;flex-direction:column}
@@ -155,15 +192,9 @@
155
192
  .vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}
156
193
  .vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}
157
194
  .vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}
158
- .vsm-perf-moved{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}
159
- .vsm-perf-moved h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}
160
- .vsm-perf-config{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}
161
- .vsm-perf-config h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}
162
195
  .vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}
163
196
  .vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}
164
197
  .vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}
165
- .vsm-perf-guide{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}
166
- .vsm-perf-guide h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}
167
198
  .vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}
168
199
  .vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}
169
200
  .vsm-perf-modal-card h3{margin:0;font-size:16px}
@@ -176,3 +207,10 @@
176
207
  .vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}
177
208
  .vsm-secondary:disabled{opacity:.55;cursor:default}
178
209
  .vsm-small{padding:4px 10px;font-size:12px}
210
+
211
+ /* 扩展管理(LSP 语言服务器) */
212
+ .vsm-mcp-actions button.vsm-primary,
213
+ .vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}
214
+ .vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}
215
+ .vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}
216
+ .vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@@ -23,6 +23,8 @@ import { setSidePending, SIDEBAR_INSTALL_CMD } from '../sidebarBridge.js'
23
23
  import { upsertViewState, viewStatesLoad, viewStatesSave } from '../state/viewStateCache.js'
24
24
  import { bindingOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybindings.js'
25
25
  import { CACHE_KEY } from '../paths.js'
26
+ import { bindLspEditor, runGoToDefinition, runFindReferences, hideReferencesOverlay } from '../monaco/lsp/providers.js'
27
+ import { onLspProgress, refreshStatus } from '../monaco/lsp/index.js'
26
28
 
27
29
  /**
28
30
  * 中央编辑区:文件页签(脏点/关闭/打开路径)+ Ctrl+P 搜索 + Monaco 编辑器 +
@@ -47,6 +49,7 @@ export function EditorView(props) {
47
49
  const [content, setContent] = React.useState(null)
48
50
  const [contentPath, setContentPath] = React.useState(null)
49
51
  const [status, setStatus] = React.useState('')
52
+ const [lspServers, setLspServers] = React.useState([])
50
53
  const [error, setError] = React.useState(null)
51
54
  const [loadError, setLoadError] = React.useState(null)
52
55
  const [loadStage, setLoadStage] = React.useState({ progress: 0, message: '准备加载编辑器…' })
@@ -265,6 +268,14 @@ export function EditorView(props) {
265
268
  return () => { clearInterval(t); window.removeEventListener('edrv:refresh', onRefresh) }
266
269
  }, [sessionId])
267
270
 
271
+ React.useEffect(() => {
272
+ const publishLsp = (servers) => setLspServers(Array.isArray(servers) ? servers : [])
273
+ const unsubscribe = onLspProgress(publishLsp)
274
+ void refreshStatus(true).then(publishLsp)
275
+ const timer = setInterval(() => { void refreshStatus(true).then(publishLsp) }, 1500)
276
+ return () => { unsubscribe(); clearInterval(timer) }
277
+ }, [sessionId])
278
+
268
279
  React.useEffect(() => {
269
280
  const onOpen = (e) => {
270
281
  const p = e?.detail?.path
@@ -272,6 +283,13 @@ export function EditorView(props) {
272
283
  if (e?.detail?.focusDiff === true) {
273
284
  pendingFocusRef.current = { path: p, region: null }
274
285
  setFocusRequest((value) => value + 1)
286
+ addTab(p, true)
287
+ return
288
+ }
289
+ if (e?.detail?.line != null) {
290
+ // LSP/搜索跳转:打开并定位到行列
291
+ openFileAt(p, e?.detail?.line, e?.detail?.column)
292
+ return
275
293
  }
276
294
  addTab(p, true)
277
295
  }
@@ -282,9 +300,12 @@ export function EditorView(props) {
282
300
  }
283
301
  window.addEventListener('edrv:open-editor', onOpen)
284
302
  window.addEventListener('edrv:show-launcher', onShowLauncher)
303
+ const onStatus = (e) => { if (e?.detail?.text) setStatus(e.detail.text) }
304
+ window.addEventListener('edrv:status', onStatus)
285
305
  return () => {
286
306
  window.removeEventListener('edrv:open-editor', onOpen)
287
307
  window.removeEventListener('edrv:show-launcher', onShowLauncher)
308
+ window.removeEventListener('edrv:status', onStatus)
288
309
  }
289
310
  }, [sessionId])
290
311
 
@@ -543,6 +564,7 @@ export function EditorView(props) {
543
564
  flushSave()
544
565
  saveViewState(active)
545
566
  diffRendererRef.current?.dispose?.()
567
+ hideReferencesOverlay()
546
568
  if (editorRef.current) { editorRef.current.dispose(); editorRef.current = null }
547
569
  for (const m of modelsRef.current.values()) m.dispose()
548
570
  modelsRef.current.clear()
@@ -629,6 +651,21 @@ export function EditorView(props) {
629
651
  precondition: 'editorTextFocus',
630
652
  run: (edx) => menuHandlers()?.openInExplorer(pathOf(edx)),
631
653
  })
654
+ // 语言智能(LSP):转到定义 / 查找所有引用(右键 + F12/Shift+F12 键位)。
655
+ ed.addAction({
656
+ id: 'edrv.goToDefinition', label: '转到定义', contextMenuGroupId: '1_edrv',
657
+ keybindings: m.KeyMod.CtrlCmd | m.KeyCode.F12,
658
+ precondition: 'editorTextFocus',
659
+ run: (edx) => { void runGoToDefinition(edx) },
660
+ })
661
+ ed.addAction({
662
+ id: 'edrv.findReferences', label: '查找所有引用', contextMenuGroupId: '1_edrv',
663
+ keybindings: m.KeyMod.Shift | m.KeyCode.F12,
664
+ precondition: 'editorTextFocus',
665
+ run: (edx) => { void runFindReferences(edx) },
666
+ })
667
+ ed.addCommand(m.KeyCode.F12, () => { void runGoToDefinition(ed) })
668
+ bindLspEditor(ed)
632
669
  // hover 差异块 → 浮出 Keep/Undo(req:鼠标移到编辑区差异块时显示)
633
670
  // 防闪烁:① 区域不变不 setState(浮窗锚定差异块起始行,不跟随鼠标);② 延迟隐藏;
634
671
  // ③ 浮窗自身 onMouseEnter 取消隐藏计时(鼠标在浮窗与编辑器间移动不闪)。
@@ -982,6 +1019,22 @@ export function EditorView(props) {
982
1019
  (cursor ? React.createElement('span', { className: 'edrv-pb-meta' }, cursor) : null),
983
1020
  (status ? React.createElement('span', { className: 'edrv-pb-meta edrv-pb-status' }, status) : null))
984
1021
 
1022
+ const lspLanguage = active ? langOf(active) : ''
1023
+ const lspServer = lspServers.find((item) => item.languageId === lspLanguage)
1024
+ const lspPhaseText = { idle: '未启动', starting: '启动中', ready: '已就绪', indexing: '解析中', unavailable: '不可用', stopped: '已停止' }
1025
+ const lspSourceText = { extension: '扩展', discover: '自动发现', manual: '手动配置', none: '未配置' }
1026
+ const lspLabel = lspServer
1027
+ ? 'LSP ' + lspLanguage + ' · ' + (lspPhaseText[lspServer.phase] ?? lspServer.phase) + ' · ' + (lspSourceText[lspServer.source] ?? lspServer.source)
1028
+ : 'LSP ' + lspLanguage + ' · 未启动'
1029
+ const lspProgress = typeof lspServer?.progress === 'number' ? Math.round(lspServer.progress) : null
1030
+ const lspFooter = (lspLanguage === 'lua' || lspLanguage === 'csharp')
1031
+ ? React.createElement('div', { className: 'edrv-statusbar', title: lspServer?.progressMessage || lspLabel },
1032
+ React.createElement('span', { className: 'edrv-lsp-status-dot ' + (lspServer?.phase === 'ready' ? 'ready' : lspServer?.phase === 'indexing' || lspServer?.phase === 'starting' ? 'busy' : 'idle') }),
1033
+ React.createElement('span', { className: 'edrv-sp-lsp' }, lspLabel),
1034
+ lspServer?.progressMessage ? React.createElement('span', { className: 'edrv-sp-progress-message' }, lspServer.progressMessage) : null,
1035
+ lspProgress !== null ? React.createElement('span', { className: 'edrv-sp-progress' }, lspProgress + '%') : null)
1036
+ : null
1037
+
985
1038
  const tabRow = React.createElement('div', { style: { display: 'flex', alignItems: 'center', background: 'var(--dsw-alias-bg-layer-1,transparent)', flexShrink: 0 } },
986
1039
  tabsEl,
987
1040
  (sum.totalFiles > 0
@@ -1169,7 +1222,8 @@ export function EditorView(props) {
1169
1222
  pathBar,
1170
1223
  tabRow,
1171
1224
  sideHintEl,
1172
- editorArea)
1225
+ editorArea,
1226
+ lspFooter)
1173
1227
  // 编辑器根节点按 composer 顶部边界动态限高,底部对话区域继续由 DSH 原生渲染。
1174
1228
  // 侧栏形态由面板容器给高(100%),不做 composer 几何同步。
1175
1229
  const editorRow = React.createElement('div', { className: 'edrv-editor-row' },
@@ -178,10 +178,13 @@ export function KeybindingsPanel() {
178
178
  unavailable && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, '设置服务暂不可用,当前使用默认键位。'),
179
179
  error && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error),
180
180
  message && React.createElement('div', { className: 'vsm-kb-message' }, message),
181
- React.createElement('div', { className: 'vsm-kb-list' }, rows),
182
- React.createElement('div', { className: 'vsm-kb-actionsbar' },
183
- React.createElement('span', { className: 'vsm-kb-dirty' }, dirty ? '有未保存的修改' : ''),
184
- React.createElement('button', { className: 'vsm-primary vsm-small', disabled: unavailable || notReady || busy || snapshot?.writable === false || !dirty, onClick: save }, busy ? '保存中…' : '保存'),
185
- React.createElement('button', { className: 'vsm-small', disabled: unavailable || notReady || busy || snapshot?.writable === false, onClick: resetAll }, '恢复默认')),
181
+ React.createElement('section', { className: 'vsm-panel' },
182
+ React.createElement('h3', { className: 'vsm-panel-title' }, '编辑器键位'),
183
+ React.createElement('div', { className: 'vsm-panel-body' }, React.createElement('div', { className: 'vsm-kb-list' }, rows)),
184
+ React.createElement('div', { className: 'vsm-panel-body vsm-kb-actionsbar' },
185
+ React.createElement('span', { className: 'vsm-kb-dirty' }, dirty ? '有未保存的修改' : ''),
186
+ React.createElement('button', { className: 'vsm-primary vsm-small', disabled: unavailable || notReady || busy || snapshot?.writable === false || !dirty, onClick: save }, busy ? '保存中…' : '保存'),
187
+ React.createElement('button', { className: 'vsm-small', disabled: unavailable || notReady || busy || snapshot?.writable === false, onClick: resetAll }, '恢复默认')),
188
+ ),
186
189
  )
187
190
  }