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.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/lib/client.js +1439 -46
- package/lib/client.js.map +1 -1
- package/lib/index.js +3519 -222
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/capture.ts +3 -0
- package/src/client/events.ts +11 -0
- package/src/client/index.ts +20 -2
- package/src/client/monaco/lsp/index.ts +30 -0
- package/src/client/monaco/lsp/lspClient.ts +192 -0
- package/src/client/monaco/lsp/providers.ts +229 -0
- package/src/client/outline/OutlinePanel.tsx +1 -1
- package/src/client/outline/lspSource.ts +49 -0
- package/src/client/outline/parse.ts +31 -1
- package/src/client/outline/types.ts +2 -0
- package/src/client/paths.ts +36 -0
- package/src/client/sidebar/panels/FileExplorer.ts +95 -30
- package/src/client/sidebar/panels/SearchPanel.ts +11 -2
- package/src/client/state/explorerCache.ts +3 -1
- package/src/client/state/explorerEntriesCache.ts +172 -0
- package/src/client/state/viewStateCache.ts +3 -1
- package/src/client/styles/editor.css +16 -0
- package/src/client/styles/mcp.css +63 -0
- package/src/client/ui/EditorView.ts +61 -6
- package/src/client/ui/LspSettings.ts +268 -0
- package/src/client/ui/McpSettings.ts +5 -1
- package/src/client/ui/PerfSettings.ts +242 -0
- package/src/client/ui/PerfSizeBadge.ts +54 -0
- package/src/compat.ts +3 -2
- package/src/fileOpenSettings.ts +1 -0
- package/src/index.ts +25 -2
- package/src/lsp/client.ts +205 -0
- package/src/lsp/config.ts +107 -0
- package/src/lsp/extmgr.ts +208 -0
- package/src/lsp/jsonrpc.ts +86 -0
- package/src/lsp/manager.ts +133 -0
- package/src/lsp/providers.ts +235 -0
- package/src/lsp/rpc.ts +334 -0
- package/src/lsp/server.ts +346 -0
- package/src/lsp/settings.ts +44 -0
- package/src/lsp/transport.ts +143 -0
- package/src/lsp/uri.ts +62 -0
- package/src/lsp/zip.ts +82 -0
- package/src/paths.ts +284 -0
- package/src/perf.ts +340 -0
- package/src/perfPatch.ts +76 -0
- package/src/routes.ts +7 -12
- package/src/rpc.ts +184 -36
- package/src/search/content.ts +14 -7
- package/src/search/ripgrep.ts +54 -1
- package/src/search/types.ts +4 -1
- package/src/shared/lsp.ts +138 -0
- package/src/shared/rpc.ts +103 -2
- package/src/store.ts +2 -2
- package/src/treeIndex.ts +397 -0
- 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$
|
|
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$
|
|
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$
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
-
*
|
|
5056
|
-
*
|
|
5057
|
-
*
|
|
5058
|
-
*
|
|
5059
|
-
*
|
|
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 [
|
|
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
|
|
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
|
-
|
|
5088
|
-
|
|
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
|
-
}
|
|
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
|
|
5096
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = () =>
|
|
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 =
|
|
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" },
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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",
|