dsh-vscode-mode 0.1.35 → 0.1.42
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/lib/client.js +1117 -156
- package/lib/client.js.map +1 -1
- package/lib/index.js +447 -62
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/monaco/loader.ts +12 -1
- package/src/client/monaco/lsp/lspClient.ts +65 -11
- package/src/client/monaco/lsp/providers.ts +34 -1
- package/src/client/monaco/theme.ts +229 -0
- package/src/client/paths.ts +2 -0
- package/src/client/state/workspaceFoldCache.ts +95 -0
- package/src/client/styles/mcp.css +35 -12
- package/src/client/ui/EditorView.ts +21 -2
- package/src/client/ui/KeybindingsPanel.ts +8 -5
- package/src/client/ui/LspSettings.ts +53 -15
- package/src/client/ui/McpSettings.ts +110 -49
- package/src/client/ui/PerfSettings.ts +145 -34
- package/src/lsp/client.ts +29 -2
- package/src/lsp/manager.ts +15 -0
- package/src/lsp/providers.ts +203 -24
- package/src/lsp/rpc.ts +86 -4
- package/src/lsp/server.ts +80 -3
- package/src/paths.ts +7 -0
- package/src/rpc.ts +4 -1
- package/src/shared/lsp.ts +24 -0
- package/src/shared/rpc.ts +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 dsh-better-sidebar 侧边栏与对话同屏)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 迁移自原 src/client/index.ts 的 MONACO_BASE/LANG_BY_EXT/langOf/loadMonaco,语义不改。
|
|
5
5
|
* 作者 ddj 2026-08-20
|
|
6
6
|
*/
|
|
7
|
+
import { applyTheme, registerThemes } from './theme.js'
|
|
7
8
|
|
|
8
9
|
export const MONACO_BASE = '/edrv/vendor/monaco/vs'
|
|
9
10
|
let monacoPromise = null
|
|
@@ -58,7 +59,12 @@ export const LANG_BY_EXT = {
|
|
|
58
59
|
py: 'python', sh: 'shell', bash: 'shell', zsh: 'shell', ps1: 'powershell',
|
|
59
60
|
lua: 'lua', java: 'java', c: 'c', h: 'c', cpp: 'cpp', cc: 'cpp', cxx: 'cpp', hpp: 'cpp',
|
|
60
61
|
cs: 'csharp', go: 'go', rs: 'rust', rb: 'ruby', php: 'php', sql: 'sql',
|
|
61
|
-
swift: 'swift', kt: 'kotlin', kts: 'kotlin', dart: 'dart', dockerfile: 'dockerfile'
|
|
62
|
+
swift: 'swift', kt: 'kotlin', kts: 'kotlin', dart: 'dart', dockerfile: 'dockerfile',
|
|
63
|
+
// 工程/配置文件(拿到正确语法分色,而非 plaintext)
|
|
64
|
+
csproj: 'xml', props: 'xml', targets: 'xml', plist: 'xml',
|
|
65
|
+
lua51: 'lua', luac: 'lua',
|
|
66
|
+
gitattributes: 'ini', editorconfig: 'ini', env: 'ini', properties: 'ini',
|
|
67
|
+
json5: 'jsonc', log: 'plaintext', txt: 'plaintext',
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
/**
|
|
@@ -96,6 +102,11 @@ export function loadMonaco(onProgress) {
|
|
|
96
102
|
publishStage('core', MONACO_STAGES.core.progress, MONACO_STAGES.core.message)
|
|
97
103
|
window.require(['vs/editor/editor.main'], () => {
|
|
98
104
|
publishStage('ready', MONACO_STAGES.ready.progress, MONACO_STAGES.ready.message)
|
|
105
|
+
// 分色主题注册 + 应用(rich token 配色,替掉内置基础 vs 的少层次着色)
|
|
106
|
+
try {
|
|
107
|
+
registerThemes(window.monaco)
|
|
108
|
+
applyTheme(window.monaco)
|
|
109
|
+
} catch (error) { /* 主题失败不阻塞编辑器 */ }
|
|
99
110
|
resolve(window.monaco)
|
|
100
111
|
}, (err) => fail(new Error('Monaco 模块加载失败:' + String(err))))
|
|
101
112
|
} catch (error) {
|
|
@@ -113,6 +113,14 @@ export async function fetchHover(path, text, position) {
|
|
|
113
113
|
return res.hover || null
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/** 查询当前文档的 semantic tokens(host 已归一化 legend 与 delta data)。 */
|
|
117
|
+
export async function fetchSemanticTokens(path, text) {
|
|
118
|
+
await syncDoc(path, text, true)
|
|
119
|
+
const res = await rpc('edrv.lsp.semanticTokens', { sessionId, path }).catch(() => null)
|
|
120
|
+
if (!res || !res.ok || !res.tokens || !Array.isArray(res.tokens.data)) return null
|
|
121
|
+
return res.tokens
|
|
122
|
+
}
|
|
123
|
+
|
|
116
124
|
/** 拉取服务器状态(带 5s 缓存)。 */
|
|
117
125
|
export async function refreshStatus(force = false) {
|
|
118
126
|
if (!force && Date.now() - statusCache.at < 1500) return statusCache.servers
|
|
@@ -148,6 +156,10 @@ function toClientLocation(loc) {
|
|
|
148
156
|
Math.max(1, (end.line ?? 0) + 1),
|
|
149
157
|
Math.max(1, (end.character ?? 0) + 1),
|
|
150
158
|
),
|
|
159
|
+
// 保留原始 LSP uri/root:后续跳转不能把 Monaco edrv Uri 当作磁盘路径
|
|
160
|
+
lspUri: loc.uri,
|
|
161
|
+
lspRoot: loc.root,
|
|
162
|
+
lspRange: loc.range,
|
|
151
163
|
}
|
|
152
164
|
}
|
|
153
165
|
|
|
@@ -161,32 +173,74 @@ export function toEdrvUri(uri, root) {
|
|
|
161
173
|
|
|
162
174
|
/** 将 LSP 目标解析为当前工作区相对路径(跨文件跳转与 model URI 对齐)。 */
|
|
163
175
|
export function targetOpenPath(loc) {
|
|
164
|
-
return relativeLspPath(loc?.uri, loc?.root)
|
|
176
|
+
return relativeLspPath(loc?.lspUri ?? loc?.uri, loc?.lspRoot ?? loc?.root)
|
|
165
177
|
}
|
|
166
178
|
|
|
167
|
-
/**
|
|
168
|
-
|
|
179
|
+
/**
|
|
180
|
+
* 归一化 Location 的 uri → 工作区路径(剥离 scheme,不补盘符)。
|
|
181
|
+
* 三种输入形态(缺一即导致 scheme 泄漏进文件路径 → 跳转"文件不存在"):
|
|
182
|
+
* - Monaco Uri 对象(toClientLocation 产物,scheme=edrv)→ 取 .path(已是工作区相对路径)
|
|
183
|
+
* - 'edrv:///x' 字符串 → 取路径部分
|
|
184
|
+
* - 'file:///D:/x' → 去 authority 空段保留盘符
|
|
185
|
+
* @author ddj 2026年08月28号
|
|
186
|
+
* @param uri Monaco Uri 对象 / file:// 或 edrv:// URI / 裸路径
|
|
187
|
+
* @returns / 分隔的路径(edrv 相对路径 或 带盘符绝对路径)
|
|
188
|
+
*/
|
|
189
|
+
export function lspUriToAbs(uri) {
|
|
169
190
|
if (!uri) return ''
|
|
170
|
-
|
|
191
|
+
const isObject = typeof uri === 'object'
|
|
192
|
+
// Monaco Uri 对象:优先取其 path(已剥离 scheme)
|
|
193
|
+
const raw = isObject && typeof uri.path === 'string' ? uri.path : String(uri)
|
|
194
|
+
let path = raw
|
|
171
195
|
if (path.startsWith('file://')) {
|
|
172
|
-
try { path = decodeURIComponent(path.replace(/^file
|
|
196
|
+
try { path = decodeURIComponent(path.replace(/^file:\/\/\/?/, '')) } catch (error) { path = path.replace(/^file:\/\/\/?/, '') }
|
|
197
|
+
// file:///D:/x → D:/x(去 authority 空段保留盘符);/home/x 保留前导 /(Unix 绝对路径)
|
|
198
|
+
return path.replace(/\\/g, '/').replace(/^\/([A-Za-z]:)/, '$1')
|
|
199
|
+
}
|
|
200
|
+
if (path.startsWith('edrv://')) {
|
|
201
|
+
try { path = decodeURIComponent(path.replace(/^edrv:\/\/\/?/, '')) } catch (error) { path = path.replace(/^edrv:\/\/\/?/, '') }
|
|
173
202
|
}
|
|
174
|
-
|
|
203
|
+
// edrv Uri(对象或字符串)→ 工作区相对路径:必须剥前导 /,否则 host 按绝对路径解析丢 cwd
|
|
204
|
+
if (isObject ? uri.scheme === 'edrv' : raw.startsWith('edrv://')) {
|
|
205
|
+
return path.replace(/\\/g, '/').replace(/^\/+/, '')
|
|
206
|
+
}
|
|
207
|
+
return path.replace(/\\/g, '/').replace(/^\/([A-Za-z]:)/, '$1')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* file URI/裸路径 → root 下的 / 分隔相对路径。
|
|
212
|
+
* root 匹配返回工作区相对路径;root 不匹配返回完整绝对路径(保留盘符),
|
|
213
|
+
* 供 edrv.read 按绝对路径解析(原先错误剥掉首个 / 导致跳转"文件不存在")。
|
|
214
|
+
* @author ddj 2026年08月28号
|
|
215
|
+
* @param uri file:// URI 或裸路径
|
|
216
|
+
* @param root 工作区根(可选)
|
|
217
|
+
* @returns 可 edrv.read 的路径
|
|
218
|
+
*/
|
|
219
|
+
function relativeLspPath(uri, root) {
|
|
220
|
+
if (!uri) return ''
|
|
221
|
+
const path = lspUriToAbs(uri)
|
|
175
222
|
const normalizedRoot = root ? String(root).replace(/\\/g, '/').replace(/^\/([A-Za-z]:)/, '$1').replace(/\/+$/, '') : ''
|
|
176
223
|
const lowerPath = path.toLowerCase()
|
|
177
224
|
const lowerRoot = normalizedRoot.toLowerCase()
|
|
178
225
|
if (normalizedRoot && (lowerPath === lowerRoot || lowerPath.startsWith(lowerRoot + '/'))) {
|
|
179
226
|
return path.slice(normalizedRoot.length).replace(/^\/+/, '')
|
|
180
227
|
}
|
|
181
|
-
return path
|
|
228
|
+
return path
|
|
182
229
|
}
|
|
183
230
|
|
|
184
231
|
/** 目标行/列(0-based LSP → 1-based Monaco)。 */
|
|
185
232
|
export function targetMonoPosition(loc) {
|
|
186
|
-
|
|
187
|
-
const
|
|
233
|
+
// 优先使用原始 LSP range(0-based);无原始值时兼容 Monaco Range(1-based)
|
|
234
|
+
const lspRange = loc?.lspRange
|
|
235
|
+
if (lspRange?.start) {
|
|
236
|
+
return {
|
|
237
|
+
line: Math.max(1, (lspRange.start.line ?? 0) + 1),
|
|
238
|
+
column: Math.max(1, (lspRange.start.character ?? 0) + 1),
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const range = loc?.range ?? {}
|
|
188
242
|
return {
|
|
189
|
-
line: Math.max(1,
|
|
190
|
-
column: Math.max(1,
|
|
243
|
+
line: Math.max(1, range.startLineNumber ?? 1),
|
|
244
|
+
column: Math.max(1, range.startColumn ?? 1),
|
|
191
245
|
}
|
|
192
246
|
}
|
|
@@ -8,10 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import {
|
|
10
10
|
pathOfModel, syncDoc, closeDoc, findDefinition, findReferences,
|
|
11
|
-
fetchDocumentSymbols, fetchHover,
|
|
11
|
+
fetchDocumentSymbols, fetchHover, fetchSemanticTokens,
|
|
12
|
+
targetOpenPath, targetMonoPosition, lspStatusFor, refreshStatus,
|
|
12
13
|
} from './lspClient.js'
|
|
14
|
+
import { LSP_SEMANTIC_TOKEN_MODIFIERS, LSP_SEMANTIC_TOKEN_TYPES } from '../../../shared/lsp.js'
|
|
13
15
|
|
|
14
16
|
const LSP_LANGS = ['lua', 'csharp']
|
|
17
|
+
const SEMANTIC_LEGEND = {
|
|
18
|
+
tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
19
|
+
tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS],
|
|
20
|
+
}
|
|
15
21
|
let registered = false
|
|
16
22
|
const disposables = []
|
|
17
23
|
let overlayEl = null
|
|
@@ -48,6 +54,22 @@ export function registerLspProviders(monaco) {
|
|
|
48
54
|
disposables.push(monaco.editor.onDidCreateModel(attachModel))
|
|
49
55
|
for (const model of monaco.editor.getModels()) attachModel(model)
|
|
50
56
|
|
|
57
|
+
// —— 重新检测事件:保存配置/切换启用/手动重检测后,把已打开模型重新同步给 host ——
|
|
58
|
+
// (host 已重置该语言 server,下一次 sync 会按最新配置重新 acquire,无需重开文件)
|
|
59
|
+
const onRedetect = (event) => {
|
|
60
|
+
const languageId = event?.detail?.languageId
|
|
61
|
+
if (!languageId) return
|
|
62
|
+
for (const model of monaco.editor.getModels()) {
|
|
63
|
+
const path = pathOfModel(model)
|
|
64
|
+
if (!path) continue
|
|
65
|
+
if (model.getLanguageId && model.getLanguageId() !== languageId) continue
|
|
66
|
+
void syncDoc(path, model.getValue(), true)
|
|
67
|
+
}
|
|
68
|
+
void refreshStatus(true)
|
|
69
|
+
}
|
|
70
|
+
window.addEventListener('edrv:lsp-redetect', onRedetect)
|
|
71
|
+
disposables.push(() => window.removeEventListener('edrv:lsp-redetect', onRedetect))
|
|
72
|
+
|
|
51
73
|
// —— 数据 provider ——
|
|
52
74
|
disposables.push(
|
|
53
75
|
monaco.languages.registerDefinitionProvider(LSP_LANGS, {
|
|
@@ -87,6 +109,17 @@ export function registerLspProviders(monaco) {
|
|
|
87
109
|
}
|
|
88
110
|
},
|
|
89
111
|
}),
|
|
112
|
+
monaco.languages.registerDocumentSemanticTokensProvider(LSP_LANGS, {
|
|
113
|
+
getLegend: () => SEMANTIC_LEGEND,
|
|
114
|
+
provideDocumentSemanticTokens: async (model, token) => {
|
|
115
|
+
const path = pathOfModel(model)
|
|
116
|
+
if (!path || token?.isCancellationRequested) return { data: new Uint32Array() }
|
|
117
|
+
const result = await fetchSemanticTokens(path, model.getValue())
|
|
118
|
+
if (!result || token?.isCancellationRequested) return { data: new Uint32Array() }
|
|
119
|
+
return { data: Uint32Array.from(result.data) }
|
|
120
|
+
},
|
|
121
|
+
releaseDocumentSemanticTokens: () => {},
|
|
122
|
+
}),
|
|
90
123
|
)
|
|
91
124
|
}
|
|
92
125
|
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — Monaco 语法分色主题(rich token 配色)。
|
|
4
|
+
* 编辑器原先硬编码 'vs'(内置基础主题),token 分色层次少;
|
|
5
|
+
* 这里定义 edrv-dark / edrv-light 两套完整 token 配色(对齐 VSCode Dark+ 语义分层),
|
|
6
|
+
* 并随 DSH 明暗主题自动切换(跟随文档根 data 主题属性 / prefers-color-scheme)。
|
|
7
|
+
* 作者 ddj 2026-08-28
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const EDRV_DARK = 'edrv-dark'
|
|
11
|
+
export const EDRV_LIGHT = 'edrv-light'
|
|
12
|
+
export const EDRV_FALLBACK = 'vs'
|
|
13
|
+
|
|
14
|
+
/** 语法分色(语义分层:关键字/字符串/数字/注释/函数/变量/类型/运算符/正则/标签/属性…)。 */
|
|
15
|
+
const DARK_RULES = [
|
|
16
|
+
{ token: '', foreground: 'd4d4d4', background: '1e1e1e' },
|
|
17
|
+
{ token: 'comment', foreground: '6a9955', fontStyle: 'italic' },
|
|
18
|
+
{ token: 'comment.doc', foreground: '7ca97c', fontStyle: 'italic' },
|
|
19
|
+
{ token: 'keyword', foreground: 'c586c0' },
|
|
20
|
+
{ token: 'keyword.control', foreground: 'c586c0' },
|
|
21
|
+
{ token: 'keyword.operator', foreground: 'd4d4d4' },
|
|
22
|
+
{ token: 'keyword.json', foreground: '9cdcfe' },
|
|
23
|
+
{ token: 'string', foreground: 'ce9178' },
|
|
24
|
+
{ token: 'string.escape', foreground: 'd7ba7d' },
|
|
25
|
+
{ token: 'string.key.json', foreground: '9cdcfe' },
|
|
26
|
+
{ token: 'string.value.json', foreground: 'ce9178' },
|
|
27
|
+
{ token: 'string.yaml', foreground: 'ce9178' },
|
|
28
|
+
{ token: 'number', foreground: 'b5cea8' },
|
|
29
|
+
{ token: 'number.hex', foreground: 'b5cea8' },
|
|
30
|
+
{ token: 'regexp', foreground: 'd16969' },
|
|
31
|
+
{ token: 'type', foreground: '4ec9b0' },
|
|
32
|
+
{ token: 'type.identifier', foreground: '4ec9b0' },
|
|
33
|
+
{ token: 'identifier', foreground: 'd4d4d4' },
|
|
34
|
+
{ token: 'variable', foreground: '9cdcfe' },
|
|
35
|
+
{ token: 'variable.global', foreground: 'ff6699' },
|
|
36
|
+
{ token: 'variable.local', foreground: '9cdcfe' },
|
|
37
|
+
{ token: 'variable.mutable', foreground: '9cdcfe' },
|
|
38
|
+
{ token: 'variable.predefined', foreground: '4fc1ff' },
|
|
39
|
+
{ token: 'variable.parameter', foreground: '00bfae' },
|
|
40
|
+
{ token: 'parameter', foreground: '00bfae' },
|
|
41
|
+
{ token: 'function', foreground: 'dcdcaa' },
|
|
42
|
+
{ token: 'method', foreground: 'dcdcaa' },
|
|
43
|
+
{ token: 'property', foreground: '9cdcfe' },
|
|
44
|
+
{ token: 'function.identifier', foreground: 'dcdcaa' },
|
|
45
|
+
{ token: 'member', foreground: 'dcdcaa' },
|
|
46
|
+
{ token: 'constant', foreground: '4fc1ff' },
|
|
47
|
+
{ token: 'constant.numeric', foreground: 'b5cea8' },
|
|
48
|
+
{ token: 'constant.language', foreground: '569cd6' },
|
|
49
|
+
{ token: 'tag', foreground: '569cd6' },
|
|
50
|
+
{ token: 'tag.xml', foreground: '569cd6' },
|
|
51
|
+
{ token: 'metatag', foreground: '808080' },
|
|
52
|
+
{ token: 'attribute.name', foreground: '9cdcfe' },
|
|
53
|
+
{ token: 'attribute.value', foreground: 'ce9178' },
|
|
54
|
+
{ token: 'delimiter', foreground: 'd4d4d4' },
|
|
55
|
+
{ token: 'delimiter.bracket', foreground: 'ffd700' },
|
|
56
|
+
{ token: 'delimiter.html', foreground: '808080' },
|
|
57
|
+
{ token: 'operator', foreground: 'd4d4d4' },
|
|
58
|
+
{ token: 'namespace', foreground: '4ec9b0' },
|
|
59
|
+
{ token: 'class', foreground: '4ec9b0' },
|
|
60
|
+
{ token: 'interface', foreground: '4ec9b0' },
|
|
61
|
+
{ token: 'enum', foreground: '4ec9b0' },
|
|
62
|
+
{ token: 'struct', foreground: '4ec9b0' },
|
|
63
|
+
{ token: 'annotation', foreground: 'dcdcaa' },
|
|
64
|
+
{ token: 'punctuation', foreground: 'd4d4d4' },
|
|
65
|
+
{ token: 'invalid', foreground: 'f44747' },
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
const LIGHT_RULES = [
|
|
69
|
+
{ token: '', foreground: '1e1e1e', background: 'ffffff' },
|
|
70
|
+
{ token: 'comment', foreground: '008000', fontStyle: 'italic' },
|
|
71
|
+
{ token: 'comment.doc', foreground: '2e7d32', fontStyle: 'italic' },
|
|
72
|
+
{ token: 'keyword', foreground: '0000ff' },
|
|
73
|
+
{ token: 'keyword.control', foreground: 'af00db' },
|
|
74
|
+
{ token: 'keyword.operator', foreground: '1e1e1e' },
|
|
75
|
+
{ token: 'keyword.json', foreground: '0451a5' },
|
|
76
|
+
{ token: 'string', foreground: 'a31515' },
|
|
77
|
+
{ token: 'string.escape', foreground: 'ee0000' },
|
|
78
|
+
{ token: 'string.key.json', foreground: '0451a5' },
|
|
79
|
+
{ token: 'string.value.json', foreground: 'a31515' },
|
|
80
|
+
{ token: 'string.yaml', foreground: 'a31515' },
|
|
81
|
+
{ token: 'number', foreground: '098658' },
|
|
82
|
+
{ token: 'number.hex', foreground: '098658' },
|
|
83
|
+
{ token: 'regexp', foreground: '811f3f' },
|
|
84
|
+
{ token: 'type', foreground: '267f99' },
|
|
85
|
+
{ token: 'type.identifier', foreground: '267f99' },
|
|
86
|
+
{ token: 'identifier', foreground: '1e1e1e' },
|
|
87
|
+
{ token: 'variable', foreground: '001080' },
|
|
88
|
+
{ token: 'variable.global', foreground: 'c2185b' },
|
|
89
|
+
{ token: 'variable.local', foreground: '001080' },
|
|
90
|
+
{ token: 'variable.mutable', foreground: '001080' },
|
|
91
|
+
{ token: 'variable.predefined', foreground: '001080' },
|
|
92
|
+
{ token: 'variable.parameter', foreground: '008577' },
|
|
93
|
+
{ token: 'parameter', foreground: '008577' },
|
|
94
|
+
{ token: 'function', foreground: '795e26' },
|
|
95
|
+
{ token: 'method', foreground: '795e26' },
|
|
96
|
+
{ token: 'function.identifier', foreground: '795e26' },
|
|
97
|
+
{ token: 'member', foreground: '795e26' },
|
|
98
|
+
{ token: 'property', foreground: '001080' },
|
|
99
|
+
{ token: 'constant', foreground: '0070c1' },
|
|
100
|
+
{ token: 'constant.numeric', foreground: '098658' },
|
|
101
|
+
{ token: 'constant.language', foreground: '0000ff' },
|
|
102
|
+
{ token: 'tag', foreground: '800000' },
|
|
103
|
+
{ token: 'tag.xml', foreground: '800000' },
|
|
104
|
+
{ token: 'metatag', foreground: '6b6b6b' },
|
|
105
|
+
{ token: 'attribute.name', foreground: 'e50000' },
|
|
106
|
+
{ token: 'attribute.value', foreground: '0451a5' },
|
|
107
|
+
{ token: 'delimiter', foreground: '1e1e1e' },
|
|
108
|
+
{ token: 'delimiter.bracket', foreground: 'b46400' },
|
|
109
|
+
{ token: 'delimiter.html', foreground: '800000' },
|
|
110
|
+
{ token: 'operator', foreground: '1e1e1e' },
|
|
111
|
+
{ token: 'namespace', foreground: '267f99' },
|
|
112
|
+
{ token: 'class', foreground: '267f99' },
|
|
113
|
+
{ token: 'interface', foreground: '267f99' },
|
|
114
|
+
{ token: 'enum', foreground: '267f99' },
|
|
115
|
+
{ token: 'struct', foreground: '267f99' },
|
|
116
|
+
{ token: 'annotation', foreground: '795e26' },
|
|
117
|
+
{ token: 'punctuation', foreground: '1e1e1e' },
|
|
118
|
+
{ token: 'invalid', foreground: 'cd3131' },
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
/** 编辑器 UI 配色(与语法配色同套,保证 gutter/minimap/selection 一致)。 */
|
|
122
|
+
const DARK_COLORS = {
|
|
123
|
+
'editor.background': '#1e1e1e',
|
|
124
|
+
'editor.foreground': '#d4d4d4',
|
|
125
|
+
'editorLineNumber.foreground': '#6e7681',
|
|
126
|
+
'editorLineNumber.activeForeground': '#c6c6c6',
|
|
127
|
+
'editor.selectionBackground': '#264f78',
|
|
128
|
+
'editor.inactiveSelectionBackground': '#3a3d41',
|
|
129
|
+
'editor.lineHighlightBackground': '#2a2d2e',
|
|
130
|
+
'editorCursor.foreground': '#aeafad',
|
|
131
|
+
'editorIndentGuide.background1': '#404040',
|
|
132
|
+
'editorIndentGuide.activeBackground1': '#707070',
|
|
133
|
+
'editorWidget.background': '#252526',
|
|
134
|
+
'editorHoverWidget.background': '#252526',
|
|
135
|
+
'editorGutter.background': '#1e1e1e',
|
|
136
|
+
'minimap.background': '#1e1e1e',
|
|
137
|
+
'scrollbarSlider.background': '#79797966',
|
|
138
|
+
'scrollbarSlider.hoverBackground': '#646464b3',
|
|
139
|
+
'scrollbarSlider.activeBackground': '#bfbfbf66',
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const LIGHT_COLORS = {
|
|
143
|
+
'editor.background': '#ffffff',
|
|
144
|
+
'editor.foreground': '#1e1e1e',
|
|
145
|
+
'editorLineNumber.foreground': '#6c6c6c',
|
|
146
|
+
'editorLineNumber.activeForeground': '#171184',
|
|
147
|
+
'editor.selectionBackground': '#add6ff',
|
|
148
|
+
'editor.inactiveSelectionBackground': '#e5ebf1',
|
|
149
|
+
'editor.lineHighlightBackground': '#f0f0f0',
|
|
150
|
+
'editorCursor.foreground': '#000000',
|
|
151
|
+
'editorIndentGuide.background1': '#d3d3d3',
|
|
152
|
+
'editorIndentGuide.activeBackground1': '#939393',
|
|
153
|
+
'editorWidget.background': '#f3f3f3',
|
|
154
|
+
'editorHoverWidget.background': '#f3f3f3',
|
|
155
|
+
'editorGutter.background': '#ffffff',
|
|
156
|
+
'minimap.background': '#ffffff',
|
|
157
|
+
'scrollbarSlider.background': '#64646466',
|
|
158
|
+
'scrollbarSlider.hoverBackground': '#646464b3',
|
|
159
|
+
'scrollbarSlider.activeBackground': '#00000099',
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 注册 edrv 双套主题(幂等;Monaco 未加载时静默跳过)。
|
|
164
|
+
* @author ddj 2026年08月28号
|
|
165
|
+
* @param monaco Monaco 实例(window.monaco)
|
|
166
|
+
*/
|
|
167
|
+
export function registerThemes(monaco) {
|
|
168
|
+
if (!monaco?.editor?.defineTheme) return
|
|
169
|
+
try {
|
|
170
|
+
monaco.editor.defineTheme(EDRV_DARK, {
|
|
171
|
+
base: 'vs-dark',
|
|
172
|
+
inherit: true,
|
|
173
|
+
rules: DARK_RULES,
|
|
174
|
+
colors: DARK_COLORS,
|
|
175
|
+
})
|
|
176
|
+
monaco.editor.defineTheme(EDRV_LIGHT, {
|
|
177
|
+
base: 'vs',
|
|
178
|
+
inherit: true,
|
|
179
|
+
rules: LIGHT_RULES,
|
|
180
|
+
colors: LIGHT_COLORS,
|
|
181
|
+
})
|
|
182
|
+
} catch (error) {
|
|
183
|
+
/* 主题注册失败保留内置 vs */
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 探测当前 DSH 明暗(文档根 data 主题属性 → prefers-color-scheme → 暗)。
|
|
189
|
+
* @author ddj 2026年08月28号
|
|
190
|
+
* @returns 'dark' | 'light'
|
|
191
|
+
*/
|
|
192
|
+
export function detectColorScheme() {
|
|
193
|
+
try {
|
|
194
|
+
const attr = document.documentElement?.getAttribute?.('data-theme')
|
|
195
|
+
?? document.body?.getAttribute?.('data-theme')
|
|
196
|
+
const value = String(attr ?? '').toLowerCase()
|
|
197
|
+
if (value.includes('light') || value.includes('catppuccin-latte')) return 'light'
|
|
198
|
+
if (value.includes('dark') || value.includes('frappe') || value.includes('macchiato') || value.includes('mocha')) return 'dark'
|
|
199
|
+
if (typeof window.matchMedia === 'function') {
|
|
200
|
+
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
/* 探测失败回落暗色 */
|
|
204
|
+
}
|
|
205
|
+
return 'dark'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 主题名:按当前明暗返回已注册主题(未注册回落 vs)。
|
|
210
|
+
* @author ddj 2026年08月28号
|
|
211
|
+
* @returns 主题 id
|
|
212
|
+
*/
|
|
213
|
+
export function themeNameOf() {
|
|
214
|
+
return detectColorScheme() === 'light' ? EDRV_LIGHT : EDRV_DARK
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 应用主题到 Monaco(全局切换;新建编辑器也用该主题)。
|
|
219
|
+
* @author ddj 2026年08月28号
|
|
220
|
+
* @param monaco Monaco 实例
|
|
221
|
+
*/
|
|
222
|
+
export function applyTheme(monaco) {
|
|
223
|
+
if (!monaco?.editor?.setTheme) return
|
|
224
|
+
try {
|
|
225
|
+
monaco.editor.setTheme(themeNameOf())
|
|
226
|
+
} catch (error) {
|
|
227
|
+
/* setTheme 失败忽略 */
|
|
228
|
+
}
|
|
229
|
+
}
|
package/src/client/paths.ts
CHANGED
|
@@ -22,6 +22,8 @@ export const CACHE_KEY = {
|
|
|
22
22
|
search: 'edrv.search.v1.',
|
|
23
23
|
/** 侧边栏提示已关闭标记(全局,无会话)。 */
|
|
24
24
|
sideHint: 'edrv.side-hint-dismissed',
|
|
25
|
+
/** 「性能优化」页工作区栏目展开状态(workspaceFoldCache v1,全局不按会话)。 */
|
|
26
|
+
workspaceFold: 'edrv.ws-fold.v1.',
|
|
25
27
|
} as const
|
|
26
28
|
|
|
27
29
|
/** 按前缀拼会话键(侧边栏/编辑器等需要布局段时自行拼接后传入)。 */
|
|
@@ -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
|
+
}
|
|
@@ -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)}
|
|
@@ -139,6 +156,7 @@
|
|
|
139
156
|
|
|
140
157
|
/* 会话性能(PerfSettings 子 Tab) */
|
|
141
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}
|
|
142
160
|
.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
143
161
|
.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}
|
|
144
162
|
.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}
|
|
@@ -148,8 +166,19 @@
|
|
|
148
166
|
.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}
|
|
149
167
|
.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
150
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}
|
|
151
174
|
.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}
|
|
152
|
-
.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}
|
|
153
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}
|
|
154
183
|
.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}
|
|
155
184
|
.vsm-perf-rows{display:flex;flex-direction:column}
|
|
@@ -163,15 +192,9 @@
|
|
|
163
192
|
.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}
|
|
164
193
|
.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}
|
|
165
194
|
.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}
|
|
166
|
-
.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)}
|
|
167
|
-
.vsm-perf-moved h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
168
|
-
.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)}
|
|
169
|
-
.vsm-perf-config h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
170
195
|
.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}
|
|
171
196
|
.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}
|
|
172
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%}
|
|
173
|
-
.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)}
|
|
174
|
-
.vsm-perf-guide h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
175
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}
|
|
176
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)}
|
|
177
200
|
.vsm-perf-modal-card h3{margin:0;font-size:16px}
|