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.
- package/LICENSE +21 -0
- package/lib/client.js +1229 -124
- package/lib/client.js.map +1 -1
- package/lib/index.js +1959 -52
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/client/events.ts +11 -0
- package/src/client/index.ts +15 -2
- package/src/client/monaco/lsp/index.ts +30 -0
- package/src/client/monaco/lsp/lspClient.ts +192 -0
- package/src/client/monaco/lsp/providers.ts +229 -0
- package/src/client/outline/OutlinePanel.tsx +1 -1
- package/src/client/outline/lspSource.ts +49 -0
- package/src/client/outline/parse.ts +31 -1
- package/src/client/outline/types.ts +2 -0
- package/src/client/paths.ts +2 -0
- package/src/client/state/workspaceFoldCache.ts +95 -0
- package/src/client/styles/editor.css +15 -0
- package/src/client/styles/mcp.css +50 -12
- package/src/client/ui/EditorView.ts +55 -1
- package/src/client/ui/KeybindingsPanel.ts +8 -5
- package/src/client/ui/LspSettings.ts +274 -0
- package/src/client/ui/McpSettings.ts +112 -49
- package/src/client/ui/PerfSettings.ts +145 -34
- package/src/fileOpenSettings.ts +1 -0
- package/src/index.ts +20 -2
- package/src/lsp/client.ts +205 -0
- package/src/lsp/config.ts +107 -0
- package/src/lsp/extmgr.ts +208 -0
- package/src/lsp/jsonrpc.ts +86 -0
- package/src/lsp/manager.ts +133 -0
- package/src/lsp/providers.ts +235 -0
- package/src/lsp/rpc.ts +334 -0
- package/src/lsp/server.ts +346 -0
- package/src/lsp/settings.ts +44 -0
- package/src/lsp/transport.ts +143 -0
- package/src/lsp/uri.ts +62 -0
- package/src/lsp/zip.ts +82 -0
- package/src/rpc.ts +11 -2
- package/src/shared/lsp.ts +138 -0
- 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
|
-
/*
|
|
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:
|
|
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
|
|
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;
|
|
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('
|
|
182
|
-
|
|
183
|
-
React.createElement('
|
|
184
|
-
React.createElement('
|
|
185
|
-
|
|
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
|
}
|