dsh-vscode-mode 0.1.30 → 0.1.35

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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/lib/client.js +1439 -46
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +3519 -222
  6. package/lib/index.js.map +1 -1
  7. package/package.json +2 -2
  8. package/src/capture.ts +3 -0
  9. package/src/client/events.ts +11 -0
  10. package/src/client/index.ts +20 -2
  11. package/src/client/monaco/lsp/index.ts +30 -0
  12. package/src/client/monaco/lsp/lspClient.ts +192 -0
  13. package/src/client/monaco/lsp/providers.ts +229 -0
  14. package/src/client/outline/OutlinePanel.tsx +1 -1
  15. package/src/client/outline/lspSource.ts +49 -0
  16. package/src/client/outline/parse.ts +31 -1
  17. package/src/client/outline/types.ts +2 -0
  18. package/src/client/paths.ts +36 -0
  19. package/src/client/sidebar/panels/FileExplorer.ts +95 -30
  20. package/src/client/sidebar/panels/SearchPanel.ts +11 -2
  21. package/src/client/state/explorerCache.ts +3 -1
  22. package/src/client/state/explorerEntriesCache.ts +172 -0
  23. package/src/client/state/viewStateCache.ts +3 -1
  24. package/src/client/styles/editor.css +16 -0
  25. package/src/client/styles/mcp.css +63 -0
  26. package/src/client/ui/EditorView.ts +61 -6
  27. package/src/client/ui/LspSettings.ts +268 -0
  28. package/src/client/ui/McpSettings.ts +5 -1
  29. package/src/client/ui/PerfSettings.ts +242 -0
  30. package/src/client/ui/PerfSizeBadge.ts +54 -0
  31. package/src/compat.ts +3 -2
  32. package/src/fileOpenSettings.ts +1 -0
  33. package/src/index.ts +25 -2
  34. package/src/lsp/client.ts +205 -0
  35. package/src/lsp/config.ts +107 -0
  36. package/src/lsp/extmgr.ts +208 -0
  37. package/src/lsp/jsonrpc.ts +86 -0
  38. package/src/lsp/manager.ts +133 -0
  39. package/src/lsp/providers.ts +235 -0
  40. package/src/lsp/rpc.ts +334 -0
  41. package/src/lsp/server.ts +346 -0
  42. package/src/lsp/settings.ts +44 -0
  43. package/src/lsp/transport.ts +143 -0
  44. package/src/lsp/uri.ts +62 -0
  45. package/src/lsp/zip.ts +82 -0
  46. package/src/paths.ts +284 -0
  47. package/src/perf.ts +340 -0
  48. package/src/perfPatch.ts +76 -0
  49. package/src/routes.ts +7 -12
  50. package/src/rpc.ts +184 -36
  51. package/src/search/content.ts +14 -7
  52. package/src/search/ripgrep.ts +54 -1
  53. package/src/search/types.ts +4 -1
  54. package/src/shared/lsp.ts +138 -0
  55. package/src/shared/rpc.ts +103 -2
  56. package/src/store.ts +2 -2
  57. package/src/treeIndex.ts +397 -0
  58. package/src/treeRead.ts +87 -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\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");
@@ -1838,15 +1838,42 @@ window.__ModuleLoader__.load({
1838
1838
  }
1839
1839
  }
1840
1840
  //#endregion
1841
+ //#region src/client/paths.ts
1842
+ /**
1843
+ * dsh-vscode-mode client — 浏览器端缓存键/路径统一出口(PathConst)。
1844
+ * 统一 localStorage 键前缀(各缓存模块/编辑区/搜索面板共用),避免内联散落;
1845
+ * 键带 schema 版本号,未来版本递增时经 sweepLegacyKeys 自动清旧。
1846
+ * 浏览器不涉 ~/.dsh 文件系统,这里只管键名 + shared 路由常量。
1847
+ * 作者 ddj 2026-09-01
1848
+ */
1849
+ /** localStorage 键前缀表(按功能隔离;均按会话拼接)。 */
1850
+ const CACHE_KEY = {
1851
+ /** 展开状态(explorerCache v1,当前使用)。 */
1852
+ expanded: "edrv.cache.explorer.v1.",
1853
+ /** 目录条目 SWR 缓存(explorerEntriesCache v2,当前使用)。 */
1854
+ entries: "edrv.cache.entries.v2.",
1855
+ /** 编辑区视图状态(viewStateCache v1)。 */
1856
+ viewstate: "edrv.cache.viewstate.v1.",
1857
+ /** 编辑器页签/活动文件(EditorView v2)。 */
1858
+ editor: "edrv.editor.v2.",
1859
+ /** 侧边栏状态(EditorView;按布局 side/central 追加段)。 */
1860
+ sidebar: "edrv.sidebar.",
1861
+ /** 搜索面板条件(SearchPanel v1)。 */
1862
+ search: "edrv.search.v1.",
1863
+ /** 侧边栏提示已关闭标记(全局,无会话)。 */
1864
+ sideHint: "edrv.side-hint-dismissed"
1865
+ };
1866
+ //#endregion
1841
1867
  //#region src/client/state/viewStateCache.ts
1842
1868
  /**
1843
1869
  * dsh-vscode-mode client — 编辑器视图状态持久化(纯函数,可单测)。
1844
1870
  * 对齐 VSCode workbench.editor.restoreViewState:恢复每个文件的光标/滚动/折叠位置。
1845
1871
  * 视图状态 = Monaco editor.saveViewState() 返回值(JSON 可序列化),按 path 存储。
1872
+ * 键前缀统一走 paths.ts PathConst(CACHE_KEY.viewstate)。
1846
1873
  * 作者 ddj 2026-08-28
1847
1874
  */
1848
1875
  /** localStorage 键前缀(按会话隔离)。 */
1849
- const KEY_PREFIX$1 = "edrv.cache.viewstate.v1.";
1876
+ const KEY_PREFIX$2 = CACHE_KEY.viewstate;
1850
1877
  /**
1851
1878
  * 判断对象是否为合法的 Monaco view state(保守形状校验,非深校验)。
1852
1879
  * @author ddj 2026年08月28号
@@ -1924,7 +1951,7 @@ window.__ModuleLoader__.load({
1924
1951
  */
1925
1952
  function viewStatesLoad(sessionId) {
1926
1953
  try {
1927
- return parseViewStates(window.localStorage.getItem(KEY_PREFIX$1 + String(sessionId))) ?? {};
1954
+ return parseViewStates(window.localStorage.getItem(KEY_PREFIX$2 + String(sessionId))) ?? {};
1928
1955
  } catch (error) {
1929
1956
  return {};
1930
1957
  }
@@ -1937,9 +1964,421 @@ window.__ModuleLoader__.load({
1937
1964
  */
1938
1965
  function viewStatesSave(sessionId, states) {
1939
1966
  try {
1940
- window.localStorage.setItem(KEY_PREFIX$1 + String(sessionId), serializeViewStates(states));
1967
+ window.localStorage.setItem(KEY_PREFIX$2 + String(sessionId), serializeViewStates(states));
1968
+ } catch (error) {}
1969
+ }
1970
+ //#endregion
1971
+ //#region src/shared/lsp.ts
1972
+ /**
1973
+ * Monaco 位置(1-based lineNumber/column)→ LSP Position(0-based)。
1974
+ * @author ddj 2026年08月27号
1975
+ * @param lineNumber Monaco 行(≥1)
1976
+ * @param column Monaco 列(≥1)
1977
+ * @returns LSP 0-based position
1978
+ */
1979
+ function monoToLsp(lineNumber, column) {
1980
+ return {
1981
+ line: Math.max(0, lineNumber - 1),
1982
+ character: Math.max(0, column - 1)
1983
+ };
1984
+ }
1985
+ //#endregion
1986
+ //#region src/client/monaco/lsp/lspClient.ts
1987
+ /**
1988
+ * dsh-vscode-mode client — LSP 会话与文档同步(模块级单例)。
1989
+ * 跟踪当前会话 + Monaco 打开文档(model 事件驱动),去抖推送 edrv.lsp.sync;
1990
+ * 并提供 definition/references/documentSymbol/hover/status 的 RPC 薄封装。
1991
+ * 作者 ddj 2026-08-27
1992
+ */
1993
+ /** edrv:// 模型 URI → 工作区相对/绝对路径(与 host 侧 path 对齐)。 */
1994
+ function pathOfModel(model) {
1995
+ if (!model || !model.uri) return null;
1996
+ const uri = model.uri;
1997
+ if (uri.scheme !== "edrv") return null;
1998
+ const raw = uri.path ?? "";
1999
+ try {
2000
+ return decodeURIComponent(String(raw).replace(/^\//, "")) || null;
2001
+ } catch (error) {
2002
+ return String(raw).replace(/^\//, "") || null;
2003
+ }
2004
+ }
2005
+ const SYNC_DEBOUNCE_MS = 250;
2006
+ let sessionId;
2007
+ const syncTimers = /* @__PURE__ */ new Map();
2008
+ let statusCache = {
2009
+ servers: [],
2010
+ at: 0
2011
+ };
2012
+ let progressListeners = /* @__PURE__ */ new Set();
2013
+ /** 设置当前会话(切换会话时清理旧文档跟踪由 host 端 session/disposed 兜底)。 */
2014
+ function setLspSession(id) {
2015
+ if (sessionId === id) return;
2016
+ sessionId = id;
2017
+ refreshStatus();
2018
+ }
2019
+ /** 订阅 LSP 状态变化(服务器进度/启动状态)。 */
2020
+ function onLspProgress(listener) {
2021
+ if (typeof listener !== "function") return () => {};
2022
+ progressListeners.add(listener);
2023
+ return () => progressListeners.delete(listener);
2024
+ }
2025
+ /** 发布当前 server 状态给编辑器底部状态区。 */
2026
+ function publishProgress() {
2027
+ for (const listener of progressListeners) try {
2028
+ listener(statusCache.servers);
1941
2029
  } catch (error) {}
1942
2030
  }
2031
+ /** 立即同步某文档到 host(首次打开/查询前保证服务器已认识该文档)。 */
2032
+ async function syncDoc(path, text, immediate = false) {
2033
+ if (!path || typeof text !== "string") return;
2034
+ if (immediate) {
2035
+ clearPending(path);
2036
+ await rpc("edrv.lsp.sync", {
2037
+ sessionId,
2038
+ path,
2039
+ text,
2040
+ version: Date.now()
2041
+ }).catch(() => {});
2042
+ return;
2043
+ }
2044
+ clearPending(path);
2045
+ syncTimers.set(path, setTimeout(() => {
2046
+ syncTimers.delete(path);
2047
+ rpc("edrv.lsp.sync", {
2048
+ sessionId,
2049
+ path,
2050
+ text,
2051
+ version: Date.now()
2052
+ }).catch(() => {});
2053
+ }, SYNC_DEBOUNCE_MS));
2054
+ }
2055
+ function clearPending(path) {
2056
+ const timer = syncTimers.get(path);
2057
+ if (timer) {
2058
+ clearTimeout(timer);
2059
+ syncTimers.delete(path);
2060
+ }
2061
+ }
2062
+ /** 文档关闭:停止去抖并通知 host。 */
2063
+ function closeDoc(path) {
2064
+ clearPending(path);
2065
+ rpc("edrv.lsp.close", {
2066
+ sessionId,
2067
+ path
2068
+ }).catch(() => {});
2069
+ }
2070
+ /** 立即推送 + 查询定义;返回 [{ uri, range }](uri 已转 edrv://)。 */
2071
+ async function findDefinition(path, text, position) {
2072
+ await syncDoc(path, text, true);
2073
+ const res = await rpc("edrv.lsp.definition", {
2074
+ sessionId,
2075
+ path,
2076
+ position: monoToLsp(position.lineNumber, position.column)
2077
+ }).catch(() => null);
2078
+ if (!res || !res.ok || !Array.isArray(res.locations)) return [];
2079
+ return res.locations.map(toClientLocation);
2080
+ }
2081
+ /** 立即推送 + 查询引用。 */
2082
+ async function findReferences(path, text, position, includeDeclaration = false) {
2083
+ await syncDoc(path, text, true);
2084
+ const res = await rpc("edrv.lsp.references", {
2085
+ sessionId,
2086
+ path,
2087
+ position: monoToLsp(position.lineNumber, position.column),
2088
+ includeDeclaration
2089
+ }).catch(() => null);
2090
+ if (!res || !res.ok || !Array.isArray(res.locations)) return [];
2091
+ return res.locations.map(toClientLocation);
2092
+ }
2093
+ /** 查询大纲符号(documentSymbol)。 */
2094
+ async function fetchDocumentSymbols(path, text) {
2095
+ await syncDoc(path, text, true);
2096
+ const res = await rpc("edrv.lsp.documentSymbol", {
2097
+ sessionId,
2098
+ path
2099
+ }).catch(() => null);
2100
+ if (!res || !res.ok || !Array.isArray(res.symbols)) return [];
2101
+ return res.symbols;
2102
+ }
2103
+ /** 查询 hover 内容。 */
2104
+ async function fetchHover(path, text, position) {
2105
+ await syncDoc(path, text, true);
2106
+ const res = await rpc("edrv.lsp.hover", {
2107
+ sessionId,
2108
+ path,
2109
+ position: monoToLsp(position.lineNumber, position.column)
2110
+ }).catch(() => null);
2111
+ if (!res || !res.ok) return null;
2112
+ return res.hover || null;
2113
+ }
2114
+ /** 拉取服务器状态(带 5s 缓存)。 */
2115
+ async function refreshStatus(force = false) {
2116
+ if (!force && Date.now() - statusCache.at < 1500) return statusCache.servers;
2117
+ const res = await rpc("edrv.lsp.status", { sessionId }).catch(() => null);
2118
+ const servers = res && res.ok && Array.isArray(res.servers) ? res.servers : [];
2119
+ statusCache = {
2120
+ servers,
2121
+ at: Date.now()
2122
+ };
2123
+ publishProgress();
2124
+ return servers;
2125
+ }
2126
+ /** file:// 或已转 edrv:// 的 LSP location → Monaco Location(edrv:// uri + 1-based range)。 */
2127
+ function toClientLocation(loc) {
2128
+ const range = loc.range || {};
2129
+ const start = range.start || {};
2130
+ const end = range.end || {};
2131
+ return {
2132
+ uri: window.monaco.Uri.parse(toEdrvUri(loc.uri, loc.root)),
2133
+ 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))
2134
+ };
2135
+ }
2136
+ /** file:// URI(或裸路径)→ edrv:// 相对路径(去掉盘符/根,与编辑器模型路径一致)。 */
2137
+ function toEdrvUri(uri, root) {
2138
+ if (!uri) return "";
2139
+ if (uri.startsWith("edrv://")) return uri;
2140
+ const path = relativeLspPath(uri, root);
2141
+ return "edrv:///" + encodeURI(path);
2142
+ }
2143
+ /** 将 LSP 目标解析为当前工作区相对路径(跨文件跳转与 model URI 对齐)。 */
2144
+ function targetOpenPath(loc) {
2145
+ return relativeLspPath(loc?.uri, loc?.root);
2146
+ }
2147
+ /** file URI/裸路径 → root 下的 / 分隔相对路径;root 不匹配时保留规范化绝对路径。 */
2148
+ function relativeLspPath(uri, root) {
2149
+ if (!uri) return "";
2150
+ let path = String(uri);
2151
+ if (path.startsWith("file://")) try {
2152
+ path = decodeURIComponent(path.replace(/^file:\/\//, ""));
2153
+ } catch (error) {
2154
+ path = path.replace(/^file:\/\//, "");
2155
+ }
2156
+ path = path.replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1");
2157
+ const normalizedRoot = root ? String(root).replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1").replace(/\/+$/, "") : "";
2158
+ const lowerPath = path.toLowerCase();
2159
+ const lowerRoot = normalizedRoot.toLowerCase();
2160
+ if (normalizedRoot && (lowerPath === lowerRoot || lowerPath.startsWith(lowerRoot + "/"))) return path.slice(normalizedRoot.length).replace(/^\/+/, "");
2161
+ return path.replace(/^\/+/, "");
2162
+ }
2163
+ /** 目标行/列(0-based LSP → 1-based Monaco)。 */
2164
+ function targetMonoPosition(loc) {
2165
+ const start = (loc.range || {}).start || {};
2166
+ return {
2167
+ line: Math.max(1, (start.line ?? 0) + 1),
2168
+ column: Math.max(1, (start.character ?? 0) + 1)
2169
+ };
2170
+ }
2171
+ //#endregion
2172
+ //#region src/client/monaco/lsp/providers.ts
2173
+ /**
2174
+ * dsh-vscode-mode client — Monaco LSP provider 注册 + 跳转/引用命令。
2175
+ * 数据层:Definition/Reference/DocumentSymbol/Hover provider(同一文件内跳转 Monaco 原生可用)。
2176
+ * 交互层:edrv.goToDefinition(F12)/ edrv.findReferences(Shift+F12 / 右键),跨文件跳转自研
2177
+ * (target 经 openFileAt 打开并定位),引用结果用轻量浮动面板展示(点击跳转)。
2178
+ * 作者 ddj 2026-08-27
2179
+ */
2180
+ const LSP_LANGS = ["lua", "csharp"];
2181
+ let registered = false;
2182
+ const disposables = [];
2183
+ let overlayEl = null;
2184
+ /** 目标文件打开并定位(复用现有 openFileAt 的 edrv:open-editor 事件通道)。 */
2185
+ function openAt(path, line, column) {
2186
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
2187
+ path,
2188
+ line,
2189
+ column
2190
+ } }));
2191
+ }
2192
+ function jumpToLocation(loc) {
2193
+ const path = targetOpenPath(loc);
2194
+ const pos = targetMonoPosition(loc);
2195
+ openAt(path, pos.line, pos.column);
2196
+ }
2197
+ /** 注册全部 Monaco LSP provider 与文档跟踪(幂等)。 */
2198
+ function registerLspProviders(monaco) {
2199
+ if (registered) return;
2200
+ registered = true;
2201
+ const attachModel = (model) => {
2202
+ if (!model || model.uri.scheme !== "edrv") return;
2203
+ const path = pathOfModel(model);
2204
+ if (!path) return;
2205
+ const contentDisposer = model.onDidChangeContent(() => syncDoc(path, model.getValue()));
2206
+ const disposeDisposer = model.onWillDispose(() => {
2207
+ closeDoc(path);
2208
+ contentDisposer.dispose();
2209
+ });
2210
+ disposables.push(contentDisposer, disposeDisposer);
2211
+ syncDoc(path, model.getValue(), true);
2212
+ };
2213
+ disposables.push(monaco.editor.onDidCreateModel(attachModel));
2214
+ for (const model of monaco.editor.getModels()) attachModel(model);
2215
+ disposables.push(monaco.languages.registerDefinitionProvider(LSP_LANGS, { provideDefinition: (model, position, token) => {
2216
+ const path = pathOfModel(model);
2217
+ if (!path) return [];
2218
+ return findDefinition(path, model.getValue(), position);
2219
+ } }), monaco.languages.registerReferenceProvider(LSP_LANGS, { provideReferences: (model, position, context, token) => {
2220
+ const path = pathOfModel(model);
2221
+ if (!path) return [];
2222
+ return findReferences(path, model.getValue(), position, Boolean(context?.includeDeclaration));
2223
+ } }), monaco.languages.registerDocumentSymbolProvider(LSP_LANGS, { provideDocumentSymbols: async (model, token) => {
2224
+ const path = pathOfModel(model);
2225
+ if (!path) return [];
2226
+ return (await fetchDocumentSymbols(path, model.getValue())).map(toMonacoSymbol);
2227
+ } }), monaco.languages.registerHoverProvider(LSP_LANGS, { provideHover: async (model, position, token) => {
2228
+ const path = pathOfModel(model);
2229
+ if (!path) return null;
2230
+ const hover = await fetchHover(path, model.getValue(), position);
2231
+ if (!hover || !hover.contents || !hover.contents.length) return null;
2232
+ 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;
2233
+ return {
2234
+ contents: hover.contents.map((text) => ({ value: text })),
2235
+ range: range || void 0
2236
+ };
2237
+ } }));
2238
+ }
2239
+ /** LSP SymbolInfo(host 归一化后)→ Monaco DocumentSymbol。 */
2240
+ function toMonacoSymbol(symbol) {
2241
+ const range = symbol.range || {};
2242
+ const sel = symbol.selectionRange || range;
2243
+ const s = (p) => ({
2244
+ lineNumber: Math.max(1, (p.line ?? 0) + 1),
2245
+ column: Math.max(1, (p.character ?? 0) + 1)
2246
+ });
2247
+ const out = {
2248
+ name: symbol.name,
2249
+ detail: symbol.detail || "",
2250
+ kind: symbol.kind ?? 0,
2251
+ tags: [],
2252
+ range: new window.monaco.Range(s(range.start).lineNumber, s(range.start).column, s(range.end).lineNumber, s(range.end).column),
2253
+ selectionRange: new window.monaco.Range(s(sel.start).lineNumber, s(sel.start).column, s(sel.end).lineNumber, s(sel.end).column)
2254
+ };
2255
+ if (Array.isArray(symbol.children) && symbol.children.length) out.children = symbol.children.map(toMonacoSymbol);
2256
+ return out;
2257
+ }
2258
+ /** 定义查询并跳转;nativeReveal 仅供 F12 保留 Monaco 原生 reveal。 */
2259
+ async function goToDefinition(ed, position, nativeReveal = false) {
2260
+ const model = ed.getModel();
2261
+ const path = pathOfModel(model);
2262
+ if (!path || !position) return;
2263
+ const locations = await findDefinition(path, model.getValue(), position);
2264
+ if (!locations.length) {
2265
+ setStatus("未找到定义");
2266
+ return;
2267
+ }
2268
+ const first = locations[0];
2269
+ if (nativeReveal && targetOpenPath(first) === path) {
2270
+ const action = ed.getAction("editor.action.revealDefinition");
2271
+ if (action) {
2272
+ await action.run();
2273
+ return;
2274
+ }
2275
+ }
2276
+ jumpToLocation(first);
2277
+ }
2278
+ /** F12 / 右键「转到定义」:查询定义并跳转(首个目标)。 */
2279
+ async function runGoToDefinition(ed) {
2280
+ await goToDefinition(ed, ed.getPosition(), true);
2281
+ }
2282
+ /** Ctrl+鼠标点击定义:使用点击位置查询,避免依赖当前光标位置。 */
2283
+ async function gotoDefinitionAt(ed, position) {
2284
+ await goToDefinition(ed, position);
2285
+ }
2286
+ /** 给一个 Monaco 编辑器绑定 Ctrl/Cmd+鼠标定义跳转(幂等)。 */
2287
+ function bindLspEditor(ed) {
2288
+ if (!ed || ed.__edrvLspClick) return;
2289
+ ed.__edrvLspClick = true;
2290
+ const disposable = ed.onMouseDown((event) => {
2291
+ const native = event?.event;
2292
+ const position = event?.target?.position;
2293
+ const button = native?.button ?? event?.button;
2294
+ const ctrl = native?.ctrlKey ?? event?.ctrlKey;
2295
+ const meta = native?.metaKey ?? event?.metaKey;
2296
+ const isLeftButton = button === 0 || native?.leftButton === true;
2297
+ if (!native || !position || !isLeftButton || !ctrl && !meta) return;
2298
+ native.preventDefault?.();
2299
+ native.stopPropagation?.();
2300
+ gotoDefinitionAt(ed, position);
2301
+ });
2302
+ disposables.push(disposable);
2303
+ }
2304
+ /** Shift+F12 / 右键「查找所有引用」:查询引用并展示浮动面板。 */
2305
+ async function runFindReferences(ed) {
2306
+ const model = ed.getModel();
2307
+ const path = pathOfModel(model);
2308
+ const position = ed.getPosition();
2309
+ if (!path || !position) return;
2310
+ showReferencesOverlay(ed, path, position, await findReferences(path, model.getValue(), position, false));
2311
+ }
2312
+ /** 引用结果浮动面板(轻量 DOM,点击跳转;Esc/失焦关闭)。 */
2313
+ function showReferencesOverlay(ed, path, position, locations) {
2314
+ hideReferencesOverlay();
2315
+ if (!locations.length) {
2316
+ setStatus("未找到引用");
2317
+ return;
2318
+ }
2319
+ const root = ed.getDomNode();
2320
+ if (!root) return;
2321
+ const el = document.createElement("div");
2322
+ el.className = "edrv-refs";
2323
+ const header = document.createElement("div");
2324
+ header.className = "edrv-refs-head";
2325
+ header.textContent = "引用 " + locations.length + " 处";
2326
+ el.appendChild(header);
2327
+ const list = document.createElement("div");
2328
+ list.className = "edrv-refs-list";
2329
+ for (const loc of locations) {
2330
+ const row = document.createElement("div");
2331
+ row.className = "edrv-refs-row";
2332
+ const pos = targetMonoPosition(loc);
2333
+ const rel = targetOpenPath(loc);
2334
+ row.textContent = (String(rel).split("/").pop() || rel) + ":" + pos.line + ":" + pos.column;
2335
+ row.title = rel + ":" + pos.line;
2336
+ row.addEventListener("click", () => {
2337
+ hideReferencesOverlay();
2338
+ jumpToLocation(loc);
2339
+ });
2340
+ list.appendChild(row);
2341
+ }
2342
+ el.appendChild(list);
2343
+ root.appendChild(el);
2344
+ overlayEl = el;
2345
+ const onKey = (e) => {
2346
+ if (e.key === "Escape") hideReferencesOverlay();
2347
+ };
2348
+ window.addEventListener("keydown", onKey);
2349
+ el.addEventListener("click", (e) => {
2350
+ if (e.target === el) hideReferencesOverlay();
2351
+ });
2352
+ el._onKey = onKey;
2353
+ }
2354
+ function hideReferencesOverlay() {
2355
+ if (overlayEl) {
2356
+ window.removeEventListener("keydown", overlayEl._onKey);
2357
+ overlayEl.remove();
2358
+ overlayEl = null;
2359
+ }
2360
+ }
2361
+ /** 编辑器状态栏提示(复用 EditorView 的 status 通道)。 */
2362
+ function setStatus(text) {
2363
+ window.dispatchEvent(new CustomEvent("edrv:status", { detail: { text } }));
2364
+ }
2365
+ //#endregion
2366
+ //#region src/client/monaco/lsp/index.ts
2367
+ /**
2368
+ * dsh-vscode-mode client — LSP 装配编排。
2369
+ * setupLsp(monaco):注册 Monaco provider 与文档跟踪(幂等);调用方在 Monaco 加载后调用。
2370
+ * setSession(id):会话切换时同步(provider 与文档同步共用)。
2371
+ * 作者 ddj 2026-08-27
2372
+ */
2373
+ /** Monaco 加载后装配(幂等;重复调用仅刷新会话)。 */
2374
+ function setupLsp(monaco) {
2375
+ registerLspProviders(monaco);
2376
+ }
2377
+ /** 会话切换:更新 host 侧文档归属 + 刷新状态。 */
2378
+ function setSession(sessionId) {
2379
+ setLspSession(sessionId);
2380
+ refreshStatus(true);
2381
+ }
1943
2382
  //#endregion
1944
2383
  //#region src/client/ui/EditorView.ts
1945
2384
  /**
@@ -1971,6 +2410,7 @@ window.__ModuleLoader__.load({
1971
2410
  const [content, setContent] = react.default.useState(null);
1972
2411
  const [contentPath, setContentPath] = react.default.useState(null);
1973
2412
  const [status, setStatus] = react.default.useState("");
2413
+ const [lspServers, setLspServers] = react.default.useState([]);
1974
2414
  const [error, setError] = react.default.useState(null);
1975
2415
  const [loadError, setLoadError] = react.default.useState(null);
1976
2416
  const [loadStage, setLoadStage] = react.default.useState({
@@ -1993,7 +2433,7 @@ window.__ModuleLoader__.load({
1993
2433
  useKeybindingsVersion();
1994
2434
  const [hintDismissed, setHintDismissed] = react.default.useState(() => {
1995
2435
  try {
1996
- return localStorage.getItem("edrv.side-hint-dismissed") === "1";
2436
+ return localStorage.getItem(CACHE_KEY.sideHint) === "1";
1997
2437
  } catch {
1998
2438
  return false;
1999
2439
  }
@@ -2207,6 +2647,18 @@ window.__ModuleLoader__.load({
2207
2647
  window.removeEventListener("edrv:refresh", onRefresh);
2208
2648
  };
2209
2649
  }, [sessionId]);
2650
+ react.default.useEffect(() => {
2651
+ const publishLsp = (servers) => setLspServers(Array.isArray(servers) ? servers : []);
2652
+ const unsubscribe = onLspProgress(publishLsp);
2653
+ refreshStatus(true).then(publishLsp);
2654
+ const timer = setInterval(() => {
2655
+ refreshStatus(true).then(publishLsp);
2656
+ }, 1500);
2657
+ return () => {
2658
+ unsubscribe();
2659
+ clearInterval(timer);
2660
+ };
2661
+ }, [sessionId]);
2210
2662
  react.default.useEffect(() => {
2211
2663
  const onOpen = (e) => {
2212
2664
  const p = e?.detail?.path;
@@ -2217,6 +2669,12 @@ window.__ModuleLoader__.load({
2217
2669
  region: null
2218
2670
  };
2219
2671
  setFocusRequest((value) => value + 1);
2672
+ addTab(p, true);
2673
+ return;
2674
+ }
2675
+ if (e?.detail?.line != null) {
2676
+ openFileAt(p, e?.detail?.line, e?.detail?.column);
2677
+ return;
2220
2678
  }
2221
2679
  addTab(p, true);
2222
2680
  };
@@ -2227,9 +2685,14 @@ window.__ModuleLoader__.load({
2227
2685
  };
2228
2686
  window.addEventListener("edrv:open-editor", onOpen);
2229
2687
  window.addEventListener("edrv:show-launcher", onShowLauncher);
2688
+ const onStatus = (e) => {
2689
+ if (e?.detail?.text) setStatus(e.detail.text);
2690
+ };
2691
+ window.addEventListener("edrv:status", onStatus);
2230
2692
  return () => {
2231
2693
  window.removeEventListener("edrv:open-editor", onOpen);
2232
2694
  window.removeEventListener("edrv:show-launcher", onShowLauncher);
2695
+ window.removeEventListener("edrv:status", onStatus);
2233
2696
  };
2234
2697
  }, [sessionId]);
2235
2698
  const dockSourceRef = react.default.useRef({});
@@ -2293,7 +2756,7 @@ window.__ModuleLoader__.load({
2293
2756
  bootRef.current = true;
2294
2757
  viewStatesRef.current = viewStatesLoad(sessionId);
2295
2758
  try {
2296
- const raw = localStorage.getItem("edrv.editor.v2." + String(sessionId));
2759
+ const raw = localStorage.getItem(CACHE_KEY.editor + String(sessionId));
2297
2760
  if (raw) {
2298
2761
  const saved = JSON.parse(raw);
2299
2762
  if (Array.isArray(saved.tabs) && saved.tabs.length) {
@@ -2306,7 +2769,7 @@ window.__ModuleLoader__.load({
2306
2769
  react.default.useEffect(() => {
2307
2770
  if (!sessionId) return;
2308
2771
  try {
2309
- localStorage.setItem("edrv.editor.v2." + String(sessionId), JSON.stringify({
2772
+ localStorage.setItem(CACHE_KEY.editor + String(sessionId), JSON.stringify({
2310
2773
  tabs: tabs.map((t) => t.path),
2311
2774
  active
2312
2775
  }));
@@ -2316,7 +2779,7 @@ window.__ModuleLoader__.load({
2316
2779
  active,
2317
2780
  sessionId
2318
2781
  ]);
2319
- const sidebarKey = "edrv.sidebar." + (layout === "side" ? "side." : "") + String(sessionId);
2782
+ const sidebarKey = CACHE_KEY.sidebar + (layout === "side" ? "side." : "") + String(sessionId);
2320
2783
  react.default.useEffect(() => {
2321
2784
  if (!sessionId) return;
2322
2785
  try {
@@ -2513,6 +2976,7 @@ window.__ModuleLoader__.load({
2513
2976
  flushSave();
2514
2977
  saveViewState(active);
2515
2978
  diffRendererRef.current?.dispose?.();
2979
+ hideReferencesOverlay();
2516
2980
  if (editorRef.current) {
2517
2981
  editorRef.current.dispose();
2518
2982
  editorRef.current = null;
@@ -2612,6 +3076,30 @@ window.__ModuleLoader__.load({
2612
3076
  precondition: "editorTextFocus",
2613
3077
  run: (edx) => menuHandlers()?.openInExplorer(pathOf(edx))
2614
3078
  });
3079
+ ed.addAction({
3080
+ id: "edrv.goToDefinition",
3081
+ label: "转到定义",
3082
+ contextMenuGroupId: "1_edrv",
3083
+ keybindings: m.KeyMod.CtrlCmd | m.KeyCode.F12,
3084
+ precondition: "editorTextFocus",
3085
+ run: (edx) => {
3086
+ runGoToDefinition(edx);
3087
+ }
3088
+ });
3089
+ ed.addAction({
3090
+ id: "edrv.findReferences",
3091
+ label: "查找所有引用",
3092
+ contextMenuGroupId: "1_edrv",
3093
+ keybindings: m.KeyMod.Shift | m.KeyCode.F12,
3094
+ precondition: "editorTextFocus",
3095
+ run: (edx) => {
3096
+ runFindReferences(edx);
3097
+ }
3098
+ });
3099
+ ed.addCommand(m.KeyCode.F12, () => {
3100
+ runGoToDefinition(ed);
3101
+ });
3102
+ bindLspEditor(ed);
2615
3103
  const hideSoon = () => {
2616
3104
  if (hoverEditorRef.current || hoverPanelRef.current || hideTimerRef.current) return;
2617
3105
  hideTimerRef.current = setTimeout(() => {
@@ -3065,6 +3553,26 @@ window.__ModuleLoader__.load({
3065
3553
  className: "edrv-pathbar",
3066
3554
  title: active || ""
3067
3555
  }, 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);
3556
+ const lspLanguage = active ? langOf(active) : "";
3557
+ const lspServer = lspServers.find((item) => item.languageId === lspLanguage);
3558
+ const lspLabel = lspServer ? "LSP " + lspLanguage + " · " + ({
3559
+ idle: "未启动",
3560
+ starting: "启动中",
3561
+ ready: "已就绪",
3562
+ indexing: "解析中",
3563
+ unavailable: "不可用",
3564
+ stopped: "已停止"
3565
+ }[lspServer.phase] ?? lspServer.phase) + " · " + ({
3566
+ extension: "扩展",
3567
+ discover: "自动发现",
3568
+ manual: "手动配置",
3569
+ none: "未配置"
3570
+ }[lspServer.source] ?? lspServer.source) : "LSP " + lspLanguage + " · 未启动";
3571
+ const lspProgress = typeof lspServer?.progress === "number" ? Math.round(lspServer.progress) : null;
3572
+ const lspFooter = lspLanguage === "lua" || lspLanguage === "csharp" ? react.default.createElement("div", {
3573
+ className: "edrv-statusbar",
3574
+ title: lspServer?.progressMessage || lspLabel
3575
+ }, 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;
3068
3576
  const tabRow = react.default.createElement("div", { style: {
3069
3577
  display: "flex",
3070
3578
  alignItems: "center",
@@ -3292,7 +3800,7 @@ window.__ModuleLoader__.load({
3292
3800
  const dismissHint = () => {
3293
3801
  setHintDismissed(true);
3294
3802
  try {
3295
- localStorage.setItem("edrv.side-hint-dismissed", "1");
3803
+ localStorage.setItem(CACHE_KEY.sideHint, "1");
3296
3804
  } catch (e) {}
3297
3805
  };
3298
3806
  const copyInstallCmd = () => {
@@ -3318,7 +3826,7 @@ window.__ModuleLoader__.load({
3318
3826
  flexDirection: "column",
3319
3827
  overflow: "hidden"
3320
3828
  }
3321
- }, pathBar, tabRow, sideHintEl, editorArea);
3829
+ }, pathBar, tabRow, sideHintEl, editorArea, lspFooter);
3322
3830
  const editorRow = react.default.createElement("div", { className: "edrv-editor-row" }, sidebarOn && sidebarPanels ? react.default.createElement(SidebarView, {
3323
3831
  registry: sidebarPanels,
3324
3832
  ctx: sidebarCtx,
@@ -3432,6 +3940,71 @@ window.__ModuleLoader__.load({
3432
3940
  } }, String(n)));
3433
3941
  }
3434
3942
  //#endregion
3943
+ //#region src/client/ui/PerfSizeBadge.ts
3944
+ /**
3945
+ * dsh-vscode-mode client — PerfSizeBadge:对话 header 会话体积指示器。
3946
+ * 5s 轮询当前会话持久化体积(host 仅 stat,近零成本),≥1MB 显示、超阈值变色,
3947
+ * 引导"一次任务一个短会话":会话过大时建议 /compact 或新开会话。
3948
+ * 作者 ddj 2026-09-02
3949
+ */
3950
+ /** 软阈值:≥ 此值琥珀色(默认 2MB)。 */
3951
+ const SOFT = 2097152;
3952
+ /** 硬阈值:≥ 此值红色(默认 8MB)。 */
3953
+ const HARD = 8388608;
3954
+ /** 显示下限:≥ 1MB 才显示(太小无提示价值)。 */
3955
+ const SHOW_MIN = 1048576;
3956
+ /**
3957
+ * 会话 header 体积徽章(挂 conversation.session.header.utilities)。
3958
+ * @author ddj 2026年09月02号
3959
+ * @param props { sessionId, sessions }(sessions 供 cwd 解析)
3960
+ */
3961
+ function PerfSizeBadge(props) {
3962
+ const sessionId = props?.sessionId;
3963
+ const sessions = props?.sessions;
3964
+ const [size, setSize] = react.default.useState(null);
3965
+ const seq = react.default.useRef(0);
3966
+ const load = react.default.useCallback(() => {
3967
+ if (!sessionId) return;
3968
+ const cwd = sessions?.list?.getSnapshot?.()?.byId?.[sessionId]?.cwd;
3969
+ if (!cwd) return;
3970
+ const s = ++seq.current;
3971
+ rpc("edrv.perf.sessionSize", {
3972
+ sessionId,
3973
+ cwd
3974
+ }).then((res) => {
3975
+ if (s !== seq.current || !res || !res.ok) return;
3976
+ setSize(res.exists ? res.bytes : 0);
3977
+ }).catch(() => {});
3978
+ }, [sessionId, sessions]);
3979
+ react.default.useEffect(() => {
3980
+ load();
3981
+ const t = setInterval(load, 5e3);
3982
+ return () => {
3983
+ clearInterval(t);
3984
+ };
3985
+ }, [load]);
3986
+ if (!size || size < SHOW_MIN) return null;
3987
+ const label = size >= 1048576 ? (size / 1048576).toFixed(1) + "MB" : Math.round(size / 1024) + "KB";
3988
+ const color = size >= HARD ? "#dc2626" : size >= SOFT ? "#d97706" : "var(--dsw-text-3,#6b7280)";
3989
+ const tip = "当前会话已持久化 " + label + "(启动回放约放大 10×,占内存)。过大建议 /compact 或新开会话。";
3990
+ return react.default.createElement("span", {
3991
+ style: {
3992
+ display: "inline-flex",
3993
+ alignItems: "center",
3994
+ height: 30,
3995
+ padding: "0 8px",
3996
+ fontSize: 11,
3997
+ color,
3998
+ borderRadius: 6,
3999
+ cursor: "default",
4000
+ whiteSpace: "nowrap",
4001
+ gap: 4,
4002
+ lineHeight: "30px"
4003
+ },
4004
+ title: tip
4005
+ }, "会话 " + label);
4006
+ }
4007
+ //#endregion
3435
4008
  //#region src/client/ui/DiffBox.ts
3436
4009
  /**
3437
4010
  * dsh-vscode-mode client — DiffBox:每个文件底部紧凑差异条(per-file hunk + ⋮ 二级菜单)。
@@ -3657,7 +4230,7 @@ window.__ModuleLoader__.load({
3657
4230
  }
3658
4231
  //#endregion
3659
4232
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
3660
- 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";
4233
+ 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/* 语言服务器(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 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\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";
3661
4234
  const tagId = "dsh-vscode-mode/mcp.css";
3662
4235
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
3663
4236
  const tag = document.createElement("style");
@@ -4055,6 +4628,517 @@ window.__ModuleLoader__.load({
4055
4628
  }, "恢复默认")));
4056
4629
  }
4057
4630
  //#endregion
4631
+ //#region src/client/ui/LspSettings.ts
4632
+ /**
4633
+ * dsh-vscode-mode client — 「语言服务器」设置子 Tab。
4634
+ * 四视图:服务器(状态 + 每语言启用/命令/路径)|已安装(卸载/更新)|市场(Open VSX 搜索/安装 + 本地 vsix)|更新。
4635
+ * 状态经 edrv.lsp.status,配置经 edrv.lsp.configGet/Update,扩展经 edrv.lsp.ext.*。
4636
+ * 作者 ddj 2026-08-27
4637
+ */
4638
+ const LANGUAGES = [{
4639
+ id: "lua",
4640
+ label: "Lua(LuaLS)",
4641
+ hint: "lua-language-server,EmmyLua 注解原生支持"
4642
+ }, {
4643
+ id: "csharp",
4644
+ label: "C#(Roslyn / OmniSharp)",
4645
+ hint: "需 dotnet + ms-dotnettools.csharp 的 .roslyn 服务器,或手动指定"
4646
+ }];
4647
+ const PHASE_LABEL = {
4648
+ idle: "未启动",
4649
+ starting: "启动中",
4650
+ ready: "就绪",
4651
+ indexing: "索引中",
4652
+ unavailable: "不可用",
4653
+ stopped: "已停止"
4654
+ };
4655
+ const SOURCE_LABEL = {
4656
+ extension: "扩展",
4657
+ discover: "自动发现",
4658
+ manual: "手动配置",
4659
+ none: "未配置"
4660
+ };
4661
+ const TABS = [
4662
+ {
4663
+ id: "servers",
4664
+ label: "服务器"
4665
+ },
4666
+ {
4667
+ id: "installed",
4668
+ label: "已安装"
4669
+ },
4670
+ {
4671
+ id: "market",
4672
+ label: "市场"
4673
+ },
4674
+ {
4675
+ id: "updates",
4676
+ label: "更新"
4677
+ }
4678
+ ];
4679
+ /** 语言卡片:状态 + 启用开关 + 命令/路径配置。 */
4680
+ function LangCard({ lang, config, status, busy, onToggle, onSave }) {
4681
+ const [pathDraft, setPathDraft] = react.default.useState(config?.path ?? "");
4682
+ const [commandDraft, setCommandDraft] = react.default.useState(config?.command ?? "");
4683
+ react.default.useEffect(() => {
4684
+ setPathDraft(config?.path ?? "");
4685
+ setCommandDraft(config?.command ?? "");
4686
+ }, [config?.path, config?.command]);
4687
+ const phase = status?.phase ?? "idle";
4688
+ 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", {
4689
+ className: "vsm-switch " + (config?.enabled !== false ? "on" : ""),
4690
+ onClick: () => onToggle(lang.id, config?.enabled !== false ? false : true),
4691
+ "aria-label": "启用/禁用"
4692
+ }, 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", {
4693
+ value: pathDraft,
4694
+ placeholder: "如 C:/.../lua-language-server.exe",
4695
+ onChange: (e) => setPathDraft(e.target.value)
4696
+ })), react.default.createElement("label", null, react.default.createElement("span", null, "命令名(PATH 内查找)"), react.default.createElement("input", {
4697
+ value: commandDraft,
4698
+ placeholder: "如 lua-language-server",
4699
+ onChange: (e) => setCommandDraft(e.target.value)
4700
+ })), react.default.createElement("button", {
4701
+ className: "vsm-primary",
4702
+ disabled: busy === lang.id,
4703
+ onClick: () => onSave(lang.id, pathDraft, commandDraft)
4704
+ }, busy === lang.id ? "保存中…" : "保存配置")), react.default.createElement("div", { className: "vsm-lsp-hint" }, lang.hint));
4705
+ }
4706
+ /** 已装扩展卡片:清单 + 卸载/更新。 */
4707
+ function InstalledCard({ ext, busy, onUninstall, onUpdate }) {
4708
+ 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", {
4709
+ className: "vsm-primary vsm-small",
4710
+ disabled: busy === ext.id,
4711
+ onClick: () => onUpdate(ext)
4712
+ }, "更新"), react.default.createElement("button", {
4713
+ className: "vsm-danger vsm-small",
4714
+ disabled: busy === ext.id,
4715
+ onClick: () => onUninstall(ext)
4716
+ }, "卸载"))), ext.description ? react.default.createElement("div", { className: "vsm-lsp-hint" }, ext.description) : null);
4717
+ }
4718
+ /** 市场条目卡片:安装/已装态。 */
4719
+ function MarketCard({ item, installed, busy, onInstall }) {
4720
+ const has = installed.some((e) => e.id === item.id);
4721
+ 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", {
4722
+ className: "vsm-primary vsm-small",
4723
+ disabled: has || busy === item.id,
4724
+ onClick: () => onInstall(item)
4725
+ }, has ? "已安装" : "安装"))), item.description ? react.default.createElement("div", { className: "vsm-lsp-hint" }, item.description) : null);
4726
+ }
4727
+ /**
4728
+ * 语言服务器设置页:服务器状态/配置 + VSIX 扩展(安装/更新/卸载三视图)。
4729
+ * @author ddj 2026年08月27号
4730
+ */
4731
+ function LspSettings() {
4732
+ const [tab, setTab] = react.default.useState("servers");
4733
+ const [config, setConfig] = react.default.useState({});
4734
+ const [servers, setServers] = react.default.useState([]);
4735
+ const [installed, setInstalled] = react.default.useState([]);
4736
+ const [updates, setUpdates] = react.default.useState([]);
4737
+ const [market, setMarket] = react.default.useState([]);
4738
+ const [query, setQuery] = react.default.useState("");
4739
+ const [vsixPath, setVsixPath] = react.default.useState("");
4740
+ const [busy, setBusy] = react.default.useState("");
4741
+ const [error, setError] = react.default.useState("");
4742
+ const [loading, setLoading] = react.default.useState(true);
4743
+ const refreshServers = react.default.useCallback(() => {
4744
+ Promise.all([rpc("edrv.lsp.configGet", {}), rpc("edrv.lsp.status", {})]).then(([cfg, st]) => {
4745
+ if (cfg?.ok) setConfig(cfg.config ?? {});
4746
+ if (st?.ok) setServers(st.servers ?? []);
4747
+ setError("");
4748
+ }).catch((e) => setError(String(e)));
4749
+ }, []);
4750
+ const refreshExt = react.default.useCallback(() => {
4751
+ Promise.all([rpc("edrv.lsp.ext.list", {}), rpc("edrv.lsp.ext.updates", {})]).then(([l, u]) => {
4752
+ if (l?.ok) setInstalled(l.extensions ?? []);
4753
+ if (u?.ok) setUpdates(u.updates ?? []);
4754
+ setError("");
4755
+ }).catch((e) => setError(String(e)));
4756
+ }, []);
4757
+ const refresh = react.default.useCallback(() => {
4758
+ setLoading(true);
4759
+ Promise.all([refreshServers(), refreshExt()]).finally(() => setLoading(false));
4760
+ }, [refreshServers, refreshExt]);
4761
+ react.default.useEffect(() => {
4762
+ refresh();
4763
+ }, [refresh]);
4764
+ const switchTab = (next) => {
4765
+ setTab(next);
4766
+ setError("");
4767
+ if (next === "market" && !query) setQuery("lua language server");
4768
+ if (next === "installed" || next === "updates") refreshExt();
4769
+ if (next === "servers") refreshServers();
4770
+ };
4771
+ const saveLang = (languageId, path, command) => {
4772
+ setBusy(languageId);
4773
+ setError("");
4774
+ rpc("edrv.lsp.configUpdate", {
4775
+ languageId,
4776
+ path,
4777
+ command
4778
+ }).then((res) => {
4779
+ if (!res?.ok) {
4780
+ setError(res?.error ?? "保存失败");
4781
+ return;
4782
+ }
4783
+ setConfig(res.config ?? {});
4784
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4785
+ };
4786
+ const toggleLang = (languageId, enabled) => {
4787
+ setBusy(languageId);
4788
+ rpc("edrv.lsp.configUpdate", {
4789
+ languageId,
4790
+ enabled
4791
+ }).then((res) => {
4792
+ if (res?.ok) {
4793
+ setConfig(res.config ?? {});
4794
+ refreshServers();
4795
+ } else setError(res?.error ?? "切换失败");
4796
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4797
+ };
4798
+ const searchMarket = () => {
4799
+ if (!query.trim()) return;
4800
+ setBusy("__market");
4801
+ setError("");
4802
+ rpc("edrv.lsp.ext.market", {
4803
+ query: query.trim(),
4804
+ size: 12
4805
+ }).then((res) => {
4806
+ if (res?.ok) setMarket(res.extensions ?? []);
4807
+ else setError(res?.error ?? "搜索失败");
4808
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4809
+ };
4810
+ const installMarket = (item) => {
4811
+ setBusy(item.id);
4812
+ setError("");
4813
+ rpc("edrv.lsp.ext.install", {
4814
+ namespace: item.namespace,
4815
+ name: item.name
4816
+ }).then((res) => {
4817
+ if (!res?.ok) {
4818
+ setError(res?.error ?? "安装失败");
4819
+ return;
4820
+ }
4821
+ refreshExt();
4822
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4823
+ };
4824
+ const installLocal = () => {
4825
+ if (!vsixPath.trim()) return;
4826
+ setBusy("__vsix");
4827
+ setError("");
4828
+ rpc("edrv.lsp.ext.install", { vsixPath: vsixPath.trim() }).then((res) => {
4829
+ if (!res?.ok) {
4830
+ setError(res?.error ?? "安装失败");
4831
+ return;
4832
+ }
4833
+ setVsixPath("");
4834
+ refreshExt();
4835
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4836
+ };
4837
+ const uninstallExt = (ext) => {
4838
+ if (!window.confirm("卸载扩展 " + ext.id + "?")) return;
4839
+ setBusy(ext.id);
4840
+ setError("");
4841
+ rpc("edrv.lsp.ext.uninstall", { id: ext.id }).then((res) => {
4842
+ if (!res?.ok) setError(res?.error ?? "卸载失败");
4843
+ refreshExt();
4844
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4845
+ };
4846
+ const updateExt = (ext) => {
4847
+ setBusy(ext.id);
4848
+ setError("");
4849
+ rpc("edrv.lsp.ext.update", { id: ext.id }).then((res) => {
4850
+ if (!res?.ok) setError(res?.error ?? "更新失败");
4851
+ refreshExt();
4852
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4853
+ };
4854
+ if (loading) return react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取语言服务器状态…");
4855
+ const nav = react.default.createElement("nav", { className: "vsm-mcp-tabs" }, TABS.map((t) => react.default.createElement("button", {
4856
+ key: t.id,
4857
+ className: tab === t.id ? "active" : "",
4858
+ onClick: () => switchTab(t.id)
4859
+ }, t.label)));
4860
+ 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", {
4861
+ className: "vsm-primary",
4862
+ onClick: refresh
4863
+ }, "↻ 刷新"));
4864
+ const banner = error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null;
4865
+ let body = null;
4866
+ if (tab === "servers") body = react.default.createElement("div", null, LANGUAGES.map((lang) => react.default.createElement(LangCard, {
4867
+ key: lang.id,
4868
+ lang,
4869
+ config: config[lang.id] ?? {},
4870
+ status: servers.find((s) => s.languageId === lang.id),
4871
+ busy,
4872
+ onToggle: toggleLang,
4873
+ onSave: saveLang
4874
+ })), react.default.createElement("div", { className: "vsm-lsp-hint" }, "提示:打开 Lua/C# 文件即自动(惰性)启动对应语言服务器;未配置时编辑器功能不受影响,大纲回退内置解析。"));
4875
+ else if (tab === "installed") body = react.default.createElement("div", null, react.default.createElement("div", { className: "vsm-lsp-hint" }, "已安装的语言服务器扩展(存于 ~/.dsh/dsh-vscode-mode/extensions/)。语言服务器被自动发现为「扩展」源。"), installed.length ? installed.map((ext) => react.default.createElement(InstalledCard, {
4876
+ key: ext.id,
4877
+ ext,
4878
+ busy,
4879
+ onUninstall: uninstallExt,
4880
+ onUpdate: updateExt
4881
+ })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有安装扩展(去「市场」或指定本地 .vsix)"));
4882
+ else if (tab === "market") body = react.default.createElement("div", 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", {
4883
+ value: query,
4884
+ placeholder: "如 sumneko.lua / lua language server",
4885
+ onChange: (e) => setQuery(e.target.value),
4886
+ onKeyDown: (e) => {
4887
+ if (e.key === "Enter") searchMarket();
4888
+ }
4889
+ })), react.default.createElement("button", {
4890
+ className: "vsm-primary",
4891
+ disabled: busy === "__market",
4892
+ onClick: searchMarket
4893
+ }, 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", {
4894
+ value: vsixPath,
4895
+ placeholder: "如 C:/Downloads/sumneko.lua-3.19.1.vsix",
4896
+ onChange: (e) => setVsixPath(e.target.value)
4897
+ })), react.default.createElement("button", {
4898
+ className: "vsm-primary",
4899
+ disabled: busy === "__vsix",
4900
+ onClick: installLocal
4901
+ }, busy === "__vsix" ? "安装中…" : "安装本地 .vsix")), react.default.createElement("div", { className: "vsm-lsp-hint" }, "市场安装会下载并解包 VSIX 到扩展目录;含语言服务器(如 sumneko.lua)的扩展会被自动发现。"), market.map((item) => react.default.createElement(MarketCard, {
4902
+ key: item.id,
4903
+ item,
4904
+ installed,
4905
+ busy,
4906
+ onInstall: installMarket
4907
+ })));
4908
+ else body = react.default.createElement("div", null, react.default.createElement("div", { className: "vsm-lsp-hint" }, "已装扩展与 Open VSX 最新版的差异。"), updates.length ? updates.map((u) => react.default.createElement("article", {
4909
+ key: u.id,
4910
+ className: "vsm-mcp-card"
4911
+ }, 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", {
4912
+ className: "vsm-primary vsm-small",
4913
+ disabled: busy === u.id,
4914
+ onClick: () => updateExt(u)
4915
+ }, busy === u.id ? "更新中…" : "更新"))))) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "全部已是最新"));
4916
+ return react.default.createElement("div", null, header, nav, banner, body);
4917
+ }
4918
+ //#endregion
4919
+ //#region src/client/ui/PerfSettings.ts
4920
+ /**
4921
+ * dsh-vscode-mode client — PerfSettings:「性能优化」设置子页(会话卫生 + 压缩调优)。
4922
+ * - 会话盘点:全工作区/每会话体积、活跃标记,按体积排序;
4923
+ * - 移出到归档(先规划→确认→执行→可恢复)与归档清除;
4924
+ * - DSH 内置压缩调优:写 profile patch(compaction-basic / tool-result-pruner 更低阈值,
4925
+ * 带备份与撤销,需重启生效);
4926
+ * - 侧车摘要用法提示(agent/脚本取关键字段,不整份读 .dsh-edit-review.json)。
4927
+ * 作者 ddj 2026-09-02
4928
+ */
4929
+ /** 字节数人类可读。 */
4930
+ function formatBytes(n) {
4931
+ if (!n) return "0B";
4932
+ if (n >= 1048576) return (n / 1048576).toFixed(1) + "MB";
4933
+ if (n >= 1024) return (n / 1024).toFixed(0) + "KB";
4934
+ return n + "B";
4935
+ }
4936
+ /** 会话行 key:workspaceKey \u0000 sessionId。 */
4937
+ const keyOf = (ws, id) => ws + "\0" + id;
4938
+ /** 单行会话。 */
4939
+ function SessionRow({ row, checked, disabled, onToggle }) {
4940
+ const age = Math.max(0, (Date.now() - row.mtime) / 864e5);
4941
+ const when = age < 1 ? Math.round(age * 24) + "h" : Math.round(age) + "d";
4942
+ return react.default.createElement("label", { className: "vsm-perf-row" }, react.default.createElement("input", {
4943
+ type: "checkbox",
4944
+ checked,
4945
+ disabled,
4946
+ onChange: onToggle
4947
+ }), react.default.createElement("span", {
4948
+ className: "vsm-perf-cell vsm-perf-id",
4949
+ title: row.sessionId
4950
+ }, row.sessionId), react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-size " + (row.bytes >= 1048576 ? "big" : "") }, formatBytes(row.bytes)), react.default.createElement("span", {
4951
+ className: "vsm-perf-cell vsm-perf-mtime",
4952
+ title: new Date(row.mtime).toLocaleString()
4953
+ }, when), row.active ? react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-active" }, "活跃") : null);
4954
+ }
4955
+ /**
4956
+ * 会话卫生 + 压缩调优设置子页。
4957
+ * @author ddj 2026年09月02号
4958
+ */
4959
+ function PerfSettings() {
4960
+ const [inv, setInv] = react.default.useState(null);
4961
+ const [error, setError] = react.default.useState("");
4962
+ const [busy, setBusy] = react.default.useState("");
4963
+ const [selected, setSelected] = react.default.useState({});
4964
+ const [confirm, setConfirm] = react.default.useState(null);
4965
+ const [moved, setMoved] = react.default.useState(null);
4966
+ const [config, setConfig] = react.default.useState(null);
4967
+ const load = react.default.useCallback(() => {
4968
+ setError("");
4969
+ Promise.all([rpc("edrv.perf.inventory", {}), rpc("edrv.perf.configGet", {})]).then(([i, c]) => {
4970
+ if (!i.ok) throw new Error(i.error);
4971
+ if (!c.ok) throw new Error(c.error);
4972
+ setInv(i);
4973
+ setConfig(c);
4974
+ }).catch((e) => setError(String(e)));
4975
+ }, []);
4976
+ react.default.useEffect(load, [load]);
4977
+ const action = (id, method, args, patch) => {
4978
+ setBusy(id);
4979
+ setError("");
4980
+ return rpc(method, args).then((res) => {
4981
+ if (!res.ok) throw new Error(res.error);
4982
+ if (patch) patch(res);
4983
+ return res;
4984
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4985
+ };
4986
+ /** 圈选 ≥ 1MB 的非活跃会话。 */
4987
+ const selectBig = () => {
4988
+ if (!inv) return;
4989
+ const next = {};
4990
+ for (const s of inv.sessions) if (s.bytes >= 1048576 && !s.active) next[keyOf(s.workspaceKey, s.sessionId)] = true;
4991
+ setSelected(next);
4992
+ };
4993
+ /** 清空选择。 */
4994
+ const clearSel = () => setSelected({});
4995
+ /** 规划移出所选(跨工作区按 sessionIds 一次规划)。 */
4996
+ const planMove = () => {
4997
+ const sessionIds = Object.keys(selected).map((k) => k.slice(k.indexOf("\0") + 1));
4998
+ if (!sessionIds.length) return;
4999
+ action("plan", "edrv.perf.movePlan", { sessionIds }).then((res) => {
5000
+ if (res.items?.length) setConfirm(res);
5001
+ else setError("所选会话没有可移出的项(活跃会话已排除)");
5002
+ });
5003
+ };
5004
+ /** 确认移出:按工作区分组逐组执行,成功后刷新并记录可恢复项。 */
5005
+ const doMove = () => {
5006
+ const grouped = {};
5007
+ for (const item of confirm.items) (grouped[item.workspaceKey] ?? (grouped[item.workspaceKey] = [])).push(item.sessionId);
5008
+ const jobs = Object.entries(grouped).map(([ws, sessionIds]) => rpc("edrv.perf.moveOut", {
5009
+ workspaceKey: ws,
5010
+ sessionIds
5011
+ }));
5012
+ Promise.all(jobs).then((results) => {
5013
+ const allMoved = [];
5014
+ const failures = [];
5015
+ for (const res of results) {
5016
+ if (!res.ok) {
5017
+ failures.push(res.error);
5018
+ continue;
5019
+ }
5020
+ allMoved.push(...res.moved ?? []);
5021
+ failures.push(...(res.failures ?? []).map((f) => f.sessionId + ": " + f.error));
5022
+ }
5023
+ setMoved({
5024
+ moved: allMoved,
5025
+ failures,
5026
+ reclaimedBytes: allMoved.reduce((s, i) => s + i.bytes, 0)
5027
+ });
5028
+ setConfirm(null);
5029
+ setSelected({});
5030
+ load();
5031
+ }).catch((e) => setError(String(e)));
5032
+ };
5033
+ /** 恢复单个已移出会话。 */
5034
+ const doRestore = (item) => {
5035
+ action("restore", "edrv.perf.restore", {
5036
+ workspaceKey: item.workspaceKey,
5037
+ sessionId: item.sessionId
5038
+ }).then(() => {
5039
+ setMoved((old) => old ? {
5040
+ ...old,
5041
+ moved: old.moved.filter((m) => !(m.workspaceKey === item.workspaceKey && m.sessionId === item.sessionId))
5042
+ } : old);
5043
+ load();
5044
+ });
5045
+ };
5046
+ /** 清除归档区早于 N 天的会话(破坏性)。 */
5047
+ const doPurge = () => {
5048
+ if (!window.confirm("确认永久删除归档区中 30 天前的会话?此操作不可恢复(仅归档区,不影响当前 sessions)。")) return;
5049
+ action("purge", "edrv.perf.purgeArchive", { olderThanDays: 30 }).then((res) => {
5050
+ setError("已清除 " + (res.removed ?? 0) + " 个归档会话,释放 " + formatBytes(res.reclaimedBytes ?? 0));
5051
+ });
5052
+ };
5053
+ /** 应用压缩调优到 profile patch。 */
5054
+ const applyConfig = () => {
5055
+ action("apply", "edrv.perf.configApply", {}).then((res) => {
5056
+ setError("已写入压缩调优配置(备份 " + res.backup + "),重启 DSH 后生效");
5057
+ load();
5058
+ });
5059
+ };
5060
+ /** 撤销压缩调优。 */
5061
+ const undoConfig = () => {
5062
+ action("undo", "edrv.perf.configUndo", {}).then(() => {
5063
+ setError("已撤销压缩调优配置(恢复备份)");
5064
+ load();
5065
+ });
5066
+ };
5067
+ const totals = inv?.totals;
5068
+ 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;
5069
+ const workspaceBlocks = inv?.workspaces ? inv.workspaces.map((ws) => {
5070
+ const rows = inv.sessions.filter((s) => s.workspaceKey === ws.workspaceKey);
5071
+ return react.default.createElement("div", {
5072
+ key: ws.workspaceKey,
5073
+ className: "vsm-perf-ws"
5074
+ }, react.default.createElement("div", { className: "vsm-perf-ws-head" }, react.default.createElement("span", {
5075
+ className: "vsm-perf-ws-name",
5076
+ title: ws.workspaceKey
5077
+ }, 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, {
5078
+ key: keyOf(row.workspaceKey, row.sessionId),
5079
+ row,
5080
+ checked: !!selected[keyOf(row.workspaceKey, row.sessionId)],
5081
+ disabled: row.active || busy === "move",
5082
+ onToggle: () => {
5083
+ const k = keyOf(row.workspaceKey, row.sessionId);
5084
+ setSelected((old) => {
5085
+ const next = { ...old };
5086
+ if (next[k]) delete next[k];
5087
+ else next[k] = true;
5088
+ return next;
5089
+ });
5090
+ }
5091
+ })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "无会话")));
5092
+ }) : null;
5093
+ const selCount = Object.keys(selected).length;
5094
+ 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", {
5095
+ className: "vsm-primary",
5096
+ onClick: doMove,
5097
+ disabled: busy === "move"
5098
+ }, busy === "move" ? "执行中…" : "确认移出"), react.default.createElement("button", {
5099
+ className: "vsm-secondary",
5100
+ onClick: () => setConfirm(null)
5101
+ }, "取消")))) : null;
5102
+ 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", {
5103
+ key: item.workspaceKey + item.sessionId,
5104
+ className: "vsm-perf-row"
5105
+ }, 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", {
5106
+ className: "vsm-secondary vsm-small",
5107
+ onClick: () => doRestore(item),
5108
+ disabled: busy === "restore"
5109
+ }, "恢复")))), moved.failures?.length ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "失败:" + moved.failures.join(";")) : null) : null;
5110
+ 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", {
5111
+ className: "vsm-secondary vsm-small",
5112
+ onClick: undoConfig,
5113
+ disabled: busy === "undo"
5114
+ }, "撤销") : react.default.createElement("button", {
5115
+ className: "vsm-primary vsm-small",
5116
+ onClick: applyConfig,
5117
+ disabled: busy === "apply"
5118
+ }, "应用建议配置(需重启)")), react.default.createElement("pre", { className: "vsm-perf-block" }, config.block)) : null;
5119
+ 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"));
5120
+ 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", {
5121
+ className: "vsm-secondary vsm-small",
5122
+ onClick: selectBig
5123
+ }, "圈选 ≥1MB 非活跃"), react.default.createElement("button", {
5124
+ className: "vsm-secondary vsm-small",
5125
+ onClick: clearSel,
5126
+ disabled: !selCount
5127
+ }, "清空选择"), react.default.createElement("span", { className: "vsm-perf-selcount" }, "已选 " + selCount + " 个"), react.default.createElement("button", {
5128
+ className: "vsm-primary vsm-small",
5129
+ onClick: planMove,
5130
+ disabled: !selCount || busy === "plan"
5131
+ }, busy === "plan" ? "规划中…" : "移出所选"), react.default.createElement("button", {
5132
+ className: "vsm-secondary vsm-small",
5133
+ onClick: doPurge,
5134
+ disabled: busy === "purge"
5135
+ }, "清除归档(>30天)"), react.default.createElement("button", {
5136
+ className: "vsm-secondary vsm-small",
5137
+ onClick: load,
5138
+ disabled: busy === "load"
5139
+ }, "⟳ 刷新")), workspaceBlocks, movedBody, configBody, guideBody, confirmBody);
5140
+ }
5141
+ //#endregion
4058
5142
  //#region src/client/ui/McpSettings.ts
4059
5143
  /**
4060
5144
  * dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理。
@@ -4441,6 +5525,8 @@ window.__ModuleLoader__.load({
4441
5525
  })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP"));
4442
5526
  else if (tab === "projects") body = projectBody;
4443
5527
  else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
5528
+ else if (tab === "lsp") body = react.default.createElement(LspSettings, null);
5529
+ else if (tab === "perf") body = react.default.createElement(PerfSettings, null);
4444
5530
  else body = marketBody;
4445
5531
  const form = showForm ? react.default.createElement(McpForm, {
4446
5532
  title: "添加全局 MCP",
@@ -4476,6 +5562,12 @@ window.__ModuleLoader__.load({
4476
5562
  className: tab === "projects" ? "active" : "",
4477
5563
  onClick: () => setTab("projects")
4478
5564
  }, "项目 MCP"), react.default.createElement("button", {
5565
+ className: tab === "lsp" ? "active" : "",
5566
+ onClick: () => setTab("lsp")
5567
+ }, "语言服务器"), react.default.createElement("button", {
5568
+ className: tab === "perf" ? "active" : "",
5569
+ onClick: () => setTab("perf")
5570
+ }, "性能优化"), react.default.createElement("button", {
4479
5571
  className: tab === "market" ? "active" : "",
4480
5572
  onClick: () => setTab("market")
4481
5573
  }, "MCP 市场"), react.default.createElement("button", {
@@ -4951,10 +6043,11 @@ window.__ModuleLoader__.load({
4951
6043
  /**
4952
6044
  * dsh-vscode-mode client — 资源管理栏展开状态持久化(纯函数,可单测)。
4953
6045
  * 对齐 VSCode:只持久化展开路径集合,不缓存目录条目数据(条目实时 listDir)。
6046
+ * 键前缀统一走 paths.ts PathConst(CACHE_KEY.expanded)。
4954
6047
  * 作者 ddj 2026-08-28
4955
6048
  */
4956
6049
  /** localStorage 键前缀(按会话隔离)。 */
4957
- const KEY_PREFIX = "edrv.cache.explorer.v1.";
6050
+ const KEY_PREFIX$1 = CACHE_KEY.expanded;
4958
6051
  /**
4959
6052
  * 归一化展开路径:去空白、反斜杠→斜杠、去首尾斜杠,`..` 段拒绝。
4960
6053
  * @author ddj 2026年08月28号
@@ -5032,7 +6125,7 @@ window.__ModuleLoader__.load({
5032
6125
  */
5033
6126
  function explorerLoad(sessionId) {
5034
6127
  try {
5035
- return parseExplorer(window.localStorage.getItem(KEY_PREFIX + String(sessionId)));
6128
+ return parseExplorer(window.localStorage.getItem(KEY_PREFIX$1 + String(sessionId)));
5036
6129
  } catch (error) {
5037
6130
  return null;
5038
6131
  }
@@ -5045,23 +6138,185 @@ window.__ModuleLoader__.load({
5045
6138
  */
5046
6139
  function explorerSave(sessionId, data) {
5047
6140
  try {
5048
- window.localStorage.setItem(KEY_PREFIX + String(sessionId), serializeExplorer(data));
6141
+ window.localStorage.setItem(KEY_PREFIX$1 + String(sessionId), serializeExplorer(data));
5049
6142
  } catch (error) {}
5050
6143
  }
5051
6144
  //#endregion
6145
+ //#region src/client/state/explorerEntriesCache.ts
6146
+ const KEY_PREFIX = CACHE_KEY.entries;
6147
+ /** 裁剪到上限(目录按 ts 逐出最旧;单目录条目截断)。 */
6148
+ function entriesTrim(data, maxDirs = 300, maxEntries = 500) {
6149
+ const dirs = {};
6150
+ const sorted = Object.entries(data.dirs).sort((a, b) => b[1].ts - a[1].ts);
6151
+ for (const [rel, dir] of sorted) {
6152
+ if (Object.keys(dirs).length >= maxDirs) break;
6153
+ dirs[rel] = {
6154
+ ts: dir.ts,
6155
+ entries: dir.entries.length > maxEntries ? dir.entries.slice(0, maxEntries) : dir.entries
6156
+ };
6157
+ }
6158
+ return {
6159
+ v: 2,
6160
+ root: data.root,
6161
+ dirs
6162
+ };
6163
+ }
6164
+ /** 序列化缓存数据。 */
6165
+ function entriesSerialize(data) {
6166
+ return JSON.stringify(data);
6167
+ }
6168
+ /** 解析缓存文本;损坏/格式不符 → null。 */
6169
+ function entriesParse(text) {
6170
+ if (!text) return null;
6171
+ try {
6172
+ const raw = JSON.parse(text);
6173
+ if (!raw || raw.v !== 2 || !raw.dirs || typeof raw.dirs !== "object") return null;
6174
+ const dirs = {};
6175
+ for (const [rel, d] of Object.entries(raw.dirs)) {
6176
+ if (!d || typeof d !== "object") continue;
6177
+ const dir = d;
6178
+ if (!Array.isArray(dir.entries)) continue;
6179
+ const entries = [];
6180
+ for (const e of dir.entries) {
6181
+ if (!e || typeof e !== "object") continue;
6182
+ const item = e;
6183
+ if (typeof item.name !== "string" || typeof item.path !== "string") continue;
6184
+ const type = item.type === "directory" ? "directory" : item.type === "file" ? "file" : "other";
6185
+ entries.push({
6186
+ name: item.name,
6187
+ path: item.path,
6188
+ type
6189
+ });
6190
+ }
6191
+ if (!entries.length) continue;
6192
+ dirs[rel] = {
6193
+ ts: typeof dir.ts === "number" ? dir.ts : 0,
6194
+ entries
6195
+ };
6196
+ }
6197
+ return {
6198
+ v: 2,
6199
+ root: typeof raw.root === "string" ? raw.root : null,
6200
+ dirs
6201
+ };
6202
+ } catch (error) {
6203
+ return null;
6204
+ }
6205
+ }
6206
+ /** sessionId → 缓存镜像(同步读,避免每次渲染 parse)。 */
6207
+ const mirrors = /* @__PURE__ */ new Map();
6208
+ /** sessionId → 落盘防抖计时器。 */
6209
+ const saveTimers = /* @__PURE__ */ new Map();
6210
+ /** 单调时钟:连续 put 的 ts 严格递增(同毫秒不并列,逐出顺序可预期)。 */
6211
+ let clock = 0;
6212
+ /** 取下一时间戳(不小于真实时间且严格递增)。 */
6213
+ function nextTs() {
6214
+ clock = Math.max(Date.now(), clock + 1);
6215
+ return clock;
6216
+ }
6217
+ /** localStorage 读取(不可用 → null)。 */
6218
+ function readRaw(sessionId) {
6219
+ try {
6220
+ return window.localStorage.getItem(KEY_PREFIX + sessionId);
6221
+ } catch (error) {
6222
+ return null;
6223
+ }
6224
+ }
6225
+ /** localStorage 写入(配额/隐私模式失败静默)。 */
6226
+ function writeRaw(sessionId, text) {
6227
+ try {
6228
+ window.localStorage.setItem(KEY_PREFIX + sessionId, text);
6229
+ } catch (error) {}
6230
+ }
6231
+ /** 取(或建)会话缓存镜像。 */
6232
+ function mirrorOf(sessionId) {
6233
+ const existing = mirrors.get(sessionId);
6234
+ if (existing) return existing;
6235
+ const data = entriesParse(readRaw(sessionId)) ?? {
6236
+ v: 2,
6237
+ root: null,
6238
+ dirs: {}
6239
+ };
6240
+ mirrors.set(sessionId, data);
6241
+ return data;
6242
+ }
6243
+ /** 调度防抖落盘(重置已有计时器)。 */
6244
+ function scheduleSave(sessionId) {
6245
+ const prev = saveTimers.get(sessionId);
6246
+ if (prev) clearTimeout(prev);
6247
+ saveTimers.set(sessionId, setTimeout(() => {
6248
+ saveTimers.delete(sessionId);
6249
+ const data = mirrors.get(sessionId);
6250
+ if (data) writeRaw(sessionId, entriesSerialize(entriesTrim(data)));
6251
+ }, 300));
6252
+ }
6253
+ /**
6254
+ * 读某目录缓存条目(无 → null;展示不受新鲜度限制)。
6255
+ * @author ddj 2026年08月31号
6256
+ * @param sessionId 会话 id
6257
+ * @param rel 目录相对路径('' = 根)
6258
+ * @returns 缓存条目或 null
6259
+ */
6260
+ function entriesCacheGet(sessionId, rel) {
6261
+ if (!sessionId) return null;
6262
+ const dir = mirrorOf(sessionId).dirs[rel];
6263
+ if (!dir || !dir.entries.length) return null;
6264
+ return dir.entries;
6265
+ }
6266
+ /**
6267
+ * 某目录缓存是否新鲜(预取去重用)。
6268
+ * @author ddj 2026年08月31号
6269
+ * @param sessionId 会话 id
6270
+ * @param rel 目录相对路径
6271
+ * @returns 新鲜返回 true
6272
+ */
6273
+ function entriesCacheIsFresh(sessionId, rel) {
6274
+ if (!sessionId) return false;
6275
+ const dir = mirrorOf(sessionId).dirs[rel];
6276
+ return Boolean(dir && Date.now() - dir.ts < 3e4);
6277
+ }
6278
+ /**
6279
+ * 写入某目录缓存条目(内存镜像 + 防抖落盘;单目录条目截断)。
6280
+ * @author ddj 2026年08月31号
6281
+ * @param sessionId 会话 id
6282
+ * @param rel 目录相对路径
6283
+ * @param entries 目录条目
6284
+ */
6285
+ function entriesCachePut(sessionId, rel, entries) {
6286
+ if (!sessionId) return;
6287
+ const data = mirrorOf(sessionId);
6288
+ data.dirs[rel] = {
6289
+ ts: nextTs(),
6290
+ entries: entries.slice(0, 500)
6291
+ };
6292
+ data.dirs = entriesTrim(data).dirs;
6293
+ scheduleSave(sessionId);
6294
+ }
6295
+ //#endregion
5052
6296
  //#region src/client/sidebar/panels/FileExplorer.ts
5053
6297
  /**
5054
- * dsh-vscode-mode client — 侧边栏「文件管理」面板(懒加载目录树)。
5055
- * 点目录展开/收起(首次经 edrv.listDir 拉取并缓存),点文件经 ctx.openFile 打开;
5056
- * 差异角标取 pendingByPath,活动文件高亮;edrv:refresh 事件与手动刷新重载树。
5057
- * 右键行/空白弹出菜单:项来自 ctx.fileMenuItems 注册表(可拓展),
5058
- * 内置「在文件浏览器中打开」经 edrv.revealInExplorer 定位/打开。
5059
- * 作者 ddj 2026-08-26 / 2026-08-27
6298
+ * dsh-vscode-mode client — 侧边栏「文件管理」面板(懒加载目录树 + SWR 缓存)。
6299
+ * 展开目录立即渲染(dirs 内存态 → 本地条目缓存兜底),后台刷新(edrv.listDir 走
6300
+ * host 树索引,命中近乎零成本);挂载/切会话从缓存即时恢复展开树;⟳/edrv:refresh
6301
+ * 保留旧条目强制后台重列(force);每次用户发起加载后预取 ≤4 个子目录(排除重型
6302
+ * 目录、缓存新鲜跳过、不级联);10s 轻量跟随已展开目录(命中索引,RPC 近零成本)。
6303
+ * 差异角标/右键菜单/展开状态持久化行为不变。
6304
+ * 作者 ddj 2026-08-26 / 2026-08-27 / 2026-08-31
5060
6305
  */
5061
6306
  const DIR_CAP = 4e3;
5062
6307
  const SAVE_DEBOUNCE_MS = 300;
6308
+ const FOLLOW_INTERVAL_MS = 1e4;
6309
+ const PREFETCH_MAX = 4;
6310
+ const PREFETCH_EXCLUDED = /* @__PURE__ */ new Set([
6311
+ "node_modules",
6312
+ ".git",
6313
+ ".hg",
6314
+ ".svn",
6315
+ ".pnpm",
6316
+ ".pnpm-store"
6317
+ ]);
5063
6318
  /**
5064
- * 目录树面板主体。
6319
+ * 目录树面板主体(SWR:有缓存先渲染,无缓存才显示加载态;加载总在后台)。
5065
6320
  * @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath/fileMenuItems/notify)
5066
6321
  */
5067
6322
  function FileExplorer(props) {
@@ -5074,29 +6329,65 @@ window.__ModuleLoader__.load({
5074
6329
  const [dirs, setDirs] = react.default.useState({});
5075
6330
  const [expanded, setExpanded] = react.default.useState({});
5076
6331
  const [loading, setLoading] = react.default.useState({});
5077
- const [error, setError] = react.default.useState(null);
6332
+ const [errors, setErrors] = react.default.useState({});
5078
6333
  const [menu, setMenu] = react.default.useState(null);
5079
6334
  const tokensRef = react.default.useRef({});
5080
6335
  const expandedRef = react.default.useRef({});
5081
6336
  expandedRef.current = expanded;
6337
+ const loadingRef = react.default.useRef({});
6338
+ loadingRef.current = loading;
6339
+ const dirsRef = react.default.useRef({});
6340
+ dirsRef.current = dirs;
5082
6341
  const saveTimerRef = react.default.useRef(null);
5083
- const loadDir = (rel) => {
6342
+ const loadDirRef = react.default.useRef(null);
6343
+ const refreshRef = react.default.useRef(null);
6344
+ /** 渲染取数:内存态 → 本地条目缓存 → null(显示加载态)。 */
6345
+ const entriesOf = (rel) => dirsRef.current[rel] ?? entriesCacheGet(sessionId, rel) ?? null;
6346
+ /** 预取子目录:≤4 个、排除重型目录、缓存新鲜跳过、已加载跳过、不级联。 */
6347
+ const prefetchDirs = (rel, entries) => {
6348
+ const want = [];
6349
+ for (const e of entries) {
6350
+ if (e.type !== "directory") continue;
6351
+ if (PREFETCH_EXCLUDED.has(e.name)) continue;
6352
+ if (entriesCacheIsFresh(sessionId, e.path)) continue;
6353
+ if (dirsRef.current[e.path] !== void 0) continue;
6354
+ want.push(e.path);
6355
+ if (want.length >= PREFETCH_MAX) break;
6356
+ }
6357
+ for (const sub of want) loadDir(sub, {});
6358
+ };
6359
+ /**
6360
+ * 后台加载目录:token 守卫防乱序;force 跳过索引命中强制实列;
6361
+ * 非 force 且已在途时跳过(避免轮询打断用户展开的响应)。
6362
+ */
6363
+ const loadDir = (rel, opts) => {
6364
+ const force = opts?.force === true;
6365
+ const doPrefetch = opts?.prefetch === true;
6366
+ if (!force && loadingRef.current[rel]) return;
5084
6367
  const token = (tokensRef.current[rel] || 0) + 1;
5085
6368
  tokensRef.current[rel] = token;
5086
6369
  setLoading((prev) => Object.assign({}, prev, { [rel]: true }));
5087
- setError(null);
5088
- rpc("edrv.listDir", {
6370
+ setErrors((prev) => {
6371
+ const next = Object.assign({}, prev);
6372
+ delete next[rel];
6373
+ return next;
6374
+ });
6375
+ const args = {
5089
6376
  sessionId,
5090
6377
  path: rel
5091
- }).then((res) => {
6378
+ };
6379
+ if (force) args.force = true;
6380
+ rpc("edrv.listDir", args).then((res) => {
5092
6381
  if (tokensRef.current[rel] !== token) return;
5093
6382
  if (res && res.ok && Array.isArray(res.entries)) {
5094
6383
  setDirs((prev) => Object.assign({}, prev, { [rel]: res.entries }));
5095
- if (res.root && !root) setRoot(res.root);
5096
- } else setError(res?.error ? String(res.error) : "读取目录失败");
6384
+ if (res.root) setRoot((prev) => prev || res.root);
6385
+ entriesCachePut(sessionId, rel, res.entries);
6386
+ if (doPrefetch) prefetchDirs(rel, res.entries);
6387
+ } else if (!entriesOf(rel)) setErrors((prev) => Object.assign({}, prev, { [rel]: res?.error ? String(res.error) : "读取目录失败" }));
5097
6388
  }).catch((e) => {
5098
6389
  if (tokensRef.current[rel] !== token) return;
5099
- setError("读取目录异常:" + String(e));
6390
+ if (!entriesOf(rel)) setErrors((prev) => Object.assign({}, prev, { [rel]: "读取目录异常:" + String(e) }));
5100
6391
  }).finally(() => {
5101
6392
  if (tokensRef.current[rel] !== token) return;
5102
6393
  setLoading((prev) => {
@@ -5106,35 +6397,42 @@ window.__ModuleLoader__.load({
5106
6397
  });
5107
6398
  });
5108
6399
  };
6400
+ loadDirRef.current = loadDir;
5109
6401
  const toggle = (rel) => {
5110
6402
  if (expanded[rel] === true) {
5111
6403
  setExpanded((prev) => Object.assign({}, prev, { [rel]: false }));
5112
6404
  return;
5113
6405
  }
5114
- if (!dirs[rel]) loadDir(rel);
5115
6406
  setExpanded((prev) => Object.assign({}, prev, { [rel]: true }));
6407
+ loadDir(rel, { prefetch: true });
5116
6408
  };
5117
6409
  const refresh = () => {
5118
6410
  const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
5119
6411
  tokensRef.current = {};
5120
6412
  setMenu(null);
5121
- setDirs({});
6413
+ setErrors({});
5122
6414
  setLoading({});
5123
- setError(null);
5124
6415
  if (keep.length) {
5125
6416
  const next = {};
5126
6417
  for (const rel of keep) next[rel] = true;
5127
6418
  setExpanded(next);
5128
- for (const rel of keep) loadDir(rel);
6419
+ for (const rel of keep) loadDir(rel, {
6420
+ force: true,
6421
+ prefetch: true
6422
+ });
5129
6423
  } else setExpanded({});
5130
- loadDir("");
6424
+ loadDir("", {
6425
+ force: true,
6426
+ prefetch: true
6427
+ });
5131
6428
  };
6429
+ refreshRef.current = refresh;
5132
6430
  react.default.useEffect(() => {
5133
6431
  tokensRef.current = {};
5134
6432
  setDirs({});
5135
6433
  setExpanded({});
5136
6434
  setLoading({});
5137
- setError(null);
6435
+ setErrors({});
5138
6436
  setMenu(null);
5139
6437
  setRoot(null);
5140
6438
  const cached = sessionId ? explorerLoad(sessionId) : null;
@@ -5145,8 +6443,8 @@ window.__ModuleLoader__.load({
5145
6443
  for (const rel of restored) next[rel] = true;
5146
6444
  setExpanded(next);
5147
6445
  }
5148
- loadDir("");
5149
- for (const rel of restored) if (rel) loadDir(rel);
6446
+ loadDir("", { prefetch: true });
6447
+ for (const rel of restored) if (rel) loadDir(rel, { prefetch: true });
5150
6448
  }, [sessionId]);
5151
6449
  react.default.useEffect(() => {
5152
6450
  if (saveTimerRef.current) {
@@ -5174,10 +6472,19 @@ window.__ModuleLoader__.load({
5174
6472
  sessionId
5175
6473
  ]);
5176
6474
  react.default.useEffect(() => {
5177
- const onRefresh = () => refresh();
6475
+ const onRefresh = () => refreshRef.current?.();
5178
6476
  window.addEventListener("edrv:refresh", onRefresh);
5179
6477
  return () => window.removeEventListener("edrv:refresh", onRefresh);
5180
6478
  }, []);
6479
+ react.default.useEffect(() => {
6480
+ if (!sessionId) return;
6481
+ const timer = setInterval(() => {
6482
+ const open = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
6483
+ if (!open.length) return;
6484
+ for (const rel of open) loadDirRef.current?.(rel, {});
6485
+ }, FOLLOW_INTERVAL_MS);
6486
+ return () => clearInterval(timer);
6487
+ }, [sessionId]);
5181
6488
  const rootName = root ? String(root).split(/[\\/]/).pop() || root : "";
5182
6489
  const rowEl = (e, depth, isDir, isOpen) => {
5183
6490
  const pending = isDir ? 0 : pendingByPath[e.path] ?? 0;
@@ -5210,7 +6517,7 @@ window.__ModuleLoader__.load({
5210
6517
  }, react.default.createElement("span", { className: "edrv-tree-chev" }, isDir ? isOpen ? "▾" : "▸" : ""), react.default.createElement("span", { className: "edrv-tree-name" + (dim ? " edrv-tree-dim" : "") }, isDir ? isOpen ? "📂" : "📁" : "📄", " ", e.name), pending > 0 ? react.default.createElement("span", { className: "edrv-tree-badge" }, String(pending)) : null);
5211
6518
  };
5212
6519
  const rowsOf = (rel, depth) => {
5213
- const entries = dirs[rel];
6520
+ const entries = entriesOf(rel);
5214
6521
  if (!entries) return [react.default.createElement("div", {
5215
6522
  key: rel + ":loading",
5216
6523
  className: "edrv-tree-loading",
@@ -5253,6 +6560,8 @@ window.__ModuleLoader__.load({
5253
6560
  separator: item.separator,
5254
6561
  onClick: () => item.run(menu.target, ctx)
5255
6562
  })) : [];
6563
+ const errorList = Object.entries(errors);
6564
+ const errorText = errorList.length ? errorList[0][1] : null;
5256
6565
  return react.default.createElement("div", {
5257
6566
  className: "edrv-side-panel",
5258
6567
  onContextMenu: onPanelContext
@@ -5263,7 +6572,7 @@ window.__ModuleLoader__.load({
5263
6572
  className: "edrv-side-btn",
5264
6573
  title: "刷新目录树",
5265
6574
  onClick: refresh
5266
- }, "⟳")), react.default.createElement("div", { className: "edrv-tree" }, error ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(error)), react.default.createElement("button", {
6575
+ }, "⟳")), react.default.createElement("div", { className: "edrv-tree" }, errorText ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(errorText)), react.default.createElement("button", {
5267
6576
  className: "edrv-side-btn",
5268
6577
  onClick: () => refresh()
5269
6578
  }, "重试")) : null, rowsOf("", 0)), menu && menuEntries.length ? react.default.createElement(ContextMenu, {
@@ -5329,6 +6638,7 @@ window.__ModuleLoader__.load({
5329
6638
  const [sectionOpen, setSectionOpen] = react.default.useState(false);
5330
6639
  const [status, setStatus] = react.default.useState("idle");
5331
6640
  const [error, setError] = react.default.useState("");
6641
+ const [warning, setWarning] = react.default.useState("");
5332
6642
  const [matches, setMatches] = react.default.useState(null);
5333
6643
  const [truncated, setTruncated] = react.default.useState(false);
5334
6644
  const [collapsed, setCollapsed] = react.default.useState({});
@@ -5356,7 +6666,7 @@ window.__ModuleLoader__.load({
5356
6666
  react.default.useEffect(() => {
5357
6667
  if (!sessionId) return;
5358
6668
  try {
5359
- const saved = JSON.parse(localStorage.getItem("edrv.search.v1." + String(sessionId)) || "null");
6669
+ const saved = JSON.parse(localStorage.getItem(CACHE_KEY.search + String(sessionId)) || "null");
5360
6670
  if (saved && typeof saved === "object") {
5361
6671
  if (typeof saved.query === "string") setQuery(saved.query);
5362
6672
  if (typeof saved.matchCase === "boolean") setMatchCase(saved.matchCase);
@@ -5373,7 +6683,7 @@ window.__ModuleLoader__.load({
5373
6683
  react.default.useEffect(() => {
5374
6684
  if (!sessionId) return;
5375
6685
  try {
5376
- localStorage.setItem("edrv.search.v1." + String(sessionId), JSON.stringify({
6686
+ localStorage.setItem(CACHE_KEY.search + String(sessionId), JSON.stringify({
5377
6687
  query,
5378
6688
  matchCase,
5379
6689
  wholeWord,
@@ -5412,6 +6722,7 @@ window.__ModuleLoader__.load({
5412
6722
  const req = requestRef.current;
5413
6723
  setStatus("searching");
5414
6724
  setError("");
6725
+ setWarning("");
5415
6726
  rpc("edrv.searchContent", {
5416
6727
  sessionId,
5417
6728
  query: clean,
@@ -5425,6 +6736,7 @@ window.__ModuleLoader__.load({
5425
6736
  if (res && res.ok) {
5426
6737
  setMatches(Array.isArray(res.matches) ? res.matches : []);
5427
6738
  setTruncated(res.truncated === true);
6739
+ setWarning(res?.warning ? String(res.warning) : "");
5428
6740
  setStatus("done");
5429
6741
  } else {
5430
6742
  setError(res?.error ? String(res.error) : "搜索失败");
@@ -5562,6 +6874,10 @@ window.__ModuleLoader__.load({
5562
6874
  onClick: () => toggleOpt("excludeOn", !excludeOn)
5563
6875
  }, "⚙"))) : null;
5564
6876
  const summary = status === "done" ? react.default.createElement("div", { className: "edrv-search-summary" }, totalFiles > 0 ? totalFiles + " 个文件 · " + totalMatches + " 处匹配" + (truncated ? "(已截断)" : "") : "无匹配结果") : null;
6877
+ const warnBanner = warning ? react.default.createElement("div", {
6878
+ className: "edrv-search-warn",
6879
+ title: warning
6880
+ }, warning) : null;
5565
6881
  const body = (() => {
5566
6882
  if (status === "searching") return react.default.createElement("div", { className: "edrv-search-empty" }, "搜索中…");
5567
6883
  if (status === "error") return react.default.createElement("div", { className: "edrv-search-error" }, error);
@@ -5603,7 +6919,7 @@ window.__ModuleLoader__.load({
5603
6919
  }
5604
6920
  if (e.key === "Escape") inputRef.current?.blur?.();
5605
6921
  }
5606
- })), optsBar, filterRows, summary, body);
6922
+ })), optsBar, filterRows, summary, warnBanner, body);
5607
6923
  }
5608
6924
  //#endregion
5609
6925
  //#region src/client/sidebar/panels/index.ts
@@ -5850,12 +7166,36 @@ window.__ModuleLoader__.load({
5850
7166
  computeEnds(out, lines.length);
5851
7167
  return out;
5852
7168
  }
5853
- /** Lua:`function Foo:bar` / `M.bar = class|function`。 */
7169
+ /** Lua:`function Foo:bar` / `M.bar = class|function` + EmmyLua 注解(---@class/@alias/@enum/@field)。 */
5854
7170
  function parseLua(text) {
5855
7171
  const out = [];
5856
7172
  const lines = text.split("\n");
7173
+ /** 当前注解类(收拢紧随的 ---@field 为字段子节点)。 */
7174
+ let pendingClass = null;
5857
7175
  for (let i = 0; i < lines.length; i++) {
5858
7176
  const line = lines[i];
7177
+ const am = /^[ \t]*---@(class|alias|enum|field|type)\s+([^\r\n]+)/.exec(line);
7178
+ if (am) {
7179
+ const tag = am[1];
7180
+ const name = (am[2].trim().match(/^([A-Za-z_][\w]*)/) || [])[1] ?? "";
7181
+ if (tag === "class") {
7182
+ if (name) {
7183
+ const item = mk(i, name, SK.Class, line);
7184
+ item.children = [];
7185
+ out.push(item);
7186
+ pendingClass = item;
7187
+ } else pendingClass = null;
7188
+ } else if (tag === "alias") {
7189
+ if (name) out.push(mk(i, name, SK.TypeParameter, line));
7190
+ pendingClass = null;
7191
+ } else if (tag === "enum") {
7192
+ if (name) out.push(mk(i, name, SK.Enum, line));
7193
+ pendingClass = null;
7194
+ } else if (tag === "field") {
7195
+ if (name && pendingClass) pendingClass.children.push(mk(i, name, SK.Field, line));
7196
+ } else if (tag === "type") pendingClass = null;
7197
+ continue;
7198
+ }
5859
7199
  if (/^\s*--/.test(line)) continue;
5860
7200
  const fm = /^[ \t]*(?:local[ \t]+)?function[ \t]+([A-Za-z_][\w.:]*)/.exec(line);
5861
7201
  if (fm) {
@@ -6213,6 +7553,7 @@ window.__ModuleLoader__.load({
6213
7553
  setError(null);
6214
7554
  resolveOutline(sources, {
6215
7555
  languageId: model.getLanguageId(),
7556
+ path: modelPath,
6216
7557
  model,
6217
7558
  editor: ed,
6218
7559
  monaco: window.monaco
@@ -6396,6 +7737,43 @@ window.__ModuleLoader__.load({
6396
7737
  render: (ctx) => react.default.createElement(OutlinePanel, { ctx })
6397
7738
  };
6398
7739
  }
7740
+ /** LSP 大纲源:lua/csharp 等有 host 服务器能力的语言。 */
7741
+ function createLspOutlineSource() {
7742
+ return {
7743
+ id: "lsp",
7744
+ priority: 60,
7745
+ provides: (languageId) => languageId === "lua" || languageId === "csharp",
7746
+ async get(input) {
7747
+ const model = input.model;
7748
+ if (!model || typeof model.getValue !== "function") return [];
7749
+ const path = input.path || (model.uri && model.uri.path ? decodeURIComponent(String(model.uri.path).replace(/^\//, "")) : null);
7750
+ if (!path) return [];
7751
+ const symbols = await fetchDocumentSymbols(path, model.getValue());
7752
+ return (Array.isArray(symbols) ? symbols : []).map(toOutline).filter(Boolean);
7753
+ }
7754
+ };
7755
+ }
7756
+ /** LSP SymbolInfo(host 归一化,0-based range)→ 大纲 OutlineSymbol(1-based)。 */
7757
+ function toOutline(symbol) {
7758
+ if (!symbol || typeof symbol.name !== "string") return null;
7759
+ const range = symbol.range || {};
7760
+ const sel = symbol.selectionRange || range;
7761
+ const start = range.start || {};
7762
+ const select = sel.start || {};
7763
+ const out = {
7764
+ name: symbol.name,
7765
+ kind: typeof symbol.kind === "number" ? symbol.kind : 0,
7766
+ startLine: Math.max(1, (start.line ?? 0) + 1),
7767
+ endLine: Math.max(1, (range.end?.line ?? start.line ?? 0) + 1),
7768
+ selectLine: Math.max(1, (select.line ?? start.line ?? 0) + 1)
7769
+ };
7770
+ if (typeof symbol.detail === "string" && symbol.detail) out.detail = symbol.detail;
7771
+ if (Array.isArray(symbol.children) && symbol.children.length) {
7772
+ const kids = symbol.children.map(toOutline).filter(Boolean);
7773
+ if (kids.length) out.children = kids;
7774
+ }
7775
+ return out;
7776
+ }
6399
7777
  //#endregion
6400
7778
  //#region src/client/index.ts
6401
7779
  /**
@@ -6438,17 +7816,25 @@ window.__ModuleLoader__.load({
6438
7816
  if (typeof window !== "undefined" && monacoList && typeof monacoList.subscribe === "function") {
6439
7817
  const list = monacoList;
6440
7818
  const schedulePreload = () => {
6441
- const preload = () => loadMonaco(() => {}).catch(() => {});
7819
+ const preload = () => loadMonaco(() => {}).then((m) => setupLsp(m)).catch(() => {});
6442
7820
  if (typeof window.requestIdleCallback === "function") window.requestIdleCallback(preload, { timeout: 2e3 });
6443
7821
  else schedule(preload, 300);
6444
7822
  };
6445
7823
  const onSessionEnter = () => {
6446
- if (!list.getSnapshot()?.current) return;
7824
+ const current = list.getSnapshot()?.current;
7825
+ if (!current) return;
7826
+ setSession(current);
6447
7827
  schedulePreload();
6448
7828
  };
6449
7829
  ctx.effect(() => list.subscribe(onSessionEnter), "vscode-mode: monaco session trigger");
6450
7830
  onSessionEnter();
6451
7831
  }
7832
+ if (monacoList && typeof monacoList.subscribe === "function") {
7833
+ const list = monacoList;
7834
+ ctx.effect(() => list.subscribe(() => {
7835
+ setSession(list.getSnapshot()?.current);
7836
+ }), "vscode-mode: lsp session sync");
7837
+ }
6452
7838
  const originalOpenPath = workspaces?.openPath;
6453
7839
  const binder = pickSettingsBinder(ctx);
6454
7840
  const settings = binder.scope;
@@ -6492,6 +7878,7 @@ window.__ModuleLoader__.load({
6492
7878
  const outlineSources = createOutlineSourceRegistry();
6493
7879
  ctx.provide("edrvOutlineSources", outlineSources);
6494
7880
  ctx.effect(() => registerBuiltinOutlineSources(outlineSources), "vscode-mode: outline sources");
7881
+ ctx.effect(() => outlineSources.register(createLspOutlineSource()), "vscode-mode: outline lsp source");
6495
7882
  ctx.effect(() => sidebarPanels.register(createOutlinePanel()), "vscode-mode: sidebar panel outline");
6496
7883
  ctx.effect(() => registry.register({
6497
7884
  id: "system",
@@ -6622,6 +8009,12 @@ window.__ModuleLoader__.load({
6622
8009
  order: 90,
6623
8010
  label: "差异"
6624
8011
  }, (props) => react.default.createElement(DiffBadge, Object.assign({}, props)));
8012
+ registerSlotSafely(ctx, {
8013
+ name: "conversation.session.header.utilities",
8014
+ id: "edrv-perf-size",
8015
+ order: 89,
8016
+ label: "会话体积"
8017
+ }, (props) => react.default.createElement(PerfSizeBadge, Object.assign({}, props, { sessions })));
6625
8018
  registerSlotSafely(ctx, {
6626
8019
  name: "settings.section",
6627
8020
  id: "vscode-mode",