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
package/lib/client.js CHANGED
@@ -30,7 +30,7 @@ window.__ModuleLoader__.load({
30
30
  react = __toESM(react, 1);
31
31
  let react_dom = require("react-dom");
32
32
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
33
- const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):\n 在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }\n\n[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-tab .edrv-tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-brand-primary, #4f8cff); flex-shrink: 0; }\n[data-edrv-view] .edrv-tab .edrv-tab-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 4px; color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: rgba(255,255,255,.12); color: var(--dsw-alias-label-primary, #eee); }\n[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; 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; }\n[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }\n[data-edrv-view] .edrv-search { width: 190px; height: 24px; 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; }\n[data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }\n[data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }\n[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }\n[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }\n[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: #fff; font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[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; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[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; }\n[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; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-mn-add-line { background: rgba(15,157,88,.10); }\n[data-edrv-view] .edrv-mn-gutter-add { color: #0f9d58 !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-mn-gutter-add::before { content: \"+\"; }\n[data-edrv-view] .edrv-del-zone { background: #fdeaea; border-top: 1px solid rgba(217,83,79,.35); border-bottom: 1px solid rgba(217,83,79,.35); box-shadow: inset 0 0 0 1px rgba(217,83,79,.12); }\n[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: #c0392b; }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }\n[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }\n[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }\n[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }\n[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }\n[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }\n[data-edrv-view] .edrv-pill-keep { background: #e8f8e8; color: #0f7a45; border: 1px solid rgba(15,157,88,.4); }\n[data-edrv-view] .edrv-pill-undo { background: #fdeaea; color: #c0392b; border: 1px solid rgba(217,83,79,.4); }\n[data-edrv-view] .edrv-pill-ghost { background: #ffffff; color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 编辑区 / Tab 右键菜单(添加文件/选中内容到对话) */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 200px; max-width: 320px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-item:disabled,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled { color: var(--dsw-alias-label-disabled, #b0b7c0); cursor: default; background: transparent; }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: rgba(217,119,6,.5); cursor: default; }\n[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }\n[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n/* 编辑区 hover 差异块的 Keep/Undo 浮层 */\n[data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }\n[data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }\n[data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }\n[data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }\n[data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }\n[data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }\n[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }\n[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }\n[data-edrv-view] .monaco-editor { overflow: hidden !important; }\n[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }\n[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }\n[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }\n[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }\n[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }\n[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-progress-value { transition: none; }\n}\n\n/* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */\n[data-edrv-diff-dock-shell] {\n box-sizing: border-box;\n width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n margin: 0 auto;\n padding: 0;\n flex: none;\n border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);\n border-radius: 12px;\n background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));\n box-shadow: none;\n overflow: hidden;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock {\n box-sizing: border-box;\n position: relative;\n left: auto;\n right: auto;\n bottom: auto;\n z-index: auto;\n width: 100%;\n max-width: none;\n margin: 0;\n display: flex;\n flex-direction: column;\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: visible;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-nav,\n[data-edrv-diff-dock-shell] .edrv-diffbar-files,\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }\n[data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }\n@media (max-width: 620px) {\n [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }\n [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }\n}\n\n/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\n[data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col > .edrv-pathbar,\n[data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }\n[data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 0; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }\n[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }\n[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; }\n[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-context { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); }\n[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }\n[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* 大纲面板:kind 字形 / detail / 行号 */\n[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== 侧栏形态(betterSidebar 面板内编辑区) ===== */\n[data-edrv-view][data-edrv-layout='side'] { box-sizing: border-box; height: 100%; }\n\n/* ===== 侧边栏引导条(旧页签形态、未装 betterSidebar 时) ===== */\n[data-edrv-view] .edrv-side-hint { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; padding: 6px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-side-hint-text { white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-cmd { font-size: 11px; padding: 2px 6px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #ffffff); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-close { margin-left: auto; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-hint-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== search panel (sidebar, VSCode search view) ===== */\n[data-edrv-view] .edrv-search-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-search-qrow { display: flex; gap: 4px; }\n[data-edrv-view] .edrv-search-input { flex: 1; min-width: 0; 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; }\n[data-edrv-view] .edrv-search-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-opts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-search-opt { min-width: 24px; height: 22px; padding: 0 6px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); cursor: pointer; }\n[data-edrv-view] .edrv-search-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-opt.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-border-l2, #d5dde3); }\n[data-edrv-view] .edrv-search-spacer { flex: 1; }\n[data-edrv-view] .edrv-search-summary { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 2px 4px; }\n[data-edrv-view] .edrv-search-results { flex: 1; min-height: 0; overflow: auto; font-size: 12px; }\n[data-edrv-view] .edrv-search-group { margin-bottom: 2px; }\n[data-edrv-view] .edrv-search-file { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-search-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-search-fname { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-search-fdir { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-search-fcount { flex-shrink: 0; min-width: 16px; height: 15px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 15px; text-align: center; }\n[data-edrv-view] .edrv-search-line { display: flex; gap: 6px; padding: 1px 4px 1px 16px; border-radius: 4px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-search-line.alt { background: var(--dsw-alias-bg-layer-1, #ffffff); }\n[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }\n[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: #1f2933; border-radius: 2px; padding: 0 1px; }\n[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid rgba(240,173,78,.45); border-radius: 6px; background: #fdf6e3; color: #b8860b; font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; 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: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[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); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n";
33
+ const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):\n 在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }\n\n[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-tab .edrv-tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-brand-primary, #4f8cff); flex-shrink: 0; }\n[data-edrv-view] .edrv-tab .edrv-tab-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 4px; color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: rgba(255,255,255,.12); color: var(--dsw-alias-label-primary, #eee); }\n[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; 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; }\n[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }\n[data-edrv-view] .edrv-search { width: 190px; height: 24px; 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; }\n[data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }\n[data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }\n[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }\n[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }\n[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: #fff; font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[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; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[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); }\n[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); }\n[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); }\n[data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }\n[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; }\n@keyframes edrv-lsp-pulse { 50% { opacity: .45; } }\n[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; }\n[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; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-mn-add-line { background: rgba(15,157,88,.10); }\n[data-edrv-view] .edrv-mn-gutter-add { color: #0f9d58 !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-mn-gutter-add::before { content: \"+\"; }\n[data-edrv-view] .edrv-del-zone { background: #fdeaea; border-top: 1px solid rgba(217,83,79,.35); border-bottom: 1px solid rgba(217,83,79,.35); box-shadow: inset 0 0 0 1px rgba(217,83,79,.12); }\n[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: #c0392b; }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }\n[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }\n[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }\n[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }\n[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }\n[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }\n[data-edrv-view] .edrv-pill-keep { background: #e8f8e8; color: #0f7a45; border: 1px solid rgba(15,157,88,.4); }\n[data-edrv-view] .edrv-pill-undo { background: #fdeaea; color: #c0392b; border: 1px solid rgba(217,83,79,.4); }\n[data-edrv-view] .edrv-pill-ghost { background: #ffffff; color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 编辑区 / Tab 右键菜单(添加文件/选中内容到对话) */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 200px; max-width: 320px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-item:disabled,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled { color: var(--dsw-alias-label-disabled, #b0b7c0); cursor: default; background: transparent; }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: rgba(217,119,6,.5); cursor: default; }\n[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }\n[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n/* 编辑区 hover 差异块的 Keep/Undo 浮层 */\n[data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }\n[data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }\n[data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }\n[data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }\n[data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }\n[data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }\n[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }\n[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }\n[data-edrv-view] .monaco-editor { overflow: hidden !important; }\n[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }\n[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }\n[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }\n[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }\n[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }\n[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-progress-value { transition: none; }\n}\n\n/* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */\n[data-edrv-diff-dock-shell] {\n box-sizing: border-box;\n width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n margin: 0 auto;\n padding: 0;\n flex: none;\n border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);\n border-radius: 12px;\n background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));\n box-shadow: none;\n overflow: hidden;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock {\n box-sizing: border-box;\n position: relative;\n left: auto;\n right: auto;\n bottom: auto;\n z-index: auto;\n width: 100%;\n max-width: none;\n margin: 0;\n display: flex;\n flex-direction: column;\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: visible;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-nav,\n[data-edrv-diff-dock-shell] .edrv-diffbar-files,\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }\n[data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }\n@media (max-width: 620px) {\n [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }\n [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }\n}\n\n/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\n[data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col > .edrv-pathbar,\n[data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }\n[data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 0; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }\n[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }\n[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; }\n[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-context { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); }\n[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }\n[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* 大纲面板:kind 字形 / detail / 行号 */\n[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== 侧栏形态(betterSidebar 面板内编辑区) ===== */\n[data-edrv-view][data-edrv-layout='side'] { box-sizing: border-box; height: 100%; }\n\n/* ===== 侧边栏引导条(旧页签形态、未装 betterSidebar 时) ===== */\n[data-edrv-view] .edrv-side-hint { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; padding: 6px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-side-hint-text { white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-cmd { font-size: 11px; padding: 2px 6px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #ffffff); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-close { margin-left: auto; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-hint-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== search panel (sidebar, VSCode search view) ===== */\n[data-edrv-view] .edrv-search-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-search-qrow { display: flex; gap: 4px; }\n[data-edrv-view] .edrv-search-input { flex: 1; min-width: 0; 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; }\n[data-edrv-view] .edrv-search-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-opts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-search-opt { min-width: 24px; height: 22px; padding: 0 6px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); cursor: pointer; }\n[data-edrv-view] .edrv-search-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-opt.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-border-l2, #d5dde3); }\n[data-edrv-view] .edrv-search-spacer { flex: 1; }\n[data-edrv-view] .edrv-search-summary { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 2px 4px; }\n[data-edrv-view] .edrv-search-results { flex: 1; min-height: 0; overflow: auto; font-size: 12px; }\n[data-edrv-view] .edrv-search-group { margin-bottom: 2px; }\n[data-edrv-view] .edrv-search-file { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-search-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-search-fname { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-search-fdir { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-search-fcount { flex-shrink: 0; min-width: 16px; height: 15px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 15px; text-align: center; }\n[data-edrv-view] .edrv-search-line { display: flex; gap: 6px; padding: 1px 4px 1px 16px; border-radius: 4px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-search-line.alt { background: var(--dsw-alias-bg-layer-1, #ffffff); }\n[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }\n[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: #1f2933; border-radius: 2px; padding: 0 1px; }\n[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid rgba(240,173,78,.45); border-radius: 6px; background: #fdf6e3; color: #b8860b; font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; 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: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[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); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n/* LSP 引用结果浮动面板(Shift+F12 / 右键「查找所有引用」) */\n[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; }\n[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); }\n[data-edrv-view] .edrv-refs-list { overflow-y: auto; }\n[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); }\n[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); }\n\n\n";
34
34
  const tagId$1 = "dsh-vscode-mode/editor.css";
35
35
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
36
36
  const tag = document.createElement("style");
@@ -1861,7 +1861,9 @@ window.__ModuleLoader__.load({
1861
1861
  /** 搜索面板条件(SearchPanel v1)。 */
1862
1862
  search: "edrv.search.v1.",
1863
1863
  /** 侧边栏提示已关闭标记(全局,无会话)。 */
1864
- sideHint: "edrv.side-hint-dismissed"
1864
+ sideHint: "edrv.side-hint-dismissed",
1865
+ /** 「性能优化」页工作区栏目展开状态(workspaceFoldCache v1,全局不按会话)。 */
1866
+ workspaceFold: "edrv.ws-fold.v1."
1865
1867
  };
1866
1868
  //#endregion
1867
1869
  //#region src/client/state/viewStateCache.ts
@@ -1968,6 +1970,418 @@ window.__ModuleLoader__.load({
1968
1970
  } catch (error) {}
1969
1971
  }
1970
1972
  //#endregion
1973
+ //#region src/shared/lsp.ts
1974
+ /**
1975
+ * Monaco 位置(1-based lineNumber/column)→ LSP Position(0-based)。
1976
+ * @author ddj 2026年08月27号
1977
+ * @param lineNumber Monaco 行(≥1)
1978
+ * @param column Monaco 列(≥1)
1979
+ * @returns LSP 0-based position
1980
+ */
1981
+ function monoToLsp(lineNumber, column) {
1982
+ return {
1983
+ line: Math.max(0, lineNumber - 1),
1984
+ character: Math.max(0, column - 1)
1985
+ };
1986
+ }
1987
+ //#endregion
1988
+ //#region src/client/monaco/lsp/lspClient.ts
1989
+ /**
1990
+ * dsh-vscode-mode client — LSP 会话与文档同步(模块级单例)。
1991
+ * 跟踪当前会话 + Monaco 打开文档(model 事件驱动),去抖推送 edrv.lsp.sync;
1992
+ * 并提供 definition/references/documentSymbol/hover/status 的 RPC 薄封装。
1993
+ * 作者 ddj 2026-08-27
1994
+ */
1995
+ /** edrv:// 模型 URI → 工作区相对/绝对路径(与 host 侧 path 对齐)。 */
1996
+ function pathOfModel(model) {
1997
+ if (!model || !model.uri) return null;
1998
+ const uri = model.uri;
1999
+ if (uri.scheme !== "edrv") return null;
2000
+ const raw = uri.path ?? "";
2001
+ try {
2002
+ return decodeURIComponent(String(raw).replace(/^\//, "")) || null;
2003
+ } catch (error) {
2004
+ return String(raw).replace(/^\//, "") || null;
2005
+ }
2006
+ }
2007
+ const SYNC_DEBOUNCE_MS = 250;
2008
+ let sessionId;
2009
+ const syncTimers = /* @__PURE__ */ new Map();
2010
+ let statusCache = {
2011
+ servers: [],
2012
+ at: 0
2013
+ };
2014
+ let progressListeners = /* @__PURE__ */ new Set();
2015
+ /** 设置当前会话(切换会话时清理旧文档跟踪由 host 端 session/disposed 兜底)。 */
2016
+ function setLspSession(id) {
2017
+ if (sessionId === id) return;
2018
+ sessionId = id;
2019
+ refreshStatus();
2020
+ }
2021
+ /** 订阅 LSP 状态变化(服务器进度/启动状态)。 */
2022
+ function onLspProgress(listener) {
2023
+ if (typeof listener !== "function") return () => {};
2024
+ progressListeners.add(listener);
2025
+ return () => progressListeners.delete(listener);
2026
+ }
2027
+ /** 发布当前 server 状态给编辑器底部状态区。 */
2028
+ function publishProgress() {
2029
+ for (const listener of progressListeners) try {
2030
+ listener(statusCache.servers);
2031
+ } catch (error) {}
2032
+ }
2033
+ /** 立即同步某文档到 host(首次打开/查询前保证服务器已认识该文档)。 */
2034
+ async function syncDoc(path, text, immediate = false) {
2035
+ if (!path || typeof text !== "string") return;
2036
+ if (immediate) {
2037
+ clearPending(path);
2038
+ await rpc("edrv.lsp.sync", {
2039
+ sessionId,
2040
+ path,
2041
+ text,
2042
+ version: Date.now()
2043
+ }).catch(() => {});
2044
+ return;
2045
+ }
2046
+ clearPending(path);
2047
+ syncTimers.set(path, setTimeout(() => {
2048
+ syncTimers.delete(path);
2049
+ rpc("edrv.lsp.sync", {
2050
+ sessionId,
2051
+ path,
2052
+ text,
2053
+ version: Date.now()
2054
+ }).catch(() => {});
2055
+ }, SYNC_DEBOUNCE_MS));
2056
+ }
2057
+ function clearPending(path) {
2058
+ const timer = syncTimers.get(path);
2059
+ if (timer) {
2060
+ clearTimeout(timer);
2061
+ syncTimers.delete(path);
2062
+ }
2063
+ }
2064
+ /** 文档关闭:停止去抖并通知 host。 */
2065
+ function closeDoc(path) {
2066
+ clearPending(path);
2067
+ rpc("edrv.lsp.close", {
2068
+ sessionId,
2069
+ path
2070
+ }).catch(() => {});
2071
+ }
2072
+ /** 立即推送 + 查询定义;返回 [{ uri, range }](uri 已转 edrv://)。 */
2073
+ async function findDefinition(path, text, position) {
2074
+ await syncDoc(path, text, true);
2075
+ const res = await rpc("edrv.lsp.definition", {
2076
+ sessionId,
2077
+ path,
2078
+ position: monoToLsp(position.lineNumber, position.column)
2079
+ }).catch(() => null);
2080
+ if (!res || !res.ok || !Array.isArray(res.locations)) return [];
2081
+ return res.locations.map(toClientLocation);
2082
+ }
2083
+ /** 立即推送 + 查询引用。 */
2084
+ async function findReferences(path, text, position, includeDeclaration = false) {
2085
+ await syncDoc(path, text, true);
2086
+ const res = await rpc("edrv.lsp.references", {
2087
+ sessionId,
2088
+ path,
2089
+ position: monoToLsp(position.lineNumber, position.column),
2090
+ includeDeclaration
2091
+ }).catch(() => null);
2092
+ if (!res || !res.ok || !Array.isArray(res.locations)) return [];
2093
+ return res.locations.map(toClientLocation);
2094
+ }
2095
+ /** 查询大纲符号(documentSymbol)。 */
2096
+ async function fetchDocumentSymbols(path, text) {
2097
+ await syncDoc(path, text, true);
2098
+ const res = await rpc("edrv.lsp.documentSymbol", {
2099
+ sessionId,
2100
+ path
2101
+ }).catch(() => null);
2102
+ if (!res || !res.ok || !Array.isArray(res.symbols)) return [];
2103
+ return res.symbols;
2104
+ }
2105
+ /** 查询 hover 内容。 */
2106
+ async function fetchHover(path, text, position) {
2107
+ await syncDoc(path, text, true);
2108
+ const res = await rpc("edrv.lsp.hover", {
2109
+ sessionId,
2110
+ path,
2111
+ position: monoToLsp(position.lineNumber, position.column)
2112
+ }).catch(() => null);
2113
+ if (!res || !res.ok) return null;
2114
+ return res.hover || null;
2115
+ }
2116
+ /** 拉取服务器状态(带 5s 缓存)。 */
2117
+ async function refreshStatus(force = false) {
2118
+ if (!force && Date.now() - statusCache.at < 1500) return statusCache.servers;
2119
+ const res = await rpc("edrv.lsp.status", { sessionId }).catch(() => null);
2120
+ const servers = res && res.ok && Array.isArray(res.servers) ? res.servers : [];
2121
+ statusCache = {
2122
+ servers,
2123
+ at: Date.now()
2124
+ };
2125
+ publishProgress();
2126
+ return servers;
2127
+ }
2128
+ /** file:// 或已转 edrv:// 的 LSP location → Monaco Location(edrv:// uri + 1-based range)。 */
2129
+ function toClientLocation(loc) {
2130
+ const range = loc.range || {};
2131
+ const start = range.start || {};
2132
+ const end = range.end || {};
2133
+ return {
2134
+ uri: window.monaco.Uri.parse(toEdrvUri(loc.uri, loc.root)),
2135
+ range: new window.monaco.Range(Math.max(1, (start.line ?? 0) + 1), Math.max(1, (start.character ?? 0) + 1), Math.max(1, (end.line ?? 0) + 1), Math.max(1, (end.character ?? 0) + 1))
2136
+ };
2137
+ }
2138
+ /** file:// URI(或裸路径)→ edrv:// 相对路径(去掉盘符/根,与编辑器模型路径一致)。 */
2139
+ function toEdrvUri(uri, root) {
2140
+ if (!uri) return "";
2141
+ if (uri.startsWith("edrv://")) return uri;
2142
+ const path = relativeLspPath(uri, root);
2143
+ return "edrv:///" + encodeURI(path);
2144
+ }
2145
+ /** 将 LSP 目标解析为当前工作区相对路径(跨文件跳转与 model URI 对齐)。 */
2146
+ function targetOpenPath(loc) {
2147
+ return relativeLspPath(loc?.uri, loc?.root);
2148
+ }
2149
+ /** file URI/裸路径 → root 下的 / 分隔相对路径;root 不匹配时保留规范化绝对路径。 */
2150
+ function relativeLspPath(uri, root) {
2151
+ if (!uri) return "";
2152
+ let path = String(uri);
2153
+ if (path.startsWith("file://")) try {
2154
+ path = decodeURIComponent(path.replace(/^file:\/\//, ""));
2155
+ } catch (error) {
2156
+ path = path.replace(/^file:\/\//, "");
2157
+ }
2158
+ path = path.replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1");
2159
+ const normalizedRoot = root ? String(root).replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1").replace(/\/+$/, "") : "";
2160
+ const lowerPath = path.toLowerCase();
2161
+ const lowerRoot = normalizedRoot.toLowerCase();
2162
+ if (normalizedRoot && (lowerPath === lowerRoot || lowerPath.startsWith(lowerRoot + "/"))) return path.slice(normalizedRoot.length).replace(/^\/+/, "");
2163
+ return path.replace(/^\/+/, "");
2164
+ }
2165
+ /** 目标行/列(0-based LSP → 1-based Monaco)。 */
2166
+ function targetMonoPosition(loc) {
2167
+ const start = (loc.range || {}).start || {};
2168
+ return {
2169
+ line: Math.max(1, (start.line ?? 0) + 1),
2170
+ column: Math.max(1, (start.character ?? 0) + 1)
2171
+ };
2172
+ }
2173
+ //#endregion
2174
+ //#region src/client/monaco/lsp/providers.ts
2175
+ /**
2176
+ * dsh-vscode-mode client — Monaco LSP provider 注册 + 跳转/引用命令。
2177
+ * 数据层:Definition/Reference/DocumentSymbol/Hover provider(同一文件内跳转 Monaco 原生可用)。
2178
+ * 交互层:edrv.goToDefinition(F12)/ edrv.findReferences(Shift+F12 / 右键),跨文件跳转自研
2179
+ * (target 经 openFileAt 打开并定位),引用结果用轻量浮动面板展示(点击跳转)。
2180
+ * 作者 ddj 2026-08-27
2181
+ */
2182
+ const LSP_LANGS = ["lua", "csharp"];
2183
+ let registered = false;
2184
+ const disposables = [];
2185
+ let overlayEl = null;
2186
+ /** 目标文件打开并定位(复用现有 openFileAt 的 edrv:open-editor 事件通道)。 */
2187
+ function openAt(path, line, column) {
2188
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
2189
+ path,
2190
+ line,
2191
+ column
2192
+ } }));
2193
+ }
2194
+ function jumpToLocation(loc) {
2195
+ const path = targetOpenPath(loc);
2196
+ const pos = targetMonoPosition(loc);
2197
+ openAt(path, pos.line, pos.column);
2198
+ }
2199
+ /** 注册全部 Monaco LSP provider 与文档跟踪(幂等)。 */
2200
+ function registerLspProviders(monaco) {
2201
+ if (registered) return;
2202
+ registered = true;
2203
+ const attachModel = (model) => {
2204
+ if (!model || model.uri.scheme !== "edrv") return;
2205
+ const path = pathOfModel(model);
2206
+ if (!path) return;
2207
+ const contentDisposer = model.onDidChangeContent(() => syncDoc(path, model.getValue()));
2208
+ const disposeDisposer = model.onWillDispose(() => {
2209
+ closeDoc(path);
2210
+ contentDisposer.dispose();
2211
+ });
2212
+ disposables.push(contentDisposer, disposeDisposer);
2213
+ syncDoc(path, model.getValue(), true);
2214
+ };
2215
+ disposables.push(monaco.editor.onDidCreateModel(attachModel));
2216
+ for (const model of monaco.editor.getModels()) attachModel(model);
2217
+ disposables.push(monaco.languages.registerDefinitionProvider(LSP_LANGS, { provideDefinition: (model, position, token) => {
2218
+ const path = pathOfModel(model);
2219
+ if (!path) return [];
2220
+ return findDefinition(path, model.getValue(), position);
2221
+ } }), monaco.languages.registerReferenceProvider(LSP_LANGS, { provideReferences: (model, position, context, token) => {
2222
+ const path = pathOfModel(model);
2223
+ if (!path) return [];
2224
+ return findReferences(path, model.getValue(), position, Boolean(context?.includeDeclaration));
2225
+ } }), monaco.languages.registerDocumentSymbolProvider(LSP_LANGS, { provideDocumentSymbols: async (model, token) => {
2226
+ const path = pathOfModel(model);
2227
+ if (!path) return [];
2228
+ return (await fetchDocumentSymbols(path, model.getValue())).map(toMonacoSymbol);
2229
+ } }), monaco.languages.registerHoverProvider(LSP_LANGS, { provideHover: async (model, position, token) => {
2230
+ const path = pathOfModel(model);
2231
+ if (!path) return null;
2232
+ const hover = await fetchHover(path, model.getValue(), position);
2233
+ if (!hover || !hover.contents || !hover.contents.length) return null;
2234
+ const range = hover.range ? new monaco.Range(hover.range.start.line + 1, hover.range.start.character + 1, hover.range.end.line + 1, hover.range.end.character + 1) : null;
2235
+ return {
2236
+ contents: hover.contents.map((text) => ({ value: text })),
2237
+ range: range || void 0
2238
+ };
2239
+ } }));
2240
+ }
2241
+ /** LSP SymbolInfo(host 归一化后)→ Monaco DocumentSymbol。 */
2242
+ function toMonacoSymbol(symbol) {
2243
+ const range = symbol.range || {};
2244
+ const sel = symbol.selectionRange || range;
2245
+ const s = (p) => ({
2246
+ lineNumber: Math.max(1, (p.line ?? 0) + 1),
2247
+ column: Math.max(1, (p.character ?? 0) + 1)
2248
+ });
2249
+ const out = {
2250
+ name: symbol.name,
2251
+ detail: symbol.detail || "",
2252
+ kind: symbol.kind ?? 0,
2253
+ tags: [],
2254
+ range: new window.monaco.Range(s(range.start).lineNumber, s(range.start).column, s(range.end).lineNumber, s(range.end).column),
2255
+ selectionRange: new window.monaco.Range(s(sel.start).lineNumber, s(sel.start).column, s(sel.end).lineNumber, s(sel.end).column)
2256
+ };
2257
+ if (Array.isArray(symbol.children) && symbol.children.length) out.children = symbol.children.map(toMonacoSymbol);
2258
+ return out;
2259
+ }
2260
+ /** 定义查询并跳转;nativeReveal 仅供 F12 保留 Monaco 原生 reveal。 */
2261
+ async function goToDefinition(ed, position, nativeReveal = false) {
2262
+ const model = ed.getModel();
2263
+ const path = pathOfModel(model);
2264
+ if (!path || !position) return;
2265
+ const locations = await findDefinition(path, model.getValue(), position);
2266
+ if (!locations.length) {
2267
+ setStatus("未找到定义");
2268
+ return;
2269
+ }
2270
+ const first = locations[0];
2271
+ if (nativeReveal && targetOpenPath(first) === path) {
2272
+ const action = ed.getAction("editor.action.revealDefinition");
2273
+ if (action) {
2274
+ await action.run();
2275
+ return;
2276
+ }
2277
+ }
2278
+ jumpToLocation(first);
2279
+ }
2280
+ /** F12 / 右键「转到定义」:查询定义并跳转(首个目标)。 */
2281
+ async function runGoToDefinition(ed) {
2282
+ await goToDefinition(ed, ed.getPosition(), true);
2283
+ }
2284
+ /** Ctrl+鼠标点击定义:使用点击位置查询,避免依赖当前光标位置。 */
2285
+ async function gotoDefinitionAt(ed, position) {
2286
+ await goToDefinition(ed, position);
2287
+ }
2288
+ /** 给一个 Monaco 编辑器绑定 Ctrl/Cmd+鼠标定义跳转(幂等)。 */
2289
+ function bindLspEditor(ed) {
2290
+ if (!ed || ed.__edrvLspClick) return;
2291
+ ed.__edrvLspClick = true;
2292
+ const disposable = ed.onMouseDown((event) => {
2293
+ const native = event?.event;
2294
+ const position = event?.target?.position;
2295
+ const button = native?.button ?? event?.button;
2296
+ const ctrl = native?.ctrlKey ?? event?.ctrlKey;
2297
+ const meta = native?.metaKey ?? event?.metaKey;
2298
+ const isLeftButton = button === 0 || native?.leftButton === true;
2299
+ if (!native || !position || !isLeftButton || !ctrl && !meta) return;
2300
+ native.preventDefault?.();
2301
+ native.stopPropagation?.();
2302
+ gotoDefinitionAt(ed, position);
2303
+ });
2304
+ disposables.push(disposable);
2305
+ }
2306
+ /** Shift+F12 / 右键「查找所有引用」:查询引用并展示浮动面板。 */
2307
+ async function runFindReferences(ed) {
2308
+ const model = ed.getModel();
2309
+ const path = pathOfModel(model);
2310
+ const position = ed.getPosition();
2311
+ if (!path || !position) return;
2312
+ showReferencesOverlay(ed, path, position, await findReferences(path, model.getValue(), position, false));
2313
+ }
2314
+ /** 引用结果浮动面板(轻量 DOM,点击跳转;Esc/失焦关闭)。 */
2315
+ function showReferencesOverlay(ed, path, position, locations) {
2316
+ hideReferencesOverlay();
2317
+ if (!locations.length) {
2318
+ setStatus("未找到引用");
2319
+ return;
2320
+ }
2321
+ const root = ed.getDomNode();
2322
+ if (!root) return;
2323
+ const el = document.createElement("div");
2324
+ el.className = "edrv-refs";
2325
+ const header = document.createElement("div");
2326
+ header.className = "edrv-refs-head";
2327
+ header.textContent = "引用 " + locations.length + " 处";
2328
+ el.appendChild(header);
2329
+ const list = document.createElement("div");
2330
+ list.className = "edrv-refs-list";
2331
+ for (const loc of locations) {
2332
+ const row = document.createElement("div");
2333
+ row.className = "edrv-refs-row";
2334
+ const pos = targetMonoPosition(loc);
2335
+ const rel = targetOpenPath(loc);
2336
+ row.textContent = (String(rel).split("/").pop() || rel) + ":" + pos.line + ":" + pos.column;
2337
+ row.title = rel + ":" + pos.line;
2338
+ row.addEventListener("click", () => {
2339
+ hideReferencesOverlay();
2340
+ jumpToLocation(loc);
2341
+ });
2342
+ list.appendChild(row);
2343
+ }
2344
+ el.appendChild(list);
2345
+ root.appendChild(el);
2346
+ overlayEl = el;
2347
+ const onKey = (e) => {
2348
+ if (e.key === "Escape") hideReferencesOverlay();
2349
+ };
2350
+ window.addEventListener("keydown", onKey);
2351
+ el.addEventListener("click", (e) => {
2352
+ if (e.target === el) hideReferencesOverlay();
2353
+ });
2354
+ el._onKey = onKey;
2355
+ }
2356
+ function hideReferencesOverlay() {
2357
+ if (overlayEl) {
2358
+ window.removeEventListener("keydown", overlayEl._onKey);
2359
+ overlayEl.remove();
2360
+ overlayEl = null;
2361
+ }
2362
+ }
2363
+ /** 编辑器状态栏提示(复用 EditorView 的 status 通道)。 */
2364
+ function setStatus(text) {
2365
+ window.dispatchEvent(new CustomEvent("edrv:status", { detail: { text } }));
2366
+ }
2367
+ //#endregion
2368
+ //#region src/client/monaco/lsp/index.ts
2369
+ /**
2370
+ * dsh-vscode-mode client — LSP 装配编排。
2371
+ * setupLsp(monaco):注册 Monaco provider 与文档跟踪(幂等);调用方在 Monaco 加载后调用。
2372
+ * setSession(id):会话切换时同步(provider 与文档同步共用)。
2373
+ * 作者 ddj 2026-08-27
2374
+ */
2375
+ /** Monaco 加载后装配(幂等;重复调用仅刷新会话)。 */
2376
+ function setupLsp(monaco) {
2377
+ registerLspProviders(monaco);
2378
+ }
2379
+ /** 会话切换:更新 host 侧文档归属 + 刷新状态。 */
2380
+ function setSession(sessionId) {
2381
+ setLspSession(sessionId);
2382
+ refreshStatus(true);
2383
+ }
2384
+ //#endregion
1971
2385
  //#region src/client/ui/EditorView.ts
1972
2386
  /**
1973
2387
  * dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
@@ -1998,6 +2412,7 @@ window.__ModuleLoader__.load({
1998
2412
  const [content, setContent] = react.default.useState(null);
1999
2413
  const [contentPath, setContentPath] = react.default.useState(null);
2000
2414
  const [status, setStatus] = react.default.useState("");
2415
+ const [lspServers, setLspServers] = react.default.useState([]);
2001
2416
  const [error, setError] = react.default.useState(null);
2002
2417
  const [loadError, setLoadError] = react.default.useState(null);
2003
2418
  const [loadStage, setLoadStage] = react.default.useState({
@@ -2234,6 +2649,18 @@ window.__ModuleLoader__.load({
2234
2649
  window.removeEventListener("edrv:refresh", onRefresh);
2235
2650
  };
2236
2651
  }, [sessionId]);
2652
+ react.default.useEffect(() => {
2653
+ const publishLsp = (servers) => setLspServers(Array.isArray(servers) ? servers : []);
2654
+ const unsubscribe = onLspProgress(publishLsp);
2655
+ refreshStatus(true).then(publishLsp);
2656
+ const timer = setInterval(() => {
2657
+ refreshStatus(true).then(publishLsp);
2658
+ }, 1500);
2659
+ return () => {
2660
+ unsubscribe();
2661
+ clearInterval(timer);
2662
+ };
2663
+ }, [sessionId]);
2237
2664
  react.default.useEffect(() => {
2238
2665
  const onOpen = (e) => {
2239
2666
  const p = e?.detail?.path;
@@ -2244,6 +2671,12 @@ window.__ModuleLoader__.load({
2244
2671
  region: null
2245
2672
  };
2246
2673
  setFocusRequest((value) => value + 1);
2674
+ addTab(p, true);
2675
+ return;
2676
+ }
2677
+ if (e?.detail?.line != null) {
2678
+ openFileAt(p, e?.detail?.line, e?.detail?.column);
2679
+ return;
2247
2680
  }
2248
2681
  addTab(p, true);
2249
2682
  };
@@ -2254,9 +2687,14 @@ window.__ModuleLoader__.load({
2254
2687
  };
2255
2688
  window.addEventListener("edrv:open-editor", onOpen);
2256
2689
  window.addEventListener("edrv:show-launcher", onShowLauncher);
2690
+ const onStatus = (e) => {
2691
+ if (e?.detail?.text) setStatus(e.detail.text);
2692
+ };
2693
+ window.addEventListener("edrv:status", onStatus);
2257
2694
  return () => {
2258
2695
  window.removeEventListener("edrv:open-editor", onOpen);
2259
2696
  window.removeEventListener("edrv:show-launcher", onShowLauncher);
2697
+ window.removeEventListener("edrv:status", onStatus);
2260
2698
  };
2261
2699
  }, [sessionId]);
2262
2700
  const dockSourceRef = react.default.useRef({});
@@ -2540,6 +2978,7 @@ window.__ModuleLoader__.load({
2540
2978
  flushSave();
2541
2979
  saveViewState(active);
2542
2980
  diffRendererRef.current?.dispose?.();
2981
+ hideReferencesOverlay();
2543
2982
  if (editorRef.current) {
2544
2983
  editorRef.current.dispose();
2545
2984
  editorRef.current = null;
@@ -2639,6 +3078,30 @@ window.__ModuleLoader__.load({
2639
3078
  precondition: "editorTextFocus",
2640
3079
  run: (edx) => menuHandlers()?.openInExplorer(pathOf(edx))
2641
3080
  });
3081
+ ed.addAction({
3082
+ id: "edrv.goToDefinition",
3083
+ label: "转到定义",
3084
+ contextMenuGroupId: "1_edrv",
3085
+ keybindings: m.KeyMod.CtrlCmd | m.KeyCode.F12,
3086
+ precondition: "editorTextFocus",
3087
+ run: (edx) => {
3088
+ runGoToDefinition(edx);
3089
+ }
3090
+ });
3091
+ ed.addAction({
3092
+ id: "edrv.findReferences",
3093
+ label: "查找所有引用",
3094
+ contextMenuGroupId: "1_edrv",
3095
+ keybindings: m.KeyMod.Shift | m.KeyCode.F12,
3096
+ precondition: "editorTextFocus",
3097
+ run: (edx) => {
3098
+ runFindReferences(edx);
3099
+ }
3100
+ });
3101
+ ed.addCommand(m.KeyCode.F12, () => {
3102
+ runGoToDefinition(ed);
3103
+ });
3104
+ bindLspEditor(ed);
2642
3105
  const hideSoon = () => {
2643
3106
  if (hoverEditorRef.current || hoverPanelRef.current || hideTimerRef.current) return;
2644
3107
  hideTimerRef.current = setTimeout(() => {
@@ -3092,6 +3555,26 @@ window.__ModuleLoader__.load({
3092
3555
  className: "edrv-pathbar",
3093
3556
  title: active || ""
3094
3557
  }, react.default.createElement("span", { className: "edrv-pb-name" }, active ? String(active).split(/[\\/]/).pop() : "未打开文件"), react.default.createElement("span", { className: "edrv-pb-full" }, active || "使用右上搜索框 (" + (chordOf("edrv.quickOpen") ?? "Ctrl+P") + ") 打开文件"), active && langOf(active) ? react.default.createElement("span", { className: "edrv-pb-meta" }, langOf(active)) : null, cursor ? react.default.createElement("span", { className: "edrv-pb-meta" }, cursor) : null, status ? react.default.createElement("span", { className: "edrv-pb-meta edrv-pb-status" }, status) : null);
3558
+ const lspLanguage = active ? langOf(active) : "";
3559
+ const lspServer = lspServers.find((item) => item.languageId === lspLanguage);
3560
+ const lspLabel = lspServer ? "LSP " + lspLanguage + " · " + ({
3561
+ idle: "未启动",
3562
+ starting: "启动中",
3563
+ ready: "已就绪",
3564
+ indexing: "解析中",
3565
+ unavailable: "不可用",
3566
+ stopped: "已停止"
3567
+ }[lspServer.phase] ?? lspServer.phase) + " · " + ({
3568
+ extension: "扩展",
3569
+ discover: "自动发现",
3570
+ manual: "手动配置",
3571
+ none: "未配置"
3572
+ }[lspServer.source] ?? lspServer.source) : "LSP " + lspLanguage + " · 未启动";
3573
+ const lspProgress = typeof lspServer?.progress === "number" ? Math.round(lspServer.progress) : null;
3574
+ const lspFooter = lspLanguage === "lua" || lspLanguage === "csharp" ? react.default.createElement("div", {
3575
+ className: "edrv-statusbar",
3576
+ title: lspServer?.progressMessage || lspLabel
3577
+ }, react.default.createElement("span", { className: "edrv-lsp-status-dot " + (lspServer?.phase === "ready" ? "ready" : lspServer?.phase === "indexing" || lspServer?.phase === "starting" ? "busy" : "idle") }), react.default.createElement("span", { className: "edrv-sp-lsp" }, lspLabel), lspServer?.progressMessage ? react.default.createElement("span", { className: "edrv-sp-progress-message" }, lspServer.progressMessage) : null, lspProgress !== null ? react.default.createElement("span", { className: "edrv-sp-progress" }, lspProgress + "%") : null) : null;
3095
3578
  const tabRow = react.default.createElement("div", { style: {
3096
3579
  display: "flex",
3097
3580
  alignItems: "center",
@@ -3345,7 +3828,7 @@ window.__ModuleLoader__.load({
3345
3828
  flexDirection: "column",
3346
3829
  overflow: "hidden"
3347
3830
  }
3348
- }, pathBar, tabRow, sideHintEl, editorArea);
3831
+ }, pathBar, tabRow, sideHintEl, editorArea, lspFooter);
3349
3832
  const editorRow = react.default.createElement("div", { className: "edrv-editor-row" }, sidebarOn && sidebarPanels ? react.default.createElement(SidebarView, {
3350
3833
  registry: sidebarPanels,
3351
3834
  ctx: sidebarCtx,
@@ -3749,7 +4232,7 @@ window.__ModuleLoader__.load({
3749
4232
  }
3750
4233
  //#endregion
3751
4234
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
3752
- const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* 子 Tab:我的 MCP / MCP 市场 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.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}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.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}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:10px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0 0 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.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}\n.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}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;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);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.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}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性小节 */\n.vsm-compat-h3{margin:16px 0 2px;font-size:14px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.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)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* keyboard shortcuts page */\n.vsm-kb-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-kb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:8px;background:var(--dsw-alias-bg-layer-1,#161616)}\n.vsm-kb-row.rec{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-kb-label{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;color:var(--dsw-alias-label-primary,#eee);font-size:13px}\n.vsm-kb-warn{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px}\n.vsm-kb-key{min-width:96px;text-align:center;padding:3px 10px;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;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-kb-key.warn{border-color:var(--dsw-alias-state-warn-primary,#e5b93f);color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-key.invalid{border-color:var(--dsw-alias-state-error-primary,#d9534f);color:var(--dsw-alias-state-error-primary,#d9534f)}\n.vsm-kb-rec{font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-kb-actions{display:flex;gap:6px}\n.vsm-kb-actions button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:4px 10px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-kb-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-kb-actions button:disabled{opacity:.55;cursor:default}\n.vsm-kb-actionsbar{display:flex;align-items:center;gap:10px;margin-top:12px}\n.vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.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)}\n.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}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.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)}\n.vsm-perf-moved h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.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)}\n.vsm-perf-config h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.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%}\n.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)}\n.vsm-perf-guide h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.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}\n.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)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n";
4235
+ const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* MCP 管理下的二级子页签:我的 MCP / 项目 MCP / MCP 市场 */\n.vsm-mcp-subhead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:-4px}\n.vsm-mcp-subtabs{display:flex;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#2e2e2e);flex:1}\n.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}\n.vsm-mcp-subtabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-subtabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.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}\n\n/* 顶层 Tab:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.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}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.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}\n\n/* 统一线框面板:圆角矩形边框圈住各设置分区,提升可读性。\n 用法:<section class=\"vsm-panel\"> + 可选 <h3 class=\"vsm-panel-title\">。 */\n.vsm-panel{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,transparent);overflow:hidden}\n.vsm-panel-pad{padding:14px 16px}\n.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)}\n.vsm-panel-body{padding:6px 16px}\n.vsm-panel-body>*:first-child{margin-top:8px}\n.vsm-panel-body>*:last-child{margin-bottom:10px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:14px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.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}\n.vsm-general-row:last-child{border-bottom:0}\n.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}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;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);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.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}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性条目(面板标题改用 .vsm-panel-title) */\n.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)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* keyboard shortcuts page */\n.vsm-kb-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-kb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:8px;background:var(--dsw-alias-bg-layer-1,#161616)}\n.vsm-kb-row.rec{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-kb-label{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;color:var(--dsw-alias-label-primary,#eee);font-size:13px}\n.vsm-kb-warn{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px}\n.vsm-kb-key{min-width:96px;text-align:center;padding:3px 10px;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;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-kb-key.warn{border-color:var(--dsw-alias-state-warn-primary,#e5b93f);color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-key.invalid{border-color:var(--dsw-alias-state-error-primary,#d9534f);color:var(--dsw-alias-state-error-primary,#d9534f)}\n.vsm-kb-rec{font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-kb-actions{display:flex;gap:6px}\n.vsm-kb-actions button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:4px 10px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-kb-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-kb-actions button:disabled{opacity:.55;cursor:default}\n.vsm-kb-actionsbar{display:flex;align-items:center;gap:10px;margin-top:12px}\n.vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.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}\n.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page>.vsm-panel-body>*:first-child{margin-top:0}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n/* 工作区列表分页器 */\n.vsm-perf-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:4px}\n.vsm-perf-pager-text{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-pager-actions{display:flex;gap:8px}\n.vsm-perf-ws-list{display:flex;flex-direction:column;gap:8px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.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}\n.vsm-perf-ws-head:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-perf-ws-head:focus-visible{outline:1px solid var(--dsw-alias-brand-primary,#6d55ff);outline-offset:-1px}\n.vsm-perf-ws.expanded .vsm-perf-ws-head{border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-caret{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;flex-shrink:0;transition:transform .15s;display:inline-block}\n.vsm-perf-ws-caret.open{transform:rotate(90deg)}\n.vsm-perf-ws .vsm-perf-ws-name{flex:1}\n.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}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.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%}\n.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}\n.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)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
3753
4236
  const tagId = "dsh-vscode-mode/mcp.css";
3754
4237
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
3755
4238
  const tag = document.createElement("style");
@@ -4136,7 +4619,7 @@ window.__ModuleLoader__.load({
4136
4619
  onClick: () => resetRow(command.id)
4137
4620
  }, "恢复"))));
4138
4621
  });
4139
- return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "快捷键"), react.default.createElement("p", null, "配置编辑器视图的快捷键(保存 / 快速打开 / 侧边栏 / 搜索)。修改即时写入配置,重新聚焦编辑器后生效。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用默认键位。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("div", { className: "vsm-kb-message" }, message), react.default.createElement("div", { className: "vsm-kb-list" }, rows), react.default.createElement("div", { className: "vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
4622
+ return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "快捷键"), react.default.createElement("p", null, "配置编辑器视图的快捷键(保存 / 快速打开 / 侧边栏 / 搜索)。修改即时写入配置,重新聚焦编辑器后生效。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用默认键位。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("div", { className: "vsm-kb-message" }, message), react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "编辑器键位"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-kb-list" }, rows)), react.default.createElement("div", { className: "vsm-panel-body vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
4140
4623
  className: "vsm-primary vsm-small",
4141
4624
  disabled: unavailable || notReady || busy || snapshot?.writable === false || !dirty,
4142
4625
  onClick: save
@@ -4144,7 +4627,386 @@ window.__ModuleLoader__.load({
4144
4627
  className: "vsm-small",
4145
4628
  disabled: unavailable || notReady || busy || snapshot?.writable === false,
4146
4629
  onClick: resetAll
4147
- }, "恢复默认")));
4630
+ }, "恢复默认"))));
4631
+ }
4632
+ //#endregion
4633
+ //#region src/client/ui/LspSettings.ts
4634
+ /**
4635
+ * dsh-vscode-mode client — 「语言服务器」设置子 Tab。
4636
+ * 四视图:服务器(状态 + 每语言启用/命令/路径)|已安装(卸载/更新)|市场(Open VSX 搜索/安装 + 本地 vsix)|更新。
4637
+ * 状态经 edrv.lsp.status,配置经 edrv.lsp.configGet/Update,扩展经 edrv.lsp.ext.*。
4638
+ * 作者 ddj 2026-08-27
4639
+ */
4640
+ const LANGUAGES = [{
4641
+ id: "lua",
4642
+ label: "Lua(LuaLS)",
4643
+ hint: "lua-language-server,EmmyLua 注解原生支持"
4644
+ }, {
4645
+ id: "csharp",
4646
+ label: "C#(Roslyn / OmniSharp)",
4647
+ hint: "需 dotnet + ms-dotnettools.csharp 的 .roslyn 服务器,或手动指定"
4648
+ }];
4649
+ const PHASE_LABEL = {
4650
+ idle: "未启动",
4651
+ starting: "启动中",
4652
+ ready: "就绪",
4653
+ indexing: "索引中",
4654
+ unavailable: "不可用",
4655
+ stopped: "已停止"
4656
+ };
4657
+ const SOURCE_LABEL = {
4658
+ extension: "扩展",
4659
+ discover: "自动发现",
4660
+ manual: "手动配置",
4661
+ none: "未配置"
4662
+ };
4663
+ const TABS = [
4664
+ {
4665
+ id: "servers",
4666
+ label: "服务器"
4667
+ },
4668
+ {
4669
+ id: "installed",
4670
+ label: "已安装"
4671
+ },
4672
+ {
4673
+ id: "market",
4674
+ label: "市场"
4675
+ },
4676
+ {
4677
+ id: "updates",
4678
+ label: "更新"
4679
+ }
4680
+ ];
4681
+ /** 语言卡片:状态 + 启用开关 + 命令/路径配置。 */
4682
+ function LangCard({ lang, config, status, busy, onToggle, onSave }) {
4683
+ const [pathDraft, setPathDraft] = react.default.useState(config?.path ?? "");
4684
+ const [commandDraft, setCommandDraft] = react.default.useState(config?.command ?? "");
4685
+ react.default.useEffect(() => {
4686
+ setPathDraft(config?.path ?? "");
4687
+ setCommandDraft(config?.command ?? "");
4688
+ }, [config?.path, config?.command]);
4689
+ const phase = status?.phase ?? "idle";
4690
+ return react.default.createElement("article", { className: "vsm-mcp-card" }, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot " + (phase === "ready" || phase === "indexing" ? "connected" : phase === "unavailable" ? "error" : "disabled") }), lang.label), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
4691
+ className: "vsm-switch " + (config?.enabled !== false ? "on" : ""),
4692
+ onClick: () => onToggle(lang.id, config?.enabled !== false ? false : true),
4693
+ "aria-label": "启用/禁用"
4694
+ }, config?.enabled !== false ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, PHASE_LABEL[phase] ?? phase, " · ", SOURCE_LABEL[status?.source] ?? status?.source, phase === "ready" && status?.root ? " · " + String(status.root).split(/[\\/]/).pop() : ""), status?.reason ? react.default.createElement("div", { className: "vsm-mcp-error" }, status.reason) : null, react.default.createElement("div", { className: "vsm-lsp-form" }, react.default.createElement("label", null, react.default.createElement("span", null, "可执行文件路径(绝对路径,优先)"), react.default.createElement("input", {
4695
+ value: pathDraft,
4696
+ placeholder: "如 C:/.../lua-language-server.exe",
4697
+ onChange: (e) => setPathDraft(e.target.value)
4698
+ })), react.default.createElement("label", null, react.default.createElement("span", null, "命令名(PATH 内查找)"), react.default.createElement("input", {
4699
+ value: commandDraft,
4700
+ placeholder: "如 lua-language-server",
4701
+ onChange: (e) => setCommandDraft(e.target.value)
4702
+ })), react.default.createElement("button", {
4703
+ className: "vsm-primary",
4704
+ disabled: busy === lang.id,
4705
+ onClick: () => onSave(lang.id, pathDraft, commandDraft)
4706
+ }, busy === lang.id ? "保存中…" : "保存配置")), react.default.createElement("div", { className: "vsm-lsp-hint" }, lang.hint));
4707
+ }
4708
+ /** 已装扩展卡片:清单 + 卸载/更新。 */
4709
+ function InstalledCard({ ext, busy, onUninstall, onUpdate }) {
4710
+ return react.default.createElement("article", { className: "vsm-mcp-card" }, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot connected" }), react.default.createElement("span", null, ext.displayName || ext.name), react.default.createElement("span", { className: "vsm-ext-ver" }, ext.id + " · v" + ext.version)), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
4711
+ className: "vsm-primary vsm-small",
4712
+ disabled: busy === ext.id,
4713
+ onClick: () => onUpdate(ext)
4714
+ }, "更新"), react.default.createElement("button", {
4715
+ className: "vsm-danger vsm-small",
4716
+ disabled: busy === ext.id,
4717
+ onClick: () => onUninstall(ext)
4718
+ }, "卸载"))), ext.description ? react.default.createElement("div", { className: "vsm-lsp-hint" }, ext.description) : null);
4719
+ }
4720
+ /** 市场条目卡片:安装/已装态。 */
4721
+ function MarketCard({ item, installed, busy, onInstall }) {
4722
+ const has = installed.some((e) => e.id === item.id);
4723
+ return react.default.createElement("article", { className: "vsm-mcp-card" }, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot " + (has ? "connected" : "disabled") }), react.default.createElement("span", null, item.displayName || item.name), react.default.createElement("span", { className: "vsm-ext-ver" }, item.id + " · v" + item.version)), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
4724
+ className: "vsm-primary vsm-small",
4725
+ disabled: has || busy === item.id,
4726
+ onClick: () => onInstall(item)
4727
+ }, has ? "已安装" : "安装"))), item.description ? react.default.createElement("div", { className: "vsm-lsp-hint" }, item.description) : null);
4728
+ }
4729
+ /**
4730
+ * 语言服务器设置页:服务器状态/配置 + VSIX 扩展(安装/更新/卸载三视图)。
4731
+ * @author ddj 2026年08月27号
4732
+ */
4733
+ function LspSettings() {
4734
+ const [tab, setTab] = react.default.useState("servers");
4735
+ const [config, setConfig] = react.default.useState({});
4736
+ const [servers, setServers] = react.default.useState([]);
4737
+ const [installed, setInstalled] = react.default.useState([]);
4738
+ const [updates, setUpdates] = react.default.useState([]);
4739
+ const [market, setMarket] = react.default.useState([]);
4740
+ const [query, setQuery] = react.default.useState("");
4741
+ const [vsixPath, setVsixPath] = react.default.useState("");
4742
+ const [busy, setBusy] = react.default.useState("");
4743
+ const [error, setError] = react.default.useState("");
4744
+ const [loading, setLoading] = react.default.useState(true);
4745
+ const refreshServers = react.default.useCallback(() => {
4746
+ Promise.all([rpc("edrv.lsp.configGet", {}), rpc("edrv.lsp.status", {})]).then(([cfg, st]) => {
4747
+ if (cfg?.ok) setConfig(cfg.config ?? {});
4748
+ if (st?.ok) setServers(st.servers ?? []);
4749
+ setError("");
4750
+ }).catch((e) => setError(String(e)));
4751
+ }, []);
4752
+ const refreshExt = react.default.useCallback(() => {
4753
+ Promise.all([rpc("edrv.lsp.ext.list", {}), rpc("edrv.lsp.ext.updates", {})]).then(([l, u]) => {
4754
+ if (l?.ok) setInstalled(l.extensions ?? []);
4755
+ if (u?.ok) setUpdates(u.updates ?? []);
4756
+ setError("");
4757
+ }).catch((e) => setError(String(e)));
4758
+ }, []);
4759
+ const refresh = react.default.useCallback(() => {
4760
+ setLoading(true);
4761
+ Promise.all([refreshServers(), refreshExt()]).finally(() => setLoading(false));
4762
+ }, [refreshServers, refreshExt]);
4763
+ react.default.useEffect(() => {
4764
+ refresh();
4765
+ }, [refresh]);
4766
+ const switchTab = (next) => {
4767
+ setTab(next);
4768
+ setError("");
4769
+ if (next === "market" && !query) setQuery("lua language server");
4770
+ if (next === "installed" || next === "updates") refreshExt();
4771
+ if (next === "servers") refreshServers();
4772
+ };
4773
+ const saveLang = (languageId, path, command) => {
4774
+ setBusy(languageId);
4775
+ setError("");
4776
+ rpc("edrv.lsp.configUpdate", {
4777
+ languageId,
4778
+ path,
4779
+ command
4780
+ }).then((res) => {
4781
+ if (!res?.ok) {
4782
+ setError(res?.error ?? "保存失败");
4783
+ return;
4784
+ }
4785
+ setConfig(res.config ?? {});
4786
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4787
+ };
4788
+ const toggleLang = (languageId, enabled) => {
4789
+ setBusy(languageId);
4790
+ rpc("edrv.lsp.configUpdate", {
4791
+ languageId,
4792
+ enabled
4793
+ }).then((res) => {
4794
+ if (res?.ok) {
4795
+ setConfig(res.config ?? {});
4796
+ refreshServers();
4797
+ } else setError(res?.error ?? "切换失败");
4798
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4799
+ };
4800
+ const searchMarket = () => {
4801
+ if (!query.trim()) return;
4802
+ setBusy("__market");
4803
+ setError("");
4804
+ rpc("edrv.lsp.ext.market", {
4805
+ query: query.trim(),
4806
+ size: 12
4807
+ }).then((res) => {
4808
+ if (res?.ok) setMarket(res.extensions ?? []);
4809
+ else setError(res?.error ?? "搜索失败");
4810
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4811
+ };
4812
+ const installMarket = (item) => {
4813
+ setBusy(item.id);
4814
+ setError("");
4815
+ rpc("edrv.lsp.ext.install", {
4816
+ namespace: item.namespace,
4817
+ name: item.name
4818
+ }).then((res) => {
4819
+ if (!res?.ok) {
4820
+ setError(res?.error ?? "安装失败");
4821
+ return;
4822
+ }
4823
+ refreshExt();
4824
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4825
+ };
4826
+ const installLocal = () => {
4827
+ if (!vsixPath.trim()) return;
4828
+ setBusy("__vsix");
4829
+ setError("");
4830
+ rpc("edrv.lsp.ext.install", { vsixPath: vsixPath.trim() }).then((res) => {
4831
+ if (!res?.ok) {
4832
+ setError(res?.error ?? "安装失败");
4833
+ return;
4834
+ }
4835
+ setVsixPath("");
4836
+ refreshExt();
4837
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4838
+ };
4839
+ const uninstallExt = (ext) => {
4840
+ if (!window.confirm("卸载扩展 " + ext.id + "?")) return;
4841
+ setBusy(ext.id);
4842
+ setError("");
4843
+ rpc("edrv.lsp.ext.uninstall", { id: ext.id }).then((res) => {
4844
+ if (!res?.ok) setError(res?.error ?? "卸载失败");
4845
+ refreshExt();
4846
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4847
+ };
4848
+ const updateExt = (ext) => {
4849
+ setBusy(ext.id);
4850
+ setError("");
4851
+ rpc("edrv.lsp.ext.update", { id: ext.id }).then((res) => {
4852
+ if (!res?.ok) setError(res?.error ?? "更新失败");
4853
+ refreshExt();
4854
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4855
+ };
4856
+ if (loading) return react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取语言服务器状态…");
4857
+ const nav = react.default.createElement("nav", { className: "vsm-mcp-tabs" }, TABS.map((t) => react.default.createElement("button", {
4858
+ key: t.id,
4859
+ className: tab === t.id ? "active" : "",
4860
+ onClick: () => switchTab(t.id)
4861
+ }, t.label)));
4862
+ const header = react.default.createElement("div", { className: "vsm-mcp-header" }, react.default.createElement("div", null, react.default.createElement("h2", null, "语言服务器(LSP)"), react.default.createElement("p", null, "代码索引 / 引用查找 / 动态跳转;可安装 VSIX 语言服务器扩展(Open VSX 市场 / 本地 .vsix)。")), react.default.createElement("button", {
4863
+ className: "vsm-primary",
4864
+ onClick: refresh
4865
+ }, "↻ 刷新"));
4866
+ const banner = error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null;
4867
+ /** 线框面板容器:标题 + 内容体。 */
4868
+ const panel = (title, children) => react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, title), react.default.createElement("div", { className: "vsm-panel-body" }, children));
4869
+ let body = null;
4870
+ if (tab === "servers") body = panel("语言服务器", react.default.createElement(react.default.Fragment, null, LANGUAGES.map((lang) => react.default.createElement(LangCard, {
4871
+ key: lang.id,
4872
+ lang,
4873
+ config: config[lang.id] ?? {},
4874
+ status: servers.find((s) => s.languageId === lang.id),
4875
+ busy,
4876
+ onToggle: toggleLang,
4877
+ onSave: saveLang
4878
+ })), react.default.createElement("div", { className: "vsm-lsp-hint" }, "提示:打开 Lua/C# 文件即自动(惰性)启动对应语言服务器;未配置时编辑器功能不受影响,大纲回退内置解析。")));
4879
+ else if (tab === "installed") body = panel("已安装扩展", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-hint" }, "已安装的语言服务器扩展(存于 ~/.dsh/dsh-vscode-mode/extensions/)。语言服务器被自动发现为「扩展」源。"), installed.length ? installed.map((ext) => react.default.createElement(InstalledCard, {
4880
+ key: ext.id,
4881
+ ext,
4882
+ busy,
4883
+ onUninstall: uninstallExt,
4884
+ onUpdate: updateExt
4885
+ })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有安装扩展(去「市场」或指定本地 .vsix)")));
4886
+ else if (tab === "market") body = panel("扩展市场", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-form" }, react.default.createElement("label", null, react.default.createElement("span", null, "搜索 Open VSX 市场"), react.default.createElement("input", {
4887
+ value: query,
4888
+ placeholder: "如 sumneko.lua / lua language server",
4889
+ onChange: (e) => setQuery(e.target.value),
4890
+ onKeyDown: (e) => {
4891
+ if (e.key === "Enter") searchMarket();
4892
+ }
4893
+ })), react.default.createElement("button", {
4894
+ className: "vsm-primary",
4895
+ disabled: busy === "__market",
4896
+ onClick: searchMarket
4897
+ }, busy === "__market" ? "搜索中…" : "搜索")), react.default.createElement("div", { className: "vsm-lsp-form" }, react.default.createElement("label", null, react.default.createElement("span", null, "安装本地 .vsix 文件(服务器绝对路径)"), react.default.createElement("input", {
4898
+ value: vsixPath,
4899
+ placeholder: "如 C:/Downloads/sumneko.lua-3.19.1.vsix",
4900
+ onChange: (e) => setVsixPath(e.target.value)
4901
+ })), react.default.createElement("button", {
4902
+ className: "vsm-primary",
4903
+ disabled: busy === "__vsix",
4904
+ onClick: installLocal
4905
+ }, busy === "__vsix" ? "安装中…" : "安装本地 .vsix")), react.default.createElement("div", { className: "vsm-lsp-hint" }, "市场安装会下载并解包 VSIX 到扩展目录;含语言服务器(如 sumneko.lua)的扩展会被自动发现。"), market.map((item) => react.default.createElement(MarketCard, {
4906
+ key: item.id,
4907
+ item,
4908
+ installed,
4909
+ busy,
4910
+ onInstall: installMarket
4911
+ }))));
4912
+ else body = panel("可用更新", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-hint" }, "已装扩展与 Open VSX 最新版的差异。"), updates.length ? updates.map((u) => react.default.createElement("article", {
4913
+ key: u.id,
4914
+ className: "vsm-mcp-card"
4915
+ }, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot error" }), react.default.createElement("span", null, u.displayName || u.id), react.default.createElement("span", { className: "vsm-ext-ver" }, "v" + u.current + " → v" + u.latest)), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
4916
+ className: "vsm-primary vsm-small",
4917
+ disabled: busy === u.id,
4918
+ onClick: () => updateExt(u)
4919
+ }, busy === u.id ? "更新中…" : "更新"))))) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "全部已是最新")));
4920
+ return react.default.createElement("div", null, header, nav, banner, body);
4921
+ }
4922
+ //#endregion
4923
+ //#region src/client/state/workspaceFoldCache.ts
4924
+ /**
4925
+ * dsh-vscode-mode client — 「性能优化」页工作区栏目展开状态持久化(纯函数,可单测)。
4926
+ * 只持久化「已展开」的工作区键集合(默认全部折叠),对齐 explorerCache 的做法:
4927
+ * 不缓存会话条目本身,条目由 edrv.perf.inventory 实时返回。
4928
+ * 键前缀统一走 paths.ts PathConst(CACHE_KEY.workspaceFold)。
4929
+ * 作者 ddj 2026-09-02
4930
+ */
4931
+ /** localStorage 键(全局,不按会话隔离)。 */
4932
+ const STORE_KEY = CACHE_KEY.workspaceFold;
4933
+ /**
4934
+ * 归一化工作区键:仅去首尾空白(工作区键非文件系统路径,不做斜杠转换)。
4935
+ * @author ddj 2026年09月02号
4936
+ * @param key 工作区标识
4937
+ * @returns 归一化键,空值返回 ''
4938
+ */
4939
+ function normalizeFoldKey(key) {
4940
+ return String(key ?? "").trim();
4941
+ }
4942
+ /**
4943
+ * 裁剪展开键集:去重、丢弃空值、超容量丢弃尾部(保持传入顺序)。
4944
+ * @author ddj 2026年09月02号
4945
+ * @param expanded 原始展开键集合
4946
+ * @returns 规范化后的键数组
4947
+ */
4948
+ function sanitizeFolded(expanded) {
4949
+ if (!Array.isArray(expanded)) return [];
4950
+ const seen = /* @__PURE__ */ new Set();
4951
+ const out = [];
4952
+ for (const item of expanded) {
4953
+ const key = normalizeFoldKey(typeof item === "string" ? item : "");
4954
+ if (!key || seen.has(key)) continue;
4955
+ seen.add(key);
4956
+ out.push(key);
4957
+ if (out.length >= 300) break;
4958
+ }
4959
+ return out;
4960
+ }
4961
+ /**
4962
+ * 序列化展开状态(JSON,带 schema 版本号)。
4963
+ * @author ddj 2026年09月02号
4964
+ * @param expanded 已展开的项目键集合
4965
+ * @returns JSON 字符串
4966
+ */
4967
+ function serializeFold(expanded) {
4968
+ return JSON.stringify({
4969
+ v: 1,
4970
+ expanded: sanitizeFolded(expanded)
4971
+ });
4972
+ }
4973
+ /**
4974
+ * 解析缓存文本;损坏/格式不符 → 空集合。
4975
+ * @author ddj 2026年09月02号
4976
+ * @param text localStorage 原文
4977
+ * @returns 已展开键集合
4978
+ */
4979
+ function parseFold(text) {
4980
+ if (!text) return [];
4981
+ try {
4982
+ const data = JSON.parse(text);
4983
+ if (!data || typeof data !== "object") return [];
4984
+ return sanitizeFolded(data.expanded);
4985
+ } catch (error) {
4986
+ return [];
4987
+ }
4988
+ }
4989
+ /**
4990
+ * 读取展开状态(localStorage 不可用 → 空集合)。
4991
+ * @author ddj 2026年09月02号
4992
+ * @returns 已展开键集合
4993
+ */
4994
+ function foldLoad() {
4995
+ try {
4996
+ return parseFold(window.localStorage.getItem(STORE_KEY));
4997
+ } catch (error) {
4998
+ return [];
4999
+ }
5000
+ }
5001
+ /**
5002
+ * 写入展开状态(配额满/隐私模式失败静默)。
5003
+ * @author ddj 2026年09月02号
5004
+ * @param expanded 已展开键集合
5005
+ */
5006
+ function foldSave(expanded) {
5007
+ try {
5008
+ window.localStorage.setItem(STORE_KEY, serializeFold(expanded));
5009
+ } catch (error) {}
4148
5010
  }
4149
5011
  //#endregion
4150
5012
  //#region src/client/ui/PerfSettings.ts
@@ -4166,6 +5028,43 @@ window.__ModuleLoader__.load({
4166
5028
  }
4167
5029
  /** 会话行 key:workspaceKey \u0000 sessionId。 */
4168
5030
  const keyOf = (ws, id) => ws + "\0" + id;
5031
+ /** 工作区列表每页条数。 */
5032
+ const PAGE_SIZE = 7;
5033
+ /**
5034
+ * 计算分页(纯函数,可单测)。
5035
+ * 作者 ddj 2026年09月02号
5036
+ * @param total 总条数
5037
+ * @param pageSize 每页条数
5038
+ * @param page 当前页码(1 起)
5039
+ * @returns 归一化后的分页信息(page 被夹到有效范围)
5040
+ */
5041
+ function paginate(total, pageSize, page) {
5042
+ const size = Math.max(1, Math.floor(pageSize) || 1);
5043
+ const pages = Math.max(1, Math.ceil(total / size));
5044
+ const current = Math.min(Math.max(1, Math.floor(page) || 1), pages);
5045
+ const start = (current - 1) * size;
5046
+ return {
5047
+ page: current,
5048
+ pages,
5049
+ pageSize: size,
5050
+ start,
5051
+ end: Math.min(start + size, total),
5052
+ total
5053
+ };
5054
+ }
5055
+ /**
5056
+ * 取当前页切片(纯函数,可单测)。
5057
+ * 作者 ddj 2026年09月02号
5058
+ * @param items 全量列表
5059
+ * @param page 当前页码(1 起)
5060
+ * @param pageSize 每页条数
5061
+ * @returns 当前页子数组
5062
+ */
5063
+ function pageSlice(items, page, pageSize) {
5064
+ const list = items ?? [];
5065
+ const { start, end } = paginate(list.length, pageSize, page);
5066
+ return list.slice(start, end);
5067
+ }
4169
5068
  /** 单行会话。 */
4170
5069
  function SessionRow({ row, checked, disabled, onToggle }) {
4171
5070
  const age = Math.max(0, (Date.now() - row.mtime) / 864e5);
@@ -4184,6 +5083,63 @@ window.__ModuleLoader__.load({
4184
5083
  }, when), row.active ? react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-active" }, "活跃") : null);
4185
5084
  }
4186
5085
  /**
5086
+ * 工作区列表分页器:顶部文案 + 上一页/下一页交互。
5087
+ * 作者 ddj 2026年09月02号
5088
+ * @param {object} info paginate() 的分页信息
5089
+ * @param {Function} onPrev 上一页回调
5090
+ * @param {Function} onNext 下一页回调
5091
+ */
5092
+ function WorkspacePager({ info, onPrev, onNext }) {
5093
+ const { page, pages, start, end, total } = info;
5094
+ return react.default.createElement("div", { className: "vsm-perf-pager" }, react.default.createElement("span", { className: "vsm-perf-pager-text" }, total ? `第 ${start + 1}–${end} 个 / 共 ${total} 个工作区(第 ${page}/${pages} 页)` : "暂无工作区"), react.default.createElement("div", { className: "vsm-perf-pager-actions" }, react.default.createElement("button", {
5095
+ className: "vsm-secondary vsm-small",
5096
+ onClick: onPrev,
5097
+ disabled: page <= 1
5098
+ }, "‹ 上一页"), react.default.createElement("button", {
5099
+ className: "vsm-secondary vsm-small",
5100
+ onClick: onNext,
5101
+ disabled: page >= pages
5102
+ }, "下一页 ›")));
5103
+ }
5104
+ /**
5105
+ * 工作区栏目(可展开/折叠),内含该工作区的会话行。
5106
+ * 作者 ddj 2026年09月02号
5107
+ * @param {object} ws 工作区汇总(workspaceKey / totalBytes)
5108
+ * @param {object[]} rows 该工作区的会话行
5109
+ * @param {boolean} expanded 是否展开
5110
+ * @param {Function} onToggleFold 展开/折叠切换回调
5111
+ * @param {object} selected 已选会话键集合
5112
+ * @param {boolean} locked 会话勾选是否禁用(移出执行中)
5113
+ * @param {Function} onToggleRow 单行勾选回调
5114
+ */
5115
+ function WorkspaceBlock({ ws, rows, expanded, onToggleFold, selected, locked, onToggleRow }) {
5116
+ const head = react.default.createElement("div", {
5117
+ className: "vsm-perf-ws-head",
5118
+ role: "button",
5119
+ tabIndex: 0,
5120
+ "aria-expanded": expanded,
5121
+ onClick: onToggleFold,
5122
+ onKeyDown: (event) => {
5123
+ if (event.key === "Enter" || event.key === " ") {
5124
+ event.preventDefault();
5125
+ onToggleFold();
5126
+ }
5127
+ }
5128
+ }, react.default.createElement("span", { className: "vsm-perf-ws-caret " + (expanded ? "open" : "") }, "▸"), react.default.createElement("span", {
5129
+ className: "vsm-perf-ws-name",
5130
+ title: ws.workspaceKey
5131
+ }, ws.workspaceKey), react.default.createElement("span", { className: "vsm-perf-ws-meta" }, rows.length + " 会话 · " + formatBytes(ws.totalBytes)));
5132
+ if (!expanded) return react.default.createElement("div", { className: "vsm-perf-ws" }, head);
5133
+ const body = rows.length ? rows.map((row) => react.default.createElement(SessionRow, {
5134
+ key: keyOf(row.workspaceKey, row.sessionId),
5135
+ row,
5136
+ checked: !!selected[keyOf(row.workspaceKey, row.sessionId)],
5137
+ disabled: row.active || locked,
5138
+ onToggle: () => onToggleRow(keyOf(row.workspaceKey, row.sessionId))
5139
+ })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "无会话");
5140
+ return react.default.createElement("div", { className: "vsm-perf-ws expanded" }, head, react.default.createElement("div", { className: "vsm-perf-rows" }, body));
5141
+ }
5142
+ /**
4187
5143
  * 会话卫生 + 压缩调优设置子页。
4188
5144
  * @author ddj 2026年09月02号
4189
5145
  */
@@ -4195,6 +5151,26 @@ window.__ModuleLoader__.load({
4195
5151
  const [confirm, setConfirm] = react.default.useState(null);
4196
5152
  const [moved, setMoved] = react.default.useState(null);
4197
5153
  const [config, setConfig] = react.default.useState(null);
5154
+ const [expanded, setExpanded] = react.default.useState(() => foldLoad());
5155
+ const [page, setPage] = react.default.useState(1);
5156
+ react.default.useEffect(() => {
5157
+ foldSave(expanded);
5158
+ }, [expanded]);
5159
+ /** 数据变化后把越界页码夹回有效范围。 */
5160
+ const workspaces = inv?.workspaces ?? [];
5161
+ const pageInfo = paginate(workspaces.length, PAGE_SIZE, page);
5162
+ react.default.useEffect(() => {
5163
+ if (page !== pageInfo.page) setPage(pageInfo.page);
5164
+ }, [page, pageInfo.page]);
5165
+ /** 切换某工作区栏目的展开/折叠(默认全折叠)。 */
5166
+ const toggleFold = (key) => setExpanded((old) => old.includes(key) ? old.filter((item) => item !== key) : old.concat(key));
5167
+ /** 切换单会话勾选。 */
5168
+ const toggleRow = (k) => setSelected((old) => {
5169
+ const next = { ...old };
5170
+ if (next[k]) delete next[k];
5171
+ else next[k] = true;
5172
+ return next;
5173
+ });
4198
5174
  const load = react.default.useCallback(() => {
4199
5175
  setError("");
4200
5176
  Promise.all([rpc("edrv.perf.inventory", {}), rpc("edrv.perf.configGet", {})]).then(([i, c]) => {
@@ -4297,30 +5273,25 @@ window.__ModuleLoader__.load({
4297
5273
  };
4298
5274
  const totals = inv?.totals;
4299
5275
  const summaryCards = totals ? react.default.createElement("div", { className: "vsm-perf-cards" }, react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "工作区"), react.default.createElement("b", null, String(totals.workspaces))), react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "会话数"), react.default.createElement("b", null, String(totals.sessions))), react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "压缩态总体积"), react.default.createElement("b", { className: totals.totalBytes >= 52428800 ? "big" : "" }, formatBytes(totals.totalBytes)))) : null;
4300
- const workspaceBlocks = inv?.workspaces ? inv.workspaces.map((ws) => {
4301
- const rows = inv.sessions.filter((s) => s.workspaceKey === ws.workspaceKey);
4302
- return react.default.createElement("div", {
4303
- key: ws.workspaceKey,
4304
- className: "vsm-perf-ws"
4305
- }, react.default.createElement("div", { className: "vsm-perf-ws-head" }, react.default.createElement("span", {
4306
- className: "vsm-perf-ws-name",
4307
- title: ws.workspaceKey
4308
- }, ws.workspaceKey), react.default.createElement("span", { className: "vsm-perf-ws-meta" }, rows.length + " 会话 · " + formatBytes(ws.totalBytes))), react.default.createElement("div", { className: "vsm-perf-rows" }, rows.length ? rows.map((row) => react.default.createElement(SessionRow, {
4309
- key: keyOf(row.workspaceKey, row.sessionId),
4310
- row,
4311
- checked: !!selected[keyOf(row.workspaceKey, row.sessionId)],
4312
- disabled: row.active || busy === "move",
4313
- onToggle: () => {
4314
- const k = keyOf(row.workspaceKey, row.sessionId);
4315
- setSelected((old) => {
4316
- const next = { ...old };
4317
- if (next[k]) delete next[k];
4318
- else next[k] = true;
4319
- return next;
4320
- });
4321
- }
4322
- })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "无会话")));
5276
+ const visible = pageSlice(workspaces, pageInfo.page, PAGE_SIZE);
5277
+ const workspacePager = workspaces.length > PAGE_SIZE ? react.default.createElement(WorkspacePager, {
5278
+ info: pageInfo,
5279
+ onPrev: () => setPage((old) => Math.max(1, old - 1)),
5280
+ onNext: () => setPage((old) => Math.min(pageInfo.pages, old + 1))
4323
5281
  }) : null;
5282
+ const workspaceBlocks = workspaces.length ? react.default.createElement("div", { className: "vsm-perf-ws-list" }, visible.map((ws) => {
5283
+ const key = normalizeFoldKey(ws.workspaceKey);
5284
+ return react.default.createElement(WorkspaceBlock, {
5285
+ key: ws.workspaceKey,
5286
+ ws,
5287
+ rows: inv.sessions.filter((s) => s.workspaceKey === ws.workspaceKey),
5288
+ expanded: expanded.includes(key),
5289
+ onToggleFold: () => toggleFold(key),
5290
+ selected,
5291
+ locked: busy === "move",
5292
+ onToggleRow: toggleRow
5293
+ });
5294
+ })) : null;
4324
5295
  const selCount = Object.keys(selected).length;
4325
5296
  const confirmBody = confirm ? react.default.createElement("div", { className: "vsm-perf-modal" }, react.default.createElement("div", { className: "vsm-perf-modal-card" }, react.default.createElement("h3", null, "确认移出 " + confirm.items.length + " 个会话?"), react.default.createElement("p", { className: "vsm-perf-modal-note" }, "将从 ~/.dsh/sessions 移出到 ~/.dsh/sessions-archive(可逆,重启后不再回放,释放 " + formatBytes(confirm.reclaimedBytes) + ")。活跃会话已排除。"), react.default.createElement("ul", { className: "vsm-perf-modal-list" }, confirm.items.slice(0, 20).map((item) => react.default.createElement("li", { key: item.workspaceKey + item.sessionId }, item.workspaceKey + " / " + item.sessionId + "(" + formatBytes(item.bytes) + ")"))), confirm.items.length > 20 ? react.default.createElement("div", { className: "vsm-perf-modal-more" }, "…等 " + confirm.items.length + " 个") : null, react.default.createElement("div", { className: "vsm-perf-modal-actions" }, react.default.createElement("button", {
4326
5297
  className: "vsm-primary",
@@ -4330,15 +5301,15 @@ window.__ModuleLoader__.load({
4330
5301
  className: "vsm-secondary",
4331
5302
  onClick: () => setConfirm(null)
4332
5303
  }, "取消")))) : null;
4333
- const movedBody = moved?.moved?.length ? react.default.createElement("div", { className: "vsm-perf-moved" }, react.default.createElement("h3", null, "已移出 " + moved.moved.length + " 个会话(释放 " + formatBytes(moved.reclaimedBytes) + ")"), react.default.createElement("div", { className: "vsm-perf-rows" }, moved.moved.map((item) => react.default.createElement("div", {
5304
+ const movedBody = moved?.moved?.length ? react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "已移出 " + moved.moved.length + " 个会话(释放 " + formatBytes(moved.reclaimedBytes) + ")"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-perf-rows" }, moved.moved.map((item) => react.default.createElement("div", {
4334
5305
  key: item.workspaceKey + item.sessionId,
4335
5306
  className: "vsm-perf-row"
4336
5307
  }, react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-id" }, item.sessionId), react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-size" }, formatBytes(item.bytes)), react.default.createElement("button", {
4337
5308
  className: "vsm-secondary vsm-small",
4338
5309
  onClick: () => doRestore(item),
4339
5310
  disabled: busy === "restore"
4340
- }, "恢复")))), moved.failures?.length ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "失败:" + moved.failures.join(";")) : null) : null;
4341
- const configBody = config ? react.default.createElement("div", { className: "vsm-perf-config" }, react.default.createElement("h3", null, "DSH 内置压缩调优"), react.default.createElement("p", null, "调低 compaction-basic / tool-result-pruner 触发阈值,让长循环会话更早压缩,降低模型可见上下文压力(减少 token 与防 context overflow)。"), react.default.createElement("p", { className: "vsm-perf-note" }, "注意:压缩只追加 compaction 事件,不重写已持久化会话日志(append-only),因此不会缩小已存在的会话文件——存量瘦身请用上方「移出到归档」。packChunks 已默认开启,无需配置。"), react.default.createElement("div", { className: "vsm-perf-config-state" }, react.default.createElement("span", { className: "vsm-perf-cell" }, "状态:" + (config.applied ? "已应用" : "未应用")), config.applied ? react.default.createElement("button", {
5311
+ }, "恢复")))), moved.failures?.length ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "失败:" + moved.failures.join(";")) : null)) : null;
5312
+ const configBody = config ? react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "DSH 内置压缩调优"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("p", null, "调低 compaction-basic / tool-result-pruner 触发阈值,让长循环会话更早压缩,降低模型可见上下文压力(减少 token 与防 context overflow)。"), react.default.createElement("p", { className: "vsm-perf-note" }, "注意:压缩只追加 compaction 事件,不重写已持久化会话日志(append-only),因此不会缩小已存在的会话文件——存量瘦身请用上方「移出到归档」。packChunks 已默认开启,无需配置。"), react.default.createElement("div", { className: "vsm-perf-config-state" }, react.default.createElement("span", { className: "vsm-perf-cell" }, "状态:" + (config.applied ? "已应用" : "未应用")), config.applied ? react.default.createElement("button", {
4342
5313
  className: "vsm-secondary vsm-small",
4343
5314
  onClick: undoConfig,
4344
5315
  disabled: busy === "undo"
@@ -4346,9 +5317,9 @@ window.__ModuleLoader__.load({
4346
5317
  className: "vsm-primary vsm-small",
4347
5318
  onClick: applyConfig,
4348
5319
  disabled: busy === "apply"
4349
- }, "应用建议配置(需重启)")), react.default.createElement("pre", { className: "vsm-perf-block" }, config.block)) : null;
4350
- const guideBody = react.default.createElement("div", { className: "vsm-perf-guide" }, react.default.createElement("h3", null, "侧车摘要(agent/脚本取关键字段,不整份读 sidecar)"), react.default.createElement("p", null, "工作区根目录的 .dsh-edit-review.json(可能数 MB)只应通过摘要接口读取,禁止 agent 整份 read/write 进对话:"), react.default.createElement("pre", { className: "vsm-perf-block" }, "curl -s -X POST http://127.0.0.1:3080/edrv/rpc -H 'content-type: application/json' -d '{\"method\":\"edrv.perf.sidecarSummary\",\"args\":{}}'\n# 差异记录列表(含每文件 pending 摘要):edrv.list\n# 历史归档:edrv.archiveList / edrv.archiveRead"));
4351
- return react.default.createElement("section", { className: "vsm-perf-page" }, react.default.createElement("h2", null, "会话性能"), react.default.createElement("p", null, "DSH 启动会回放 ~/.dsh/sessions 全部会话(V8 展开约 10×),体积越大内存越吃紧。定期把巨型/旧会话移出到归档可显著降低启动内存。"), error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null, summaryCards, react.default.createElement("div", { className: "vsm-perf-toolbar" }, react.default.createElement("button", {
5320
+ }, "应用建议配置(需重启)")), react.default.createElement("pre", { className: "vsm-perf-block" }, config.block))) : null;
5321
+ const guideBody = react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "侧车摘要(agent/脚本取关键字段,不整份读 sidecar)"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("p", null, "工作区根目录的 .dsh-edit-review.json(可能数 MB)只应通过摘要接口读取,禁止 agent 整份 read/write 进对话:"), react.default.createElement("pre", { className: "vsm-perf-block" }, "curl -s -X POST http://127.0.0.1:3080/edrv/rpc -H 'content-type: application/json' -d '{\"method\":\"edrv.perf.sidecarSummary\",\"args\":{}}'\n# 差异记录列表(含每文件 pending 摘要):edrv.list\n# 历史归档:edrv.archiveList / edrv.archiveRead")));
5322
+ return react.default.createElement("section", { className: "vsm-perf-page" }, react.default.createElement("h2", null, "会话性能"), react.default.createElement("p", null, "DSH 启动会回放 ~/.dsh/sessions 全部会话(V8 展开约 10×),体积越大内存越吃紧。定期把巨型/旧会话移出到归档可显著降低启动内存。"), error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null, summaryCards, react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "会话盘点"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-perf-toolbar" }, react.default.createElement("button", {
4352
5323
  className: "vsm-secondary vsm-small",
4353
5324
  onClick: selectBig
4354
5325
  }, "圈选 ≥1MB 非活跃"), react.default.createElement("button", {
@@ -4367,15 +5338,14 @@ window.__ModuleLoader__.load({
4367
5338
  className: "vsm-secondary vsm-small",
4368
5339
  onClick: load,
4369
5340
  disabled: busy === "load"
4370
- }, "⟳ 刷新")), workspaceBlocks, movedBody, configBody, guideBody, confirmBody);
5341
+ }, "⟳ 刷新")), workspacePager, workspaceBlocks)), movedBody, configBody, guideBody, confirmBody);
4371
5342
  }
4372
5343
  //#endregion
4373
5344
  //#region src/client/ui/McpSettings.ts
4374
5345
  /**
4375
- * dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理。
4376
- * 子 Tab:通用(文件打开工具/开发形态)/ 快捷键(编辑器键位配置,可重置)/
4377
- * 我的 MCP(profile 全局)/ 项目 MCP(各项目 .mcp.json)/ MCP 市场(占位)/ 兼容性。
4378
- * 作者 ddj 2026年08月22号 / 2026年08月26号
5346
+ * dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性。
5347
+ * MCP 管理 Tab 下含三个子页签:我的 MCP(profile 全局)/ 项目 MCP(各项目 .mcp.json)/ MCP 市场(占位)。
5348
+ * 作者 ddj 2026年08月22号 / 2026年08月26号 / 2026年08月27号
4379
5349
  */
4380
5350
  const EMPTY = {
4381
5351
  serverName: "",
@@ -4588,14 +5558,14 @@ window.__ModuleLoader__.load({
4588
5558
  disabled: devBusy,
4589
5559
  onClick: closeDevForm
4590
5560
  }, devBusy ? "切换中…" : "关闭开发形态"))) : null;
4591
- return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "通用"), react.default.createElement("p", null, "配置 DSH 对话中文件链接的打开工具。工具由当前运行时自动扫描。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用自动选择。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "文件链接使用工具"), react.default.createElement("select", {
5561
+ return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "通用"), react.default.createElement("p", null, "配置 DSH 对话中文件链接的打开工具。工具由当前运行时自动扫描。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用自动选择。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "文件与安装"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "文件链接使用工具"), react.default.createElement("select", {
4592
5562
  value: currentOption,
4593
5563
  disabled: loading || unavailable || notReady || busy || snapshot?.writable === false,
4594
5564
  onChange: (event) => save(event.target.value)
4595
5565
  }, options.map((item) => react.default.createElement("option", {
4596
5566
  key: item.id,
4597
5567
  value: item.id
4598
- }, item.label))), loading && react.default.createElement("small", null, "读取中…")), devFormRow);
5568
+ }, item.label))), loading && react.default.createElement("small", null, "读取中…")), devFormRow)));
4599
5569
  }
4600
5570
  /** 兼容性小节:host 报告(RPC compat)+ 客户端本地适配项。 */
4601
5571
  function CompatSection({ getSummary }) {
@@ -4618,12 +5588,111 @@ window.__ModuleLoader__.load({
4618
5588
  key: item.name,
4619
5589
  className: "vsm-compat-item " + (item.active ? "ok" : "warn")
4620
5590
  }, react.default.createElement("span", { className: "vsm-compat-name" }, item.name), react.default.createElement("span", { className: "vsm-compat-note" }, item.note ?? (item.active ? "正常" : "未生效"))));
5591
+ /** 兼容性分组面板:标题 + 线框内容区。 */
5592
+ const group = (title, children) => react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, title), react.default.createElement("div", { className: "vsm-panel-body" }, children));
4621
5593
  const warnings = report?.warnings ?? [];
4622
5594
  const devForm = report?.devForm;
4623
- return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + (report?.pluginVersion ? "(插件版本 " + report.pluginVersion + ")" : "")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), devForm?.enabled && react.default.createElement(react.default.Fragment, null, react.default.createElement("h3", { className: "vsm-compat-h3" }, "开发形态"), react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "开发形态开启(工作区链接安装)"), react.default.createElement("span", { className: "vsm-compat-note" }, devForm.path))), react.default.createElement("h3", { className: "vsm-compat-h3" }, "外部插件与服务"), renderItems(report?.external), react.default.createElement("h3", { className: "vsm-compat-h3" }, "客户端适配"), renderItems(getSummary?.() ?? []), react.default.createElement("h3", { className: "vsm-compat-h3" }, "护栏"), renderItems(report?.guards), react.default.createElement("h3", { className: "vsm-compat-h3" }, "警告"), warnings.length ? warnings.map((w, index) => react.default.createElement("div", {
5595
+ return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + (report?.pluginVersion ? "(插件版本 " + report.pluginVersion + ")" : "")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), devForm?.enabled && group("开发形态", react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "开发形态开启(工作区链接安装)"), react.default.createElement("span", { className: "vsm-compat-note" }, devForm.path))), group("外部插件与服务", renderItems(report?.external)), group("客户端适配", renderItems(getSummary?.() ?? [])), group("护栏", renderItems(report?.guards)), group("警告", warnings.length ? warnings.map((w, index) => react.default.createElement("div", {
4624
5596
  key: index,
4625
5597
  className: "vsm-mcp-error vsm-mcp-banner"
4626
- }, w)) : react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "未检测到兼容性问题"), react.default.createElement("span", { className: "vsm-compat-note" }, "")));
5598
+ }, w)) : react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "未检测到兼容性问题"), react.default.createElement("span", { className: "vsm-compat-note" }, ""))));
5599
+ }
5600
+ /** MCP 管理子页签定义(我的 / 项目 / 市场)。 */
5601
+ const MCP_TABS = [
5602
+ {
5603
+ id: "mine",
5604
+ label: "我的 MCP"
5605
+ },
5606
+ {
5607
+ id: "projects",
5608
+ label: "项目 MCP"
5609
+ },
5610
+ {
5611
+ id: "market",
5612
+ label: "MCP 市场"
5613
+ }
5614
+ ];
5615
+ /**
5616
+ * MCP 管理面板:顶层「MCP 管理」Tab 下的三个子页签容器。
5617
+ * 作者 ddj 2026年08月27号
5618
+ * @param {object[]} servers 全局 MCP 服务列表
5619
+ * @param {object[]} projects 项目及其项目级 MCP 列表
5620
+ * @param {string} busy 正在执行的操作标识
5621
+ * @param {object} draft 新增表单草稿
5622
+ * @param {Function} edit 草稿字段变更回调
5623
+ * @param {Function} resetDraft 清空草稿回调
5624
+ */
5625
+ function McpManagePanel({ servers, projects, busy, draft, edit, resetDraft, refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject, saveGlobal, saveProject }) {
5626
+ const [mcpTab, setMcpTab] = react.default.useState("mine");
5627
+ const [showForm, setShowForm] = react.default.useState(false);
5628
+ const [projectForm, setProjectForm] = react.default.useState(null);
5629
+ const [selectedPath, setSelectedPath] = react.default.useState("");
5630
+ react.default.useEffect(() => {
5631
+ if (!projects.length) {
5632
+ setSelectedPath("");
5633
+ return;
5634
+ }
5635
+ if (!projects.some((project) => project.workspacePath === selectedPath)) setSelectedPath(projects[0].workspacePath);
5636
+ }, [projects, selectedPath]);
5637
+ const closeGlobalForm = () => setShowForm(false);
5638
+ const closeProjectForm = () => setProjectForm(null);
5639
+ const addProject = (project) => {
5640
+ resetDraft?.();
5641
+ setProjectForm({
5642
+ workspacePath: project.workspacePath,
5643
+ title: project.title
5644
+ });
5645
+ };
5646
+ const selectedProject = projects.find((project) => project.workspacePath === selectedPath);
5647
+ const mineBody = servers.length ? servers.map((s) => react.default.createElement(ServerCard, {
5648
+ key: s.id,
5649
+ server: s,
5650
+ onRefresh: refreshGlobal,
5651
+ onToggle: toggleGlobal,
5652
+ onRemove: removeGlobal
5653
+ })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP");
5654
+ const projectBody = projects.length ? react.default.createElement(react.default.Fragment, null, react.default.createElement(ProjectPicker, {
5655
+ projects,
5656
+ value: selectedPath,
5657
+ onChange: setSelectedPath
5658
+ }), selectedProject ? react.default.createElement(ProjectGroup, {
5659
+ project: selectedProject,
5660
+ busy,
5661
+ onAdd: addProject,
5662
+ onRefresh: refreshProject,
5663
+ onToggle: toggleProject,
5664
+ onRemove: removeProject
5665
+ }) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目")) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
5666
+ let body;
5667
+ if (mcpTab === "projects") body = projectBody;
5668
+ else if (mcpTab === "market") body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "MCP 市场暂未接入");
5669
+ else body = mineBody;
5670
+ const form = showForm ? react.default.createElement(McpForm, {
5671
+ title: "添加全局 MCP",
5672
+ draft,
5673
+ busy,
5674
+ edit,
5675
+ save: () => saveGlobal(closeGlobalForm),
5676
+ close: closeGlobalForm
5677
+ }) : projectForm ? react.default.createElement(McpForm, {
5678
+ title: "添加项目 MCP · " + projectForm.title,
5679
+ draft,
5680
+ busy,
5681
+ edit,
5682
+ save: () => saveProject(projectForm.workspacePath, closeProjectForm),
5683
+ close: closeProjectForm
5684
+ }) : null;
5685
+ return react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-mcp-subhead" }, react.default.createElement("nav", { className: "vsm-mcp-subtabs" }, MCP_TABS.map((item) => react.default.createElement("button", {
5686
+ key: item.id,
5687
+ className: mcpTab === item.id ? "active" : "",
5688
+ onClick: () => setMcpTab(item.id)
5689
+ }, item.label))), react.default.createElement("button", {
5690
+ className: "vsm-primary vsm-small",
5691
+ onClick: () => {
5692
+ resetDraft?.();
5693
+ setShowForm(true);
5694
+ }
5695
+ }, "+ 添加全局 MCP")), body, form);
4627
5696
  }
4628
5697
  /** MCP 管理主面板。 */
4629
5698
  function McpSettings(props) {
@@ -4636,16 +5705,6 @@ window.__ModuleLoader__.load({
4636
5705
  const [error, setError] = react.default.useState("");
4637
5706
  const [tab, setTab] = react.default.useState("general");
4638
5707
  const [draft, setDraft] = react.default.useState(EMPTY);
4639
- const [showForm, setShowForm] = react.default.useState(false);
4640
- const [projectForm, setProjectForm] = react.default.useState(null);
4641
- const [selectedPath, setSelectedPath] = react.default.useState("");
4642
- react.default.useEffect(() => {
4643
- if (!projects.length) {
4644
- setSelectedPath("");
4645
- return;
4646
- }
4647
- if (!projects.some((project) => project.workspacePath === selectedPath)) setSelectedPath(projects[0].workspacePath);
4648
- }, [projects, selectedPath]);
4649
5708
  const loadAll = react.default.useCallback(() => {
4650
5709
  setLoading(true);
4651
5710
  setError("");
@@ -4695,112 +5754,87 @@ window.__ModuleLoader__.load({
4695
5754
  workspacePath: p.workspacePath,
4696
5755
  serverName: s.serverName
4697
5756
  }, replaceProject(p.workspacePath));
4698
- const saveGlobal = () => {
5757
+ /**
5758
+ * 保存全局 MCP:成功后由回调关闭子面板。
5759
+ * 作者 ddj 2026年08月27号
5760
+ * @param {Function} close 关闭新增表单回调
5761
+ */
5762
+ const saveGlobal = (close) => {
4699
5763
  setBusy("save");
4700
5764
  setError("");
4701
5765
  rpc("mcp.save", { config: configOf(draft) }).then(finish).then((result) => {
4702
5766
  setServers((old) => old.some((s) => s.id === result.server.id) ? old.map((s) => s.id === result.server.id ? result.server : s) : old.concat(result.server));
4703
5767
  setDraft(EMPTY);
4704
- setShowForm(false);
5768
+ close();
4705
5769
  }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4706
5770
  };
4707
- const saveProject = () => {
4708
- if (!projectForm) return;
5771
+ /**
5772
+ * 保存项目 MCP:成功后由回调关闭子面板。
5773
+ * 作者 ddj 2026年08月27号
5774
+ * @param {string} workspacePath 目标项目路径
5775
+ * @param {Function} close 关闭新增表单回调
5776
+ */
5777
+ const saveProject = (workspacePath, close) => {
4709
5778
  setBusy("project-save");
4710
5779
  setError("");
4711
5780
  const config = configOf(draft);
4712
5781
  rpc("mcp.projectSave", {
4713
- workspacePath: projectForm.workspacePath,
5782
+ workspacePath,
4714
5783
  serverName: config.serverName,
4715
5784
  config
4716
5785
  }).then(finish).then((result) => {
4717
- setProjects((old) => old.map((p) => p.workspacePath === projectForm.workspacePath ? result.project : p));
5786
+ setProjects((old) => old.map((p) => p.workspacePath === workspacePath ? result.project : p));
4718
5787
  setDraft(EMPTY);
4719
- setProjectForm(null);
5788
+ close();
4720
5789
  }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4721
5790
  };
4722
5791
  const edit = (key, value) => setDraft((old) => ({
4723
5792
  ...old,
4724
5793
  [key]: value
4725
5794
  }));
4726
- const marketBody = react.default.createElement("div", { className: "vsm-mcp-empty" }, "MCP 市场暂未接入");
4727
- const selectedProject = projects.find((project) => project.workspacePath === selectedPath);
4728
- const projectBody = projects.length ? react.default.createElement(react.default.Fragment, null, react.default.createElement(ProjectPicker, {
4729
- projects,
4730
- value: selectedPath,
4731
- onChange: setSelectedPath
4732
- }), selectedProject ? react.default.createElement(ProjectGroup, {
4733
- project: selectedProject,
4734
- busy,
4735
- onAdd: (project) => {
4736
- setDraft(EMPTY);
4737
- setProjectForm({
4738
- workspacePath: project.workspacePath,
4739
- title: project.title
4740
- });
4741
- },
4742
- onRefresh: refreshProject,
4743
- onToggle: toggleProject,
4744
- onRemove: removeProject
4745
- }) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目")) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
5795
+ const resetDraft = () => setDraft(EMPTY);
4746
5796
  let body;
4747
5797
  if (loading) body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取 MCP 服务…");
4748
5798
  else if (tab === "general") body = react.default.createElement(GeneralSettings, { registry: openerRegistry });
4749
5799
  else if (tab === "keys") body = react.default.createElement(KeybindingsPanel, null);
4750
- else if (tab === "mine") body = react.default.createElement("div", null, servers.length ? servers.map((s) => react.default.createElement(ServerCard, {
4751
- key: s.id,
4752
- server: s,
4753
- onRefresh: refreshGlobal,
4754
- onToggle: toggleGlobal,
4755
- onRemove: removeGlobal
4756
- })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP"));
4757
- else if (tab === "projects") body = projectBody;
4758
- else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
4759
- else if (tab === "perf") body = react.default.createElement(PerfSettings, null);
4760
- else body = marketBody;
4761
- const form = showForm ? react.default.createElement(McpForm, {
4762
- title: "添加全局 MCP",
4763
- draft,
5800
+ else if (tab === "mcp") body = react.default.createElement(McpManagePanel, {
5801
+ servers,
5802
+ projects,
4764
5803
  busy,
4765
- edit,
4766
- save: saveGlobal,
4767
- close: () => setShowForm(false)
4768
- }) : projectForm ? react.default.createElement(McpForm, {
4769
- title: "添加项目 MCP · " + projectForm.title,
4770
5804
  draft,
4771
- busy,
4772
5805
  edit,
4773
- save: saveProject,
4774
- close: () => setProjectForm(null)
4775
- }) : null;
4776
- return react.default.createElement("section", { className: "vsm-mcp-page" }, react.default.createElement("header", { className: "vsm-mcp-header" }, react.default.createElement("div", null, react.default.createElement("h2", null, "VSCodeMode"), react.default.createElement("p", null, "管理当前 profile 与各项目的 Model Context Protocol 服务。")), react.default.createElement("button", {
4777
- className: "vsm-primary",
4778
- onClick: () => {
4779
- setDraft(EMPTY);
4780
- setShowForm(true);
4781
- }
4782
- }, "+ 添加全局 MCP")), react.default.createElement("nav", { className: "vsm-mcp-tabs" }, react.default.createElement("button", {
5806
+ resetDraft,
5807
+ saveGlobal: (close) => saveGlobal(close),
5808
+ saveProject: (workspacePath, close) => saveProject(workspacePath, close),
5809
+ refreshGlobal,
5810
+ toggleGlobal,
5811
+ removeGlobal,
5812
+ refreshProject,
5813
+ toggleProject,
5814
+ removeProject
5815
+ });
5816
+ else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
5817
+ else if (tab === "lsp") body = react.default.createElement(LspSettings, null);
5818
+ else body = react.default.createElement(PerfSettings, null);
5819
+ return react.default.createElement("section", { className: "vsm-mcp-page" }, react.default.createElement("header", { className: "vsm-mcp-header" }, react.default.createElement("div", null, react.default.createElement("h2", null, "VSCodeMode"), react.default.createElement("p", null, "管理当前 profile 与各项目的 Model Context Protocol 服务。"))), react.default.createElement("nav", { className: "vsm-mcp-tabs" }, react.default.createElement("button", {
4783
5820
  className: tab === "general" ? "active" : "",
4784
5821
  onClick: () => setTab("general")
4785
5822
  }, "通用"), react.default.createElement("button", {
4786
5823
  className: tab === "keys" ? "active" : "",
4787
5824
  onClick: () => setTab("keys")
4788
5825
  }, "快捷键"), react.default.createElement("button", {
4789
- className: tab === "mine" ? "active" : "",
4790
- onClick: () => setTab("mine")
4791
- }, "我的 MCP"), react.default.createElement("button", {
4792
- className: tab === "projects" ? "active" : "",
4793
- onClick: () => setTab("projects")
4794
- }, "项目 MCP"), react.default.createElement("button", {
5826
+ className: tab === "mcp" ? "active" : "",
5827
+ onClick: () => setTab("mcp")
5828
+ }, "MCP 管理"), react.default.createElement("button", {
5829
+ className: tab === "lsp" ? "active" : "",
5830
+ onClick: () => setTab("lsp")
5831
+ }, "语言服务器"), react.default.createElement("button", {
4795
5832
  className: tab === "perf" ? "active" : "",
4796
5833
  onClick: () => setTab("perf")
4797
5834
  }, "性能优化"), react.default.createElement("button", {
4798
- className: tab === "market" ? "active" : "",
4799
- onClick: () => setTab("market")
4800
- }, "MCP 市场"), react.default.createElement("button", {
4801
5835
  className: tab === "compat" ? "active" : "",
4802
5836
  onClick: () => setTab("compat")
4803
- }, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body, form);
5837
+ }, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body);
4804
5838
  }
4805
5839
  /** MCP 新增表单(全局/项目共用)。 */
4806
5840
  function McpForm({ title, draft, busy, edit, save, close }) {
@@ -6393,12 +7427,36 @@ window.__ModuleLoader__.load({
6393
7427
  computeEnds(out, lines.length);
6394
7428
  return out;
6395
7429
  }
6396
- /** Lua:`function Foo:bar` / `M.bar = class|function`。 */
7430
+ /** Lua:`function Foo:bar` / `M.bar = class|function` + EmmyLua 注解(---@class/@alias/@enum/@field)。 */
6397
7431
  function parseLua(text) {
6398
7432
  const out = [];
6399
7433
  const lines = text.split("\n");
7434
+ /** 当前注解类(收拢紧随的 ---@field 为字段子节点)。 */
7435
+ let pendingClass = null;
6400
7436
  for (let i = 0; i < lines.length; i++) {
6401
7437
  const line = lines[i];
7438
+ const am = /^[ \t]*---@(class|alias|enum|field|type)\s+([^\r\n]+)/.exec(line);
7439
+ if (am) {
7440
+ const tag = am[1];
7441
+ const name = (am[2].trim().match(/^([A-Za-z_][\w]*)/) || [])[1] ?? "";
7442
+ if (tag === "class") {
7443
+ if (name) {
7444
+ const item = mk(i, name, SK.Class, line);
7445
+ item.children = [];
7446
+ out.push(item);
7447
+ pendingClass = item;
7448
+ } else pendingClass = null;
7449
+ } else if (tag === "alias") {
7450
+ if (name) out.push(mk(i, name, SK.TypeParameter, line));
7451
+ pendingClass = null;
7452
+ } else if (tag === "enum") {
7453
+ if (name) out.push(mk(i, name, SK.Enum, line));
7454
+ pendingClass = null;
7455
+ } else if (tag === "field") {
7456
+ if (name && pendingClass) pendingClass.children.push(mk(i, name, SK.Field, line));
7457
+ } else if (tag === "type") pendingClass = null;
7458
+ continue;
7459
+ }
6402
7460
  if (/^\s*--/.test(line)) continue;
6403
7461
  const fm = /^[ \t]*(?:local[ \t]+)?function[ \t]+([A-Za-z_][\w.:]*)/.exec(line);
6404
7462
  if (fm) {
@@ -6756,6 +7814,7 @@ window.__ModuleLoader__.load({
6756
7814
  setError(null);
6757
7815
  resolveOutline(sources, {
6758
7816
  languageId: model.getLanguageId(),
7817
+ path: modelPath,
6759
7818
  model,
6760
7819
  editor: ed,
6761
7820
  monaco: window.monaco
@@ -6939,6 +7998,43 @@ window.__ModuleLoader__.load({
6939
7998
  render: (ctx) => react.default.createElement(OutlinePanel, { ctx })
6940
7999
  };
6941
8000
  }
8001
+ /** LSP 大纲源:lua/csharp 等有 host 服务器能力的语言。 */
8002
+ function createLspOutlineSource() {
8003
+ return {
8004
+ id: "lsp",
8005
+ priority: 60,
8006
+ provides: (languageId) => languageId === "lua" || languageId === "csharp",
8007
+ async get(input) {
8008
+ const model = input.model;
8009
+ if (!model || typeof model.getValue !== "function") return [];
8010
+ const path = input.path || (model.uri && model.uri.path ? decodeURIComponent(String(model.uri.path).replace(/^\//, "")) : null);
8011
+ if (!path) return [];
8012
+ const symbols = await fetchDocumentSymbols(path, model.getValue());
8013
+ return (Array.isArray(symbols) ? symbols : []).map(toOutline).filter(Boolean);
8014
+ }
8015
+ };
8016
+ }
8017
+ /** LSP SymbolInfo(host 归一化,0-based range)→ 大纲 OutlineSymbol(1-based)。 */
8018
+ function toOutline(symbol) {
8019
+ if (!symbol || typeof symbol.name !== "string") return null;
8020
+ const range = symbol.range || {};
8021
+ const sel = symbol.selectionRange || range;
8022
+ const start = range.start || {};
8023
+ const select = sel.start || {};
8024
+ const out = {
8025
+ name: symbol.name,
8026
+ kind: typeof symbol.kind === "number" ? symbol.kind : 0,
8027
+ startLine: Math.max(1, (start.line ?? 0) + 1),
8028
+ endLine: Math.max(1, (range.end?.line ?? start.line ?? 0) + 1),
8029
+ selectLine: Math.max(1, (select.line ?? start.line ?? 0) + 1)
8030
+ };
8031
+ if (typeof symbol.detail === "string" && symbol.detail) out.detail = symbol.detail;
8032
+ if (Array.isArray(symbol.children) && symbol.children.length) {
8033
+ const kids = symbol.children.map(toOutline).filter(Boolean);
8034
+ if (kids.length) out.children = kids;
8035
+ }
8036
+ return out;
8037
+ }
6942
8038
  //#endregion
6943
8039
  //#region src/client/index.ts
6944
8040
  /**
@@ -6981,17 +8077,25 @@ window.__ModuleLoader__.load({
6981
8077
  if (typeof window !== "undefined" && monacoList && typeof monacoList.subscribe === "function") {
6982
8078
  const list = monacoList;
6983
8079
  const schedulePreload = () => {
6984
- const preload = () => loadMonaco(() => {}).catch(() => {});
8080
+ const preload = () => loadMonaco(() => {}).then((m) => setupLsp(m)).catch(() => {});
6985
8081
  if (typeof window.requestIdleCallback === "function") window.requestIdleCallback(preload, { timeout: 2e3 });
6986
8082
  else schedule(preload, 300);
6987
8083
  };
6988
8084
  const onSessionEnter = () => {
6989
- if (!list.getSnapshot()?.current) return;
8085
+ const current = list.getSnapshot()?.current;
8086
+ if (!current) return;
8087
+ setSession(current);
6990
8088
  schedulePreload();
6991
8089
  };
6992
8090
  ctx.effect(() => list.subscribe(onSessionEnter), "vscode-mode: monaco session trigger");
6993
8091
  onSessionEnter();
6994
8092
  }
8093
+ if (monacoList && typeof monacoList.subscribe === "function") {
8094
+ const list = monacoList;
8095
+ ctx.effect(() => list.subscribe(() => {
8096
+ setSession(list.getSnapshot()?.current);
8097
+ }), "vscode-mode: lsp session sync");
8098
+ }
6995
8099
  const originalOpenPath = workspaces?.openPath;
6996
8100
  const binder = pickSettingsBinder(ctx);
6997
8101
  const settings = binder.scope;
@@ -7035,6 +8139,7 @@ window.__ModuleLoader__.load({
7035
8139
  const outlineSources = createOutlineSourceRegistry();
7036
8140
  ctx.provide("edrvOutlineSources", outlineSources);
7037
8141
  ctx.effect(() => registerBuiltinOutlineSources(outlineSources), "vscode-mode: outline sources");
8142
+ ctx.effect(() => outlineSources.register(createLspOutlineSource()), "vscode-mode: outline lsp source");
7038
8143
  ctx.effect(() => sidebarPanels.register(createOutlinePanel()), "vscode-mode: sidebar panel outline");
7039
8144
  ctx.effect(() => registry.register({
7040
8145
  id: "system",