dsh-vscode-mode 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -29,7 +29,7 @@ window.__ModuleLoader__.load({
29
29
  let react = require("react");
30
30
  react = __toESM(react, 1);
31
31
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
32
- 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] { --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-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; min-height: 0; position: relative; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 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: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 16px rgba(31,41,51,.12); overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px; 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-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";
32
+ 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-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; }\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-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";
33
33
  const tagId$1 = "dsh-vscode-mode/editor.css";
34
34
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
35
35
  const tag = document.createElement("style");
@@ -82,28 +82,53 @@ window.__ModuleLoader__.load({
82
82
  * 作者 ddj 2026-08-20
83
83
  */
84
84
  /**
85
- * 打开中央「文件编辑」页签:点击 shell 会话头部的视图页签(DOM 级,无需 store actions)。
86
- * @author ddj 2026年08月20号
87
- * @param path 要打开的路径(可空)
85
+ * 选择中央「文件编辑」页签(DOM 级,无需 store actions)。
86
+ * @author ddj 2026年08月26号
88
87
  */
89
- function openEditorView(path) {
88
+ function selectEditorTab() {
90
89
  const tabs = Array.from(document.querySelectorAll("div[role=\"tablist\"] button[role=\"tab\"]"));
91
- for (const b of tabs) if (b.textContent && b.textContent.includes("文件编辑")) {
92
- b.click();
90
+ for (const tab of tabs) if (tab.textContent && tab.textContent.includes("文件编辑")) {
91
+ tab.click();
93
92
  break;
94
93
  }
95
- const p = path ?? null;
94
+ }
95
+ /**
96
+ * 打开中央「文件编辑」页签。
97
+ * @author ddj 2026年08月26号
98
+ * @param path 要打开的路径(可空)
99
+ */
100
+ function openEditorView(path) {
101
+ selectEditorTab();
102
+ const target = path ?? null;
96
103
  setTimeout(() => {
97
- window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: { path: p } }));
104
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: { path: target } }));
105
+ }, 80);
106
+ }
107
+ /**
108
+ * 打开文件编辑页并聚焦指定文件的首个差异。
109
+ * @author ddj 2026年08月26号
110
+ * @param path 待聚焦的差异文件路径
111
+ */
112
+ function openDiffView(path) {
113
+ selectEditorTab();
114
+ setTimeout(() => {
115
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
116
+ path,
117
+ focusDiff: true
118
+ } }));
98
119
  }, 80);
99
120
  }
100
121
  /** 差异状态变化:触发角标/DiffBox 重拉。 */
101
122
  function emitRefresh() {
102
123
  window.dispatchEvent(new CustomEvent("edrv:refresh"));
103
124
  }
104
- /** 拉起 DiffLauncher。 */
105
- function emitShowLauncher() {
106
- window.dispatchEvent(new CustomEvent("edrv:show-launcher"));
125
+ /**
126
+ * 拉起 DiffLauncher。
127
+ * @author ddj 2026年08月26号
128
+ * @param tab 可选的目标页签
129
+ */
130
+ function emitShowLauncher(tab) {
131
+ window.dispatchEvent(new CustomEvent("edrv:show-launcher", { detail: { tab } }));
107
132
  }
108
133
  //#endregion
109
134
  //#region src/client/monaco/loader.ts
@@ -863,199 +888,6 @@ window.__ModuleLoader__.load({
863
888
  lineHeight: 1.55
864
889
  };
865
890
  //#endregion
866
- //#region src/client/ui/DiffBox.ts
867
- /**
868
- * dsh-vscode-mode client — DiffBox:每个文件底部紧凑差异条(per-file hunk + ⋮ 二级菜单)。
869
- * 迁移自原 src/client/index.ts 的 DiffBox,语义不改。
870
- * 作者 ddj 2026-08-20
871
- */
872
- /**
873
- * 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
874
- * (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
875
- */
876
- function DiffBox(props) {
877
- const { pendingRegions, staleRegions, onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles, allPendingCount, onRollback, onJump, otherFiles, onOpenOther, onOpenLauncher, onRefresh, activePath, diffIdx, diffTotal, fileIdx, fileTotal, onPrevDiff, onNextDiff, onPrevFile, onNextFile } = props;
878
- const [expanded, setExpanded] = react.default.useState(false);
879
- const [menuOpen, setMenuOpen] = react.default.useState(false);
880
- const canAct = pendingRegions.length > 0;
881
- const base = String(activePath || "").split(/[\\/]/).pop() || "";
882
- let bodyEl = null;
883
- if (expanded) {
884
- const rows = pendingRegions.map((r) => {
885
- const key = callIdAttr(r.callId, r.idx);
886
- return react.default.createElement("div", {
887
- key,
888
- className: "edrv-diffrow",
889
- "data-edrv-hunk": key,
890
- title: "跳转到 L" + (r.start ?? "?"),
891
- onClick: () => onJump(r)
892
- }, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + "-" + (r.end ?? "?")), r.oldLines.length ? react.default.createElement("span", { className: "edrv-diffrow-o" }, "-" + r.oldLines.length) : null, r.newLines.length ? react.default.createElement("span", { className: "edrv-diffrow-n" }, "+" + r.newLines.length) : null, r.create ? react.default.createElement("span", { className: "edrv-diffrow-tag" }, "新建") : null, react.default.createElement("span", { style: { flex: 1 } }));
893
- });
894
- const stale = staleRegions.map((r) => react.default.createElement("div", {
895
- key: "stale" + callIdAttr(r.callId, r.idx),
896
- className: "edrv-diffrow edrv-diffrow-stale",
897
- title: "差异无法定位(文件可能已被手动修改)"
898
- }, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + " 无法定位"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, "冲突/需刷新"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, badgeOf(r.status))));
899
- let othersEl = null;
900
- if (otherFiles.length) othersEl = react.default.createElement("div", { className: "edrv-diffbox-others" }, react.default.createElement("span", { className: "edrv-diffrow-tag" }, "其他差异文件 (" + otherFiles.length + "):"), otherFiles.map((f) => react.default.createElement("button", {
901
- key: f.path,
902
- className: "edrv-diffrow-file",
903
- title: f.path,
904
- onClick: () => onOpenOther(f.path)
905
- }, String(f.path).split(/[\\/]/).pop() + " (" + f.pending + ")")));
906
- bodyEl = react.default.createElement("div", { className: "edrv-diffbar-body" }, ...rows, ...stale, othersEl);
907
- }
908
- const menu = menuOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
909
- style: {
910
- position: "fixed",
911
- inset: 0,
912
- zIndex: 44
913
- },
914
- onClick: () => setMenuOpen(false)
915
- }), react.default.createElement("div", { className: "edrv-diffbar-menu" }, react.default.createElement("button", {
916
- className: "edrv-diffmenu-item",
917
- disabled: !allPendingCount,
918
- onClick: () => {
919
- onAcceptAllFiles();
920
- setMenuOpen(false);
921
- }
922
- }, "✓ Keep All(全部文件采纳)"), react.default.createElement("button", {
923
- className: "edrv-diffmenu-item danger",
924
- disabled: !allPendingCount,
925
- onClick: () => {
926
- onUndoAllFiles();
927
- setMenuOpen(false);
928
- }
929
- }, "↩ Undo All(全部文件不采纳)"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
930
- className: "edrv-diffmenu-item",
931
- onClick: () => {
932
- onOpenLauncher("pending");
933
- setMenuOpen(false);
934
- }
935
- }, "🗂 差异总览"), react.default.createElement("button", {
936
- className: "edrv-diffmenu-item",
937
- onClick: () => {
938
- onOpenLauncher("archive");
939
- setMenuOpen(false);
940
- }
941
- }, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
942
- className: "edrv-diffmenu-item",
943
- disabled: !canAct,
944
- onClick: () => {
945
- onRollback();
946
- setMenuOpen(false);
947
- }
948
- }, "⟲ 回滚文件"), react.default.createElement("button", {
949
- className: "edrv-diffmenu-item",
950
- onClick: () => {
951
- onRefresh();
952
- setMenuOpen(false);
953
- }
954
- }, "⟳ 刷新"))) : null;
955
- return react.default.createElement("div", {
956
- className: "edrv-diffbar",
957
- "data-edrv-diffbox": "1"
958
- }, react.default.createElement("div", { className: "edrv-diffbar-row" }, react.default.createElement("button", {
959
- className: "edrv-pill edrv-pill-ghost",
960
- title: "上一个差异",
961
- disabled: !canAct,
962
- onClick: onPrevDiff
963
- }, "↑"), react.default.createElement("span", {
964
- className: "edrv-diffbar-count",
965
- title: "当前文件内差异位置(点击展开/收起差异列表)",
966
- onClick: () => setExpanded((v) => !v)
967
- }, diffTotal ? diffIdx + 1 + "/" + diffTotal : "0/0"), react.default.createElement("button", {
968
- className: "edrv-pill edrv-pill-ghost",
969
- title: "下一个差异",
970
- disabled: !canAct,
971
- onClick: onNextDiff
972
- }, "↓"), react.default.createElement("button", {
973
- className: "edrv-pill edrv-pill-ghost",
974
- title: "上一个差异文件",
975
- disabled: !fileTotal,
976
- onClick: onPrevFile
977
- }, "←"), react.default.createElement("span", {
978
- className: "edrv-diffbar-count edrv-count-file",
979
- title: "差异文件位置"
980
- }, fileTotal ? fileIdx + 1 + "/" + fileTotal + " 文件" : "0/0 文件"), react.default.createElement("button", {
981
- className: "edrv-pill edrv-pill-ghost",
982
- title: "下一个差异文件",
983
- disabled: !fileTotal,
984
- onClick: onNextFile
985
- }, "→"), react.default.createElement("span", {
986
- className: "edrv-diffbar-file",
987
- title: activePath || ""
988
- }, base), react.default.createElement("button", {
989
- className: "edrv-pill edrv-pill-keep",
990
- title: "采纳当前文件的全部差异",
991
- disabled: !canAct,
992
- onClick: onAcceptFile
993
- }, "✓ Keep"), react.default.createElement("button", {
994
- className: "edrv-pill edrv-pill-undo",
995
- title: "不采纳当前文件的全部差异(回滚)",
996
- disabled: !canAct,
997
- onClick: onUndoFile
998
- }, "↩ Undo"), react.default.createElement("button", {
999
- className: "edrv-pill edrv-pill-ghost",
1000
- title: "更多操作",
1001
- onClick: () => setMenuOpen((v) => !v)
1002
- }, "⋮")), bodyEl, menu);
1003
- }
1004
- //#endregion
1005
- //#region src/client/ui/DiffBarEmpty.ts
1006
- /**
1007
- * dsh-vscode-mode client — DiffBarEmpty:无文件打开 / 当前文件无差异但有差异文件时的空态浮窗。
1008
- * 迁移自原 src/client/index.ts 的 DiffBarEmpty,语义不改。
1009
- * 作者 ddj 2026-08-20
1010
- */
1011
- /**
1012
- * 空态差异条:提示当前状态 + 查看下一个差异文件 + ⋮(总览/归档/刷新)。
1013
- */
1014
- function DiffBarEmpty(props) {
1015
- const { sum, active, staleCount, onNextFile, onOpenLauncher, onRefresh } = props;
1016
- const [menuOpen, setMenuOpen] = react.default.useState(false);
1017
- const n = sum?.totalFiles ?? 0;
1018
- const tip = active ? staleCount > 0 ? "当前文件有 " + staleCount + " 处差异无法定位(可能已被后续修改影响)" : "当前文件无待处理差异" : "未打开文件";
1019
- const menu = menuOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
1020
- style: {
1021
- position: "fixed",
1022
- inset: 0,
1023
- zIndex: 44
1024
- },
1025
- onClick: () => setMenuOpen(false)
1026
- }), react.default.createElement("div", { className: "edrv-diffbar-menu" }, react.default.createElement("button", {
1027
- className: "edrv-diffmenu-item",
1028
- onClick: () => {
1029
- onOpenLauncher("pending");
1030
- setMenuOpen(false);
1031
- }
1032
- }, "🗂 差异总览"), react.default.createElement("button", {
1033
- className: "edrv-diffmenu-item",
1034
- onClick: () => {
1035
- onOpenLauncher("archive");
1036
- setMenuOpen(false);
1037
- }
1038
- }, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
1039
- className: "edrv-diffmenu-item",
1040
- onClick: () => {
1041
- onRefresh();
1042
- setMenuOpen(false);
1043
- }
1044
- }, "⟳ 刷新"))) : null;
1045
- return react.default.createElement("div", {
1046
- className: "edrv-diffbar",
1047
- "data-edrv-diffbox": "1"
1048
- }, react.default.createElement("div", { className: "edrv-diffbar-row" }, react.default.createElement("span", { className: "edrv-diffbar-count" }, "⚠ 差异 " + n + " 文件"), react.default.createElement("span", { className: "edrv-diffbar-file" }, tip), react.default.createElement("button", {
1049
- className: "edrv-pill edrv-pill-keep",
1050
- title: "打开并跳转到下一个差异文件",
1051
- onClick: onNextFile
1052
- }, "查看下一个差异文件"), react.default.createElement("button", {
1053
- className: "edrv-pill edrv-pill-ghost",
1054
- title: "更多操作",
1055
- onClick: () => setMenuOpen((v) => !v)
1056
- }, "⋮")), menu);
1057
- }
1058
- //#endregion
1059
891
  //#region src/client/ui/DiffLauncher.ts
1060
892
  /**
1061
893
  * dsh-vscode-mode client — DiffLauncher:全局差异总览 + 归档 圆角下拉。
@@ -1288,6 +1120,213 @@ window.__ModuleLoader__.load({
1288
1120
  }, "✕ 关闭")), react.default.createElement("div", { className: "edrv-launch-body" }, body));
1289
1121
  }
1290
1122
  //#endregion
1123
+ //#region src/client/sidebar/SidebarView.ts
1124
+ /**
1125
+ * dsh-vscode-mode client — 侧边栏容器(活动栏 + 面板区 + 拖拽调宽)。
1126
+ * 面板列表来自注册表(活动栏图标/徽标/激活态),渲染当前激活面板的 render(ctx);
1127
+ * 右侧手柄拖拽调宽(clamp 180–560),底部按钮收起侧边栏。
1128
+ * 作者 ddj 2026-08-26
1129
+ */
1130
+ const W_MIN = 180;
1131
+ const W_MAX = 560;
1132
+ /**
1133
+ * 侧边栏容器。
1134
+ * @param props.registry 面板注册表(list() 提供面板顺序)
1135
+ * @param props.ctx 面板共享上下文
1136
+ * @param props.activePanel 当前激活面板 id
1137
+ * @param props.onActive 激活面板变化回调
1138
+ * @param props.width 面板区宽度
1139
+ * @param props.onWidth 宽度变化回调
1140
+ * @param props.onHide 收起侧边栏回调
1141
+ */
1142
+ function SidebarView(props) {
1143
+ const panels = props.registry?.list?.() ?? [];
1144
+ const ctx = props.ctx;
1145
+ const activePanel = props.activePanel;
1146
+ const onActive = props.onActive;
1147
+ const width = props.width;
1148
+ const onWidth = props.onWidth;
1149
+ const onHide = props.onHide;
1150
+ const active = panels.find((p) => p.id === activePanel) || panels[0];
1151
+ const onPointerDown = (e) => {
1152
+ e.preventDefault();
1153
+ const startX = e.clientX;
1154
+ const startW = width;
1155
+ const onMove = (ev) => {
1156
+ const w = Math.max(W_MIN, Math.min(W_MAX, startW + (ev.clientX - startX)));
1157
+ onWidth(w);
1158
+ };
1159
+ const onUp = () => {
1160
+ window.removeEventListener("pointermove", onMove);
1161
+ window.removeEventListener("pointerup", onUp);
1162
+ };
1163
+ window.addEventListener("pointermove", onMove);
1164
+ window.addEventListener("pointerup", onUp);
1165
+ };
1166
+ const rail = react.default.createElement("div", { className: "edrv-rail" }, react.default.createElement("div", { style: {
1167
+ flex: 1,
1168
+ display: "flex",
1169
+ flexDirection: "column",
1170
+ gap: 2
1171
+ } }, panels.map((p) => {
1172
+ const count = typeof p.badge === "function" ? p.badge(ctx) : null;
1173
+ return react.default.createElement("button", {
1174
+ key: p.id,
1175
+ className: "edrv-rail-btn" + (active && p.id === active.id ? " edrv-rail-on" : ""),
1176
+ title: p.title,
1177
+ onClick: () => onActive(p.id)
1178
+ }, react.default.createElement("span", { className: "edrv-rail-icon" }, p.icon), count > 0 ? react.default.createElement("span", { className: "edrv-rail-badge" }, String(count)) : null);
1179
+ })), react.default.createElement("button", {
1180
+ className: "edrv-rail-btn",
1181
+ title: "隐藏侧边栏 (Ctrl+B)",
1182
+ onClick: onHide
1183
+ }, "◀"));
1184
+ return react.default.createElement("div", {
1185
+ className: "edrv-sidebar",
1186
+ style: { width }
1187
+ }, rail, react.default.createElement("div", { className: "edrv-side-body" }, active ? active.render(ctx) : null), react.default.createElement("div", {
1188
+ className: "edrv-side-resize",
1189
+ onPointerDown
1190
+ }));
1191
+ }
1192
+ //#endregion
1193
+ //#region src/client/diffDockStore.ts
1194
+ const values = /* @__PURE__ */ new Map();
1195
+ const sources = /* @__PURE__ */ new Map();
1196
+ const listeners = /* @__PURE__ */ new Map();
1197
+ /** 通知指定会话的 dock 订阅者。 */
1198
+ function notifyDiff(sessionId) {
1199
+ for (const listener of listeners.get(sessionId) ?? []) listener();
1200
+ }
1201
+ /**
1202
+ * 发布指定会话的最新编辑器差异上下文。
1203
+ * @author ddj 2026年08月26号
1204
+ * @param sessionId 会话 id
1205
+ * @param snapshot 完整 DiffBox 操作上下文
1206
+ * @param source 发布者令牌,用于卸载时防止误清理新实例
1207
+ */
1208
+ function publishDiffDock(sessionId, snapshot, source) {
1209
+ values.set(sessionId, snapshot);
1210
+ if (source) sources.set(sessionId, source);
1211
+ else sources.delete(sessionId);
1212
+ notifyDiff(sessionId);
1213
+ }
1214
+ /**
1215
+ * 清除指定会话的差异上下文。
1216
+ * @author ddj 2026年08月26号
1217
+ * @param sessionId 会话 id
1218
+ * @param source 可选发布者令牌;不匹配时忽略清理
1219
+ */
1220
+ function clearDiffDock(sessionId, source) {
1221
+ if (source && sources.get(sessionId) !== source) return;
1222
+ values.delete(sessionId);
1223
+ sources.delete(sessionId);
1224
+ notifyDiff(sessionId);
1225
+ }
1226
+ /**
1227
+ * 读取指定会话的最新上下文。
1228
+ * @author ddj 2026年08月26号
1229
+ * @param sessionId 会话 id
1230
+ * @returns 最新上下文或 null
1231
+ */
1232
+ function readDiffDock(sessionId) {
1233
+ if (!sessionId) return null;
1234
+ return values.get(sessionId) ?? null;
1235
+ }
1236
+ /**
1237
+ * 订阅指定会话的上下文变化。
1238
+ * @author ddj 2026年08月26号
1239
+ * @param sessionId 会话 id
1240
+ * @param listener 变化回调
1241
+ * @returns 取消订阅函数
1242
+ */
1243
+ function subscribeDiffDock(sessionId, listener) {
1244
+ let bucket = listeners.get(sessionId);
1245
+ if (!bucket) {
1246
+ bucket = /* @__PURE__ */ new Set();
1247
+ listeners.set(sessionId, bucket);
1248
+ }
1249
+ bucket.add(listener);
1250
+ return () => {
1251
+ bucket.delete(listener);
1252
+ if (!bucket.size) listeners.delete(sessionId);
1253
+ };
1254
+ }
1255
+ //#endregion
1256
+ //#region src/client/diffDock.ts
1257
+ /**
1258
+ * dsh-vscode-mode client — 对话差异 dock 纯函数。
1259
+ * 只负责差异文件轮转和文案,状态查询仍由组件复用现有 edrv.list/summarize。
1260
+ * 作者 ddj 2026-08-26
1261
+ */
1262
+ /**
1263
+ * 从稳定差异文件列表中取下一个路径,并返回下一次索引。
1264
+ * @author ddj 2026年08月26号
1265
+ * @param paths 待处理差异文件路径
1266
+ * @param index 当前轮转索引
1267
+ * @returns 下一个路径与归一化后的下一索引
1268
+ */
1269
+ function nextDiffPath(paths, index) {
1270
+ if (!paths.length) return {
1271
+ path: null,
1272
+ index: 0
1273
+ };
1274
+ const at = (Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0) % paths.length;
1275
+ return {
1276
+ path: paths[at] ?? null,
1277
+ index: (at + 1) % paths.length
1278
+ };
1279
+ }
1280
+ /**
1281
+ * 生成对话差异 dock 的单按钮文案。
1282
+ * @author ddj 2026年08月26号
1283
+ * @param count 待处理差异文件数量
1284
+ * @returns 展示文案
1285
+ */
1286
+ function diffDockText(count) {
1287
+ return "差异 " + Math.max(0, Math.floor(count)) + " 个文件 · 查看下一个";
1288
+ }
1289
+ /**
1290
+ * 根据活动文件决定编辑页 dock 形态,加载期间保持 editor 结构稳定。
1291
+ * @author ddj 2026年08月26号
1292
+ * @param activePath 当前活动文件路径
1293
+ * @returns 编辑态或无文件空态
1294
+ */
1295
+ function editorDockMode(activePath) {
1296
+ return activePath ? "editor" : "editor-empty";
1297
+ }
1298
+ /**
1299
+ * 获取切换文件期间稳定展示的文件内差异数量。
1300
+ * @author ddj 2026年08月26号
1301
+ * @param ready 当前文件内容是否已加载完成
1302
+ * @param actualTotal 内容就绪后的精确差异数
1303
+ * @param fallbackTotal 内容加载期间的文件摘要差异数
1304
+ * @returns 非负整数差异数
1305
+ */
1306
+ function displayDiffTotal(ready, actualTotal, fallbackTotal) {
1307
+ const value = ready ? actualTotal : fallbackTotal;
1308
+ return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
1309
+ }
1310
+ //#endregion
1311
+ //#region src/client/editorLayout.ts
1312
+ /**
1313
+ * dsh-vscode-mode client — 编辑区与原生 composer 的几何边界计算。
1314
+ * @author ddj 2026年08月26号
1315
+ */
1316
+ /**
1317
+ * 计算编辑根节点在当前会话滚动区内、composer 上方可使用的高度。
1318
+ * @author ddj 2026年08月26号
1319
+ * @param rootTop 编辑根节点顶边
1320
+ * @param scrollBottom 会话滚动区可视底边
1321
+ * @param composerTop composer 顶边;缺失时回退滚动区底边
1322
+ * @returns 非负可用高度
1323
+ */
1324
+ function editorHeight(rootTop, scrollBottom, composerTop) {
1325
+ const root = Number.isFinite(rootTop) ? rootTop : 0;
1326
+ const scroll = Number.isFinite(scrollBottom) ? scrollBottom : root;
1327
+ return Math.max(0, Math.min(scroll, Number.isFinite(composerTop) ? composerTop : scroll) - root);
1328
+ }
1329
+ //#endregion
1291
1330
  //#region src/client/ui/EditorView.ts
1292
1331
  /**
1293
1332
  * dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
@@ -1312,6 +1351,7 @@ window.__ModuleLoader__.load({
1312
1351
  const [active, setActive] = react.default.useState(null);
1313
1352
  const [dirtyMap, setDirtyMap] = react.default.useState({});
1314
1353
  const [content, setContent] = react.default.useState(null);
1354
+ const [contentPath, setContentPath] = react.default.useState(null);
1315
1355
  const [status, setStatus] = react.default.useState("");
1316
1356
  const [error, setError] = react.default.useState(null);
1317
1357
  const [loadError, setLoadError] = react.default.useState(null);
@@ -1328,7 +1368,12 @@ window.__ModuleLoader__.load({
1328
1368
  const [diffIdx, setDiffIdx] = react.default.useState(0);
1329
1369
  const [fileIdx, setFileIdx] = react.default.useState(0);
1330
1370
  const [tabMenu, setTabMenu] = react.default.useState(null);
1371
+ const [sidebarOn, setSidebarOn] = react.default.useState(true);
1372
+ const [sidebarW, setSidebarW] = react.default.useState(240);
1373
+ const [activePanel, setActivePanel] = react.default.useState("explorer");
1374
+ const [focusRequest, setFocusRequest] = react.default.useState(0);
1331
1375
  const editorRef = react.default.useRef(null);
1376
+ const viewRootRef = react.default.useRef(null);
1332
1377
  const monacoRef = react.default.useRef(null);
1333
1378
  const modelsRef = react.default.useRef(/* @__PURE__ */ new Map());
1334
1379
  const saveTimerRef = react.default.useRef(null);
@@ -1353,7 +1398,13 @@ window.__ModuleLoader__.load({
1353
1398
  for (const rec of Object.values(records)) if (rec.path === active && rec.archived !== true) list.push(rec);
1354
1399
  return list;
1355
1400
  }, [records, active]);
1356
- const regions = react.default.useMemo(() => diffRegions(currentRecords, content).filter((r) => !r.superseded), [currentRecords, content]);
1401
+ const contentReady = content !== null && contentPath === active;
1402
+ const regions = react.default.useMemo(() => diffRegions(currentRecords, contentReady ? content : null).filter((r) => !r.superseded), [
1403
+ currentRecords,
1404
+ content,
1405
+ contentPath,
1406
+ active
1407
+ ]);
1357
1408
  const pendingRegions = react.default.useMemo(() => regions.filter((r) => r.status === ST.PENDING && !r.stale), [regions]);
1358
1409
  const staleRegions = react.default.useMemo(() => regions.filter((r) => r.status === ST.PENDING && r.stale), [regions]);
1359
1410
  hoverRegionsRef.current = pendingRegions;
@@ -1413,6 +1464,7 @@ window.__ModuleLoader__.load({
1413
1464
  if (seq !== loadSeqRef.current || path !== active) return;
1414
1465
  if (res && res.ok) {
1415
1466
  setContent(res.content);
1467
+ setContentPath(path);
1416
1468
  setLoadStage((prev) => ({
1417
1469
  progress: Math.max(84, prev.progress),
1418
1470
  message: "文件已读取,准备创建编辑器…"
@@ -1446,9 +1498,21 @@ window.__ModuleLoader__.load({
1446
1498
  react.default.useEffect(() => {
1447
1499
  const onOpen = (e) => {
1448
1500
  const p = e?.detail?.path;
1449
- if (p) addTab(p, true);
1501
+ if (!p) return;
1502
+ if (e?.detail?.focusDiff === true) {
1503
+ pendingFocusRef.current = {
1504
+ path: p,
1505
+ region: null
1506
+ };
1507
+ setFocusRequest((value) => value + 1);
1508
+ }
1509
+ addTab(p, true);
1510
+ };
1511
+ const onShowLauncher = (event) => {
1512
+ const tab = event?.detail?.tab;
1513
+ if (tab === "pending" || tab === "archive") setLauncherTab(tab);
1514
+ setLauncherOpen(true);
1450
1515
  };
1451
- const onShowLauncher = () => setLauncherOpen(true);
1452
1516
  window.addEventListener("edrv:open-editor", onOpen);
1453
1517
  window.addEventListener("edrv:show-launcher", onShowLauncher);
1454
1518
  return () => {
@@ -1456,6 +1520,58 @@ window.__ModuleLoader__.load({
1456
1520
  window.removeEventListener("edrv:show-launcher", onShowLauncher);
1457
1521
  };
1458
1522
  }, [sessionId]);
1523
+ const dockSourceRef = react.default.useRef({});
1524
+ react.default.useEffect(() => () => clearDiffDock(sessionId, dockSourceRef.current), [sessionId]);
1525
+ react.default.useLayoutEffect(() => {
1526
+ const root = viewRootRef.current;
1527
+ const scroll = root?.closest?.("[data-conversation-scroll]");
1528
+ if (!root || !scroll) return;
1529
+ let seat = null;
1530
+ let frame = null;
1531
+ let resizeObserver = null;
1532
+ const update = () => {
1533
+ frame = null;
1534
+ const rootRect = root.getBoundingClientRect();
1535
+ const scrollRect = scroll.getBoundingClientRect();
1536
+ const seatRect = seat?.getBoundingClientRect?.();
1537
+ const height = editorHeight(rootRect.top, scrollRect.bottom, seatRect?.top);
1538
+ root.style.setProperty("--edrv-editor-height", height + "px");
1539
+ };
1540
+ const scheduleUpdate = () => {
1541
+ if (frame !== null) return;
1542
+ frame = requestAnimationFrame(update);
1543
+ };
1544
+ const attachSeat = () => {
1545
+ const next = scroll.querySelector?.("[data-composer-seat]");
1546
+ if (next === seat) {
1547
+ scheduleUpdate();
1548
+ return;
1549
+ }
1550
+ resizeObserver?.disconnect();
1551
+ seat = next;
1552
+ resizeObserver = null;
1553
+ if (seat && typeof ResizeObserver === "function") {
1554
+ resizeObserver = new ResizeObserver(scheduleUpdate);
1555
+ resizeObserver.observe(seat);
1556
+ }
1557
+ scheduleUpdate();
1558
+ };
1559
+ attachSeat();
1560
+ const mutationObserver = typeof MutationObserver === "function" ? new MutationObserver(attachSeat) : null;
1561
+ mutationObserver?.observe(scroll, { childList: true });
1562
+ const scrollObserver = typeof ResizeObserver === "function" ? new ResizeObserver(scheduleUpdate) : null;
1563
+ scrollObserver?.observe(scroll);
1564
+ window.addEventListener("resize", scheduleUpdate);
1565
+ scheduleUpdate();
1566
+ return () => {
1567
+ if (frame !== null) cancelAnimationFrame(frame);
1568
+ resizeObserver?.disconnect();
1569
+ scrollObserver?.disconnect();
1570
+ mutationObserver?.disconnect();
1571
+ window.removeEventListener("resize", scheduleUpdate);
1572
+ root.style.removeProperty("--edrv-editor-height");
1573
+ };
1574
+ }, [sessionId]);
1459
1575
  react.default.useEffect(() => {
1460
1576
  if (bootRef.current || !sessionId) return;
1461
1577
  bootRef.current = true;
@@ -1483,9 +1599,53 @@ window.__ModuleLoader__.load({
1483
1599
  active,
1484
1600
  sessionId
1485
1601
  ]);
1602
+ react.default.useEffect(() => {
1603
+ if (!sessionId) return;
1604
+ try {
1605
+ const raw = localStorage.getItem("edrv.sidebar." + String(sessionId));
1606
+ if (raw) {
1607
+ const saved = JSON.parse(raw);
1608
+ if (typeof saved.on === "boolean") setSidebarOn(saved.on);
1609
+ if (typeof saved.width === "number") setSidebarW(Math.max(180, Math.min(560, saved.width)));
1610
+ if (typeof saved.panel === "string") setActivePanel(saved.panel);
1611
+ }
1612
+ } catch (e) {}
1613
+ }, [sessionId]);
1614
+ react.default.useEffect(() => {
1615
+ if (!sessionId) return;
1616
+ try {
1617
+ localStorage.setItem("edrv.sidebar." + String(sessionId), JSON.stringify({
1618
+ on: sidebarOn,
1619
+ width: sidebarW,
1620
+ panel: activePanel
1621
+ }));
1622
+ } catch (e) {}
1623
+ }, [
1624
+ sidebarOn,
1625
+ sidebarW,
1626
+ activePanel,
1627
+ sessionId
1628
+ ]);
1629
+ react.default.useEffect(() => {
1630
+ const onKey = (e) => {
1631
+ if ((e.ctrlKey || e.metaKey) && String(e.key).toLowerCase() === "b") {
1632
+ e.preventDefault();
1633
+ e.stopPropagation();
1634
+ setSidebarOn((v) => !v);
1635
+ }
1636
+ };
1637
+ window.addEventListener("keydown", onKey, true);
1638
+ return () => window.removeEventListener("keydown", onKey, true);
1639
+ }, []);
1640
+ const pendingByPath = react.default.useMemo(() => {
1641
+ const out = {};
1642
+ for (const f of sum.files) if (f.pending > 0) out[f.path] = f.pending;
1643
+ return out;
1644
+ }, [sum]);
1486
1645
  react.default.useEffect(() => {
1487
1646
  if (!active) return;
1488
1647
  setContent(null);
1648
+ setContentPath(null);
1489
1649
  setLoadError(null);
1490
1650
  setLoadStage({
1491
1651
  progress: 10,
@@ -1558,6 +1718,7 @@ window.__ModuleLoader__.load({
1558
1718
  if (res && res.ok) {
1559
1719
  setStatus("已保存 " + (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8));
1560
1720
  setContent(text);
1721
+ setContentPath(active);
1561
1722
  setDirtyMap((d) => Object.assign({}, d, { [active]: false }));
1562
1723
  refreshRecords();
1563
1724
  emitRefresh();
@@ -1764,7 +1925,8 @@ window.__ModuleLoader__.load({
1764
1925
  }, [
1765
1926
  active,
1766
1927
  content,
1767
- pendingRegions
1928
+ pendingRegions,
1929
+ focusRequest
1768
1930
  ]);
1769
1931
  const jumpTo = (region) => {
1770
1932
  if (editorRef.current) {
@@ -1801,6 +1963,11 @@ window.__ModuleLoader__.load({
1801
1963
  setFileIdx(next);
1802
1964
  openFile(sum.pendingFiles[next].path, true);
1803
1965
  };
1966
+ const openNextFile = () => {
1967
+ if (!sum.pendingFiles.length) return;
1968
+ if (sum.pendingFiles.some((file) => file.path === active)) gotoFile(1);
1969
+ else openFile(sum.pendingFiles[0].path, true);
1970
+ };
1804
1971
  const reloadFile = (skipStale) => {
1805
1972
  if (!active) return;
1806
1973
  loadContent(active, sessionId);
@@ -2085,25 +2252,28 @@ window.__ModuleLoader__.load({
2085
2252
  const pathBar = react.default.createElement("div", {
2086
2253
  className: "edrv-pathbar",
2087
2254
  title: active || ""
2088
- }, react.default.createElement("span", { className: "edrv-pb-name" }, active ? String(active).split(/[\\/]/).pop() : "未打开文件"), react.default.createElement("span", { className: "edrv-pb-full" }, active || "使用右上搜索框 (Ctrl+P) 打开文件"));
2255
+ }, react.default.createElement("span", { className: "edrv-pb-name" }, active ? String(active).split(/[\\/]/).pop() : "未打开文件"), react.default.createElement("span", { className: "edrv-pb-full" }, active || "使用右上搜索框 (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);
2089
2256
  const tabRow = react.default.createElement("div", { style: {
2090
2257
  display: "flex",
2091
2258
  alignItems: "center",
2092
2259
  background: "var(--dsw-alias-bg-layer-1,transparent)",
2093
2260
  flexShrink: 0
2094
- } }, tabsEl, react.default.createElement(QuickOpen, {
2095
- sessionId,
2096
- onOpen: (p) => openFile(p, false)
2097
- }));
2098
- const statusBar = react.default.createElement("div", { className: "edrv-statusbar" }, react.default.createElement("span", null, "编辑"), react.default.createElement("span", null, active ? langOf(active) : ""), react.default.createElement("span", null, cursor), status ? react.default.createElement("span", null, status) : null, react.default.createElement("span", { style: { flex: 1 } }), sum.totalFiles > 0 ? react.default.createElement("button", {
2261
+ } }, tabsEl, sum.totalFiles > 0 ? react.default.createElement("button", {
2099
2262
  className: "edrv-diffchip",
2100
2263
  title: "打开差异总览/归档",
2101
2264
  onClick: () => setLauncherOpen((o) => !o)
2102
2265
  }, "⚠ 差异 " + sum.totalFiles + " 文件") : null, react.default.createElement("button", {
2266
+ className: "edrv-chip-btn" + (sidebarOn ? " edrv-chip-on" : ""),
2267
+ title: "切换侧边栏 (Ctrl+B)",
2268
+ onClick: () => setSidebarOn((v) => !v)
2269
+ }, "☰"), react.default.createElement("button", {
2103
2270
  className: "edrv-chip-btn",
2104
2271
  title: "刷新",
2105
2272
  onClick: reloadFile
2106
- }, "⟳"));
2273
+ }, "⟳"), react.default.createElement(QuickOpen, {
2274
+ sessionId,
2275
+ onOpen: (p) => openFile(p, false)
2276
+ }));
2107
2277
  const otherFiles = sum.pendingFiles.filter((f) => f.path !== active);
2108
2278
  /**
2109
2279
  * 渲染编辑器/文件加载进度面板。
@@ -2146,6 +2316,7 @@ window.__ModuleLoader__.load({
2146
2316
  else if (content === null && loadError) body = loadingBody("文件加载失败:" + loadError, 0, () => {
2147
2317
  setLoadError(null);
2148
2318
  setContent(null);
2319
+ setContentPath(null);
2149
2320
  setLoadStage({
2150
2321
  progress: 10,
2151
2322
  message: "重新读取文件…"
@@ -2198,6 +2369,40 @@ window.__ModuleLoader__.load({
2198
2369
  actHunk(hoverAct.region, true);
2199
2370
  }
2200
2371
  }, "↩ Undo")) : null;
2372
+ react.default.useEffect(() => {
2373
+ if (!sessionId) return;
2374
+ const mode = editorDockMode(active);
2375
+ publishDiffDock(sessionId, {
2376
+ mode,
2377
+ pendingRegions,
2378
+ staleRegions,
2379
+ onAct: actHunk,
2380
+ onAcceptFile: acceptFile,
2381
+ onUndoFile: undoFile,
2382
+ onAcceptAllFiles: acceptAllFiles,
2383
+ onUndoAllFiles: undoAllFiles,
2384
+ allPendingCount: allPending.length,
2385
+ onRollback: rollbackFile,
2386
+ onJump: jumpTo,
2387
+ otherFiles,
2388
+ onOpenOther: (path) => openFile(path, true),
2389
+ onOpenLauncher: (tab) => {
2390
+ setLauncherTab(tab || "pending");
2391
+ setLauncherOpen(true);
2392
+ },
2393
+ onRefresh: reloadFile,
2394
+ activePath: active,
2395
+ diffIdx: contentReady ? diffIdx : 0,
2396
+ diffTotal: displayDiffTotal(contentReady, pendingRegions.length, sum.files.find((file) => file.path === active)?.pending ?? 0),
2397
+ fileIdx,
2398
+ fileTotal: sum.pendingFiles.length,
2399
+ onPrevDiff: () => gotoDiff(-1),
2400
+ onNextDiff: () => gotoDiff(1),
2401
+ onPrevFile: () => gotoFile(-1),
2402
+ onNextFile: () => gotoFile(1),
2403
+ onOpenNextFile: openNextFile
2404
+ }, dockSourceRef.current);
2405
+ });
2201
2406
  const overlay = launcherOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
2202
2407
  style: {
2203
2408
  position: "fixed",
@@ -2214,46 +2419,7 @@ window.__ModuleLoader__.load({
2214
2419
  openFile(p, true);
2215
2420
  setLauncherOpen(false);
2216
2421
  }
2217
- })) : pendingRegions.length > 0 ? react.default.createElement(DiffBox, {
2218
- pendingRegions,
2219
- staleRegions,
2220
- onAct: actHunk,
2221
- onAcceptFile: acceptFile,
2222
- onUndoFile: undoFile,
2223
- onAcceptAllFiles: acceptAllFiles,
2224
- onUndoAllFiles: undoAllFiles,
2225
- allPendingCount: allPending.length,
2226
- onRollback: rollbackFile,
2227
- onJump: jumpTo,
2228
- otherFiles,
2229
- onOpenOther: (p) => openFile(p, true),
2230
- onOpenLauncher: (tab) => {
2231
- setLauncherTab(tab || "pending");
2232
- setLauncherOpen(true);
2233
- },
2234
- onRefresh: reloadFile,
2235
- activePath: active,
2236
- diffIdx,
2237
- diffTotal: pendingRegions.length,
2238
- fileIdx,
2239
- fileTotal: sum.pendingFiles.length,
2240
- onPrevDiff: () => gotoDiff(-1),
2241
- onNextDiff: () => gotoDiff(1),
2242
- onPrevFile: () => gotoFile(-1),
2243
- onNextFile: () => gotoFile(1)
2244
- }) : sum.pendingFiles.length > 0 ? react.default.createElement(DiffBarEmpty, {
2245
- sum,
2246
- active,
2247
- staleCount: staleRegions.length,
2248
- onNextFile: () => {
2249
- if (sum.pendingFiles.length) openFile(sum.pendingFiles[0].path, true);
2250
- },
2251
- onOpenLauncher: (tab) => {
2252
- setLauncherTab(tab || "pending");
2253
- setLauncherOpen(true);
2254
- },
2255
- onRefresh: reloadFile
2256
- }) : null;
2422
+ })) : null;
2257
2423
  /**
2258
2424
  * 计算右键菜单固定定位(clamp 防越出视口)。
2259
2425
  * @author ddj 2026年08月25号
@@ -2290,23 +2456,54 @@ window.__ModuleLoader__.load({
2290
2456
  dismissMenus();
2291
2457
  }
2292
2458
  }, "关闭标签页")) : null;
2459
+ const sidebarPanels = props.sidebarPanels;
2460
+ const sidebarCtx = {
2461
+ sessionId,
2462
+ openFile: (p) => openFile(p, false),
2463
+ activePath: active,
2464
+ pendingByPath,
2465
+ sum: {
2466
+ totalFiles: sum.totalFiles,
2467
+ files: sum.files
2468
+ },
2469
+ refreshRecords: () => refreshRecords(),
2470
+ editor: () => editorRef.current,
2471
+ outlineSources: props.outlineSources
2472
+ };
2473
+ const editorArea = react.default.createElement("div", { className: "edrv-editor-area" }, body, hoverEl, overlay);
2474
+ const mainCol = react.default.createElement("div", {
2475
+ className: "edrv-main-col",
2476
+ style: {
2477
+ flex: 1,
2478
+ minWidth: 0,
2479
+ minHeight: 0,
2480
+ display: "flex",
2481
+ flexDirection: "column",
2482
+ overflow: "hidden"
2483
+ }
2484
+ }, pathBar, tabRow, editorArea);
2485
+ const editorRow = react.default.createElement("div", { className: "edrv-editor-row" }, sidebarOn && sidebarPanels ? react.default.createElement(SidebarView, {
2486
+ registry: sidebarPanels,
2487
+ ctx: sidebarCtx,
2488
+ activePanel,
2489
+ onActive: setActivePanel,
2490
+ width: sidebarW,
2491
+ onWidth: setSidebarW,
2492
+ onHide: () => setSidebarOn(false)
2493
+ }) : null, mainCol);
2293
2494
  return react.default.createElement("div", {
2495
+ ref: viewRootRef,
2294
2496
  "data-edrv-view": "1",
2295
2497
  style: {
2296
- height: "100%",
2498
+ height: "var(--edrv-editor-height, 100%)",
2499
+ maxHeight: "var(--edrv-editor-height, 100%)",
2297
2500
  minHeight: 0,
2298
2501
  display: "flex",
2299
2502
  flexDirection: "column",
2300
2503
  background: "var(--dsw-alias-bg-base,transparent)",
2301
2504
  overflow: "hidden"
2302
2505
  }
2303
- }, pathBar, tabRow, react.default.createElement("div", { style: {
2304
- position: "relative",
2305
- flex: 1,
2306
- minHeight: 0,
2307
- display: "flex",
2308
- flexDirection: "column"
2309
- } }, body, hoverEl, overlay), menuBackdrop, tabMenuEl, statusBar);
2506
+ }, editorRow, menuBackdrop, tabMenuEl);
2310
2507
  }
2311
2508
  //#endregion
2312
2509
  //#region src/client/ui/DiffBadge.ts
@@ -2389,6 +2586,230 @@ window.__ModuleLoader__.load({
2389
2586
  } }, String(n)));
2390
2587
  }
2391
2588
  //#endregion
2589
+ //#region src/client/ui/DiffBox.ts
2590
+ /**
2591
+ * dsh-vscode-mode client — DiffBox:每个文件底部紧凑差异条(per-file hunk + ⋮ 二级菜单)。
2592
+ * 迁移自原 src/client/index.ts 的 DiffBox,语义不改。
2593
+ * 作者 ddj 2026-08-20
2594
+ */
2595
+ /**
2596
+ * 折叠控制使用任务栏同尺寸的 chevron 轮廓,避免依赖未声明的运行时图标包。
2597
+ * @author ddj 2026年08月25号
2598
+ * @param open 是否已展开
2599
+ * @returns 14×14 的装饰性 chevron SVG
2600
+ */
2601
+ function chevronOf(open) {
2602
+ const path = open ? "M3.25 5.25 7 9l3.75-3.75" : "M3.25 8.75 7 5l3.75 3.75";
2603
+ return react.default.createElement("svg", {
2604
+ width: 14,
2605
+ height: 14,
2606
+ viewBox: "0 0 14 14",
2607
+ fill: "none",
2608
+ "aria-hidden": true,
2609
+ focusable: false
2610
+ }, react.default.createElement("path", {
2611
+ d: path,
2612
+ stroke: "currentColor",
2613
+ strokeWidth: 1.2,
2614
+ strokeLinecap: "round",
2615
+ strokeLinejoin: "round"
2616
+ }));
2617
+ }
2618
+ /**
2619
+ * 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
2620
+ * (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
2621
+ */
2622
+ function DiffBox(props) {
2623
+ const { pendingRegions = [], staleRegions = [], onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles, allPendingCount = 0, onRollback, onJump, otherFiles = [], onOpenOther, onOpenLauncher, onRefresh, activePath, diffIdx = 0, diffTotal = 0, fileIdx = 0, fileTotal = 0, onPrevDiff, onNextDiff, onPrevFile, onNextFile, mode = "editor", onOpenNextFile, dock = false } = props;
2624
+ const [expanded, setExpanded] = react.default.useState(false);
2625
+ const [detailsOpen, setDetailsOpen] = react.default.useState(false);
2626
+ const detailsId = react.default.useId();
2627
+ const canAct = pendingRegions.length > 0;
2628
+ const base = String(activePath || "").split(/[\\/]/).pop() || "";
2629
+ if (mode === "chat" || mode === "editor-empty") return react.default.createElement("div", {
2630
+ className: "edrv-diffbar edrv-diffbar-dock",
2631
+ "data-edrv-diffbox": "1",
2632
+ "data-edrv-diff-mode": mode
2633
+ }, react.default.createElement("div", { className: "edrv-diffbar-row edrv-diffbar-dock-row" }, react.default.createElement("button", {
2634
+ type: "button",
2635
+ className: "edrv-diffbar-dock-button",
2636
+ title: "切换到下一个差异文件",
2637
+ "aria-label": diffDockText(fileTotal),
2638
+ onClick: onOpenNextFile
2639
+ }, diffDockText(fileTotal))));
2640
+ let bodyEl = null;
2641
+ if (expanded) {
2642
+ const rows = pendingRegions.map((r) => {
2643
+ const key = callIdAttr(r.callId, r.idx);
2644
+ return react.default.createElement("div", {
2645
+ key,
2646
+ className: "edrv-diffrow",
2647
+ "data-edrv-hunk": key,
2648
+ title: "跳转到 L" + (r.start ?? "?"),
2649
+ onClick: () => onJump(r)
2650
+ }, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + "-" + (r.end ?? "?")), r.oldLines.length ? react.default.createElement("span", { className: "edrv-diffrow-o" }, "-" + r.oldLines.length) : null, r.newLines.length ? react.default.createElement("span", { className: "edrv-diffrow-n" }, "+" + r.newLines.length) : null, r.create ? react.default.createElement("span", { className: "edrv-diffrow-tag" }, "新建") : null, react.default.createElement("span", { style: { flex: 1 } }));
2651
+ });
2652
+ const stale = staleRegions.map((r) => react.default.createElement("div", {
2653
+ key: "stale" + callIdAttr(r.callId, r.idx),
2654
+ className: "edrv-diffrow edrv-diffrow-stale",
2655
+ title: "差异无法定位(文件可能已被手动修改)"
2656
+ }, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + " 无法定位"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, "冲突/需刷新"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, badgeOf(r.status))));
2657
+ let othersEl = null;
2658
+ if (otherFiles.length) othersEl = react.default.createElement("div", { className: "edrv-diffbox-others" }, react.default.createElement("span", { className: "edrv-diffrow-tag" }, "其他差异文件 (" + otherFiles.length + "):"), otherFiles.map((f) => react.default.createElement("button", {
2659
+ key: f.path,
2660
+ className: "edrv-diffrow-file",
2661
+ title: f.path,
2662
+ onClick: () => onOpenOther(f.path)
2663
+ }, String(f.path).split(/[\\/]/).pop() + " (" + f.pending + ")")));
2664
+ bodyEl = react.default.createElement("div", { className: "edrv-diffbar-body" }, ...rows, ...stale, othersEl);
2665
+ }
2666
+ const details = detailsOpen ? react.default.createElement("div", {
2667
+ className: "edrv-diff-details",
2668
+ id: detailsId
2669
+ }, react.default.createElement("button", {
2670
+ className: "edrv-diffmenu-item",
2671
+ disabled: !allPendingCount,
2672
+ onClick: onAcceptAllFiles
2673
+ }, "✓ Keep All(全部文件采纳)"), react.default.createElement("button", {
2674
+ className: "edrv-diffmenu-item danger",
2675
+ disabled: !allPendingCount,
2676
+ onClick: onUndoAllFiles
2677
+ }, "↩ Undo All(全部文件不采纳)"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
2678
+ className: "edrv-diffmenu-item",
2679
+ onClick: () => onOpenLauncher("pending")
2680
+ }, "🗂 差异总览"), react.default.createElement("button", {
2681
+ className: "edrv-diffmenu-item",
2682
+ onClick: () => onOpenLauncher("archive")
2683
+ }, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
2684
+ className: "edrv-diffmenu-item",
2685
+ disabled: !canAct,
2686
+ onClick: onRollback
2687
+ }, "⟲ 回滚文件"), react.default.createElement("button", {
2688
+ className: "edrv-diffmenu-item",
2689
+ onClick: onRefresh
2690
+ }, "⟳ 刷新")) : null;
2691
+ return react.default.createElement("div", {
2692
+ className: "edrv-diffbar edrv-diffbar-dock",
2693
+ "data-edrv-diffbox": "1",
2694
+ "data-edrv-diff-mode": "editor"
2695
+ }, react.default.createElement("div", { className: "edrv-diffbar-row edrv-diffbar-editor-row" }, react.default.createElement("div", { className: "edrv-diffbar-editor-content" }, react.default.createElement("div", { className: "edrv-diffbar-nav" }, react.default.createElement("button", {
2696
+ className: "edrv-pill edrv-pill-ghost",
2697
+ title: "上一个差异",
2698
+ disabled: !canAct,
2699
+ onClick: onPrevDiff
2700
+ }, "↑"), react.default.createElement("span", {
2701
+ className: "edrv-diffbar-count edrv-diffbar-progress",
2702
+ title: "当前文件内差异位置(点击展开/收起差异列表)",
2703
+ onClick: () => setExpanded((v) => !v)
2704
+ }, diffTotal ? diffIdx + 1 + "/" + diffTotal : "0/0"), react.default.createElement("button", {
2705
+ className: "edrv-pill edrv-pill-ghost",
2706
+ title: "下一个差异",
2707
+ disabled: !canAct,
2708
+ onClick: onNextDiff
2709
+ }, "↓")), react.default.createElement("div", { className: "edrv-diffbar-files" }, react.default.createElement("button", {
2710
+ className: "edrv-pill edrv-pill-ghost",
2711
+ title: "上一个差异文件",
2712
+ disabled: !fileTotal,
2713
+ onClick: onPrevFile
2714
+ }, "←"), react.default.createElement("span", {
2715
+ className: "edrv-diffbar-count edrv-count-file edrv-diffbar-progress",
2716
+ title: "差异文件位置"
2717
+ }, fileTotal ? fileIdx + 1 + "/" + fileTotal + " 文件" : "0/0 文件"), react.default.createElement("button", {
2718
+ className: "edrv-pill edrv-pill-ghost",
2719
+ title: "下一个差异文件",
2720
+ disabled: !fileTotal,
2721
+ onClick: onNextFile
2722
+ }, "→"), react.default.createElement("span", {
2723
+ className: "edrv-diffbar-file edrv-diffbar-summary",
2724
+ title: activePath || ""
2725
+ }, base)), react.default.createElement("div", { className: "edrv-diffbar-actions" }, react.default.createElement("button", {
2726
+ className: "edrv-pill edrv-pill-keep",
2727
+ title: "采纳当前文件的全部差异",
2728
+ disabled: !canAct,
2729
+ onClick: onAcceptFile
2730
+ }, "✓ Keep"), react.default.createElement("button", {
2731
+ className: "edrv-pill edrv-pill-undo",
2732
+ title: "不采纳当前文件的全部差异(回滚)",
2733
+ disabled: !canAct,
2734
+ onClick: onUndoFile
2735
+ }, "↩ Undo"), react.default.createElement("button", {
2736
+ className: "edrv-pill edrv-pill-ghost edrv-diff-details-toggle",
2737
+ title: detailsOpen ? "收起差异操作" : "展开差异操作",
2738
+ "aria-label": detailsOpen ? "收起差异操作" : "展开差异操作",
2739
+ "aria-expanded": detailsOpen,
2740
+ "aria-controls": detailsId,
2741
+ onClick: () => setDetailsOpen((value) => !value)
2742
+ }, chevronOf(detailsOpen))))), bodyEl, details);
2743
+ }
2744
+ //#endregion
2745
+ //#region src/client/ui/ConversationDiffDock.ts
2746
+ /**
2747
+ * dsh-vscode-mode client — 会话级差异 dock。
2748
+ * conversation.input.dock 是唯一实例:普通对话显示摘要,文件编辑页显示完整操作条。
2749
+ * 作者 ddj 2026-08-26
2750
+ */
2751
+ const nextIndexBySession = /* @__PURE__ */ new Map();
2752
+ /**
2753
+ * 对话输入框上方的唯一差异 dock。
2754
+ * @author ddj 2026年08月26号
2755
+ * @param props DSH 注入的会话属性
2756
+ * @returns 差异 dock React 元素或 null
2757
+ */
2758
+ function ConversationDiffDock(props) {
2759
+ const sessionId = props?.sessionId;
2760
+ const [summary, setSummary] = react.default.useState(null);
2761
+ const [, setStoreRevision] = react.default.useState(0);
2762
+ const seq = react.default.useRef(0);
2763
+ const load = react.default.useCallback(() => {
2764
+ if (!sessionId) return;
2765
+ const current = ++seq.current;
2766
+ rpc("edrv.list", { sessionId }).then((res) => {
2767
+ if (current !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return;
2768
+ setSummary(summarize(res.records));
2769
+ }).catch(() => {});
2770
+ }, [sessionId]);
2771
+ react.default.useEffect(() => {
2772
+ load();
2773
+ const timer = setInterval(load, 5e3);
2774
+ const onRefresh = () => load();
2775
+ window.addEventListener("edrv:refresh", onRefresh);
2776
+ return () => {
2777
+ clearInterval(timer);
2778
+ window.removeEventListener("edrv:refresh", onRefresh);
2779
+ };
2780
+ }, [load]);
2781
+ react.default.useEffect(() => {
2782
+ if (!sessionId) return void 0;
2783
+ const sync = () => setStoreRevision((value) => value + 1);
2784
+ sync();
2785
+ return subscribeDiffDock(sessionId, sync);
2786
+ }, [sessionId]);
2787
+ const renderDock = (content, key) => react.default.createElement("div", {
2788
+ key,
2789
+ className: "edrv-diff-dock-shell",
2790
+ "data-edrv-diff-dock-shell": "1"
2791
+ }, content);
2792
+ const editorSnapshot = readDiffDock(sessionId);
2793
+ if (editorSnapshot) {
2794
+ if (editorSnapshot.mode === "editor-empty" && !editorSnapshot.fileTotal) return null;
2795
+ return renderDock(react.default.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId);
2796
+ }
2797
+ if (!sessionId || !summary?.pendingFiles?.length) return null;
2798
+ const paths = summary.pendingFiles.map((file) => file.path);
2799
+ const click = () => {
2800
+ const current = nextIndexBySession.get(sessionId) ?? 0;
2801
+ const next = nextDiffPath(paths, current);
2802
+ nextIndexBySession.set(sessionId, next.index);
2803
+ if (next.path) openDiffView(next.path);
2804
+ };
2805
+ return renderDock(react.default.createElement(DiffBox, {
2806
+ mode: "chat",
2807
+ dock: true,
2808
+ fileTotal: paths.length,
2809
+ onOpenNextFile: click
2810
+ }), sessionId || "chat");
2811
+ }
2812
+ //#endregion
2392
2813
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
2393
2814
  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";
2394
2815
  const tagId = "dsh-vscode-mode/mcp.css";
@@ -3250,10 +3671,939 @@ window.__ModuleLoader__.load({
3250
3671
  return { appendReference };
3251
3672
  }
3252
3673
  //#endregion
3674
+ //#region src/client/sidebar/registry.ts
3675
+ /** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
3676
+ function panelRegister(entries, notify, panel) {
3677
+ if (!panel.id || typeof panel.render !== "function") throw new TypeError("侧边栏面板必须提供 id 和 render");
3678
+ entries.set(panel.id, panel);
3679
+ notify();
3680
+ }
3681
+ /**
3682
+ * 创建生命周期独立的面板注册表。
3683
+ * @author ddj 2026年08月26号
3684
+ * @returns 面板注册表
3685
+ */
3686
+ function createSidebarPanelRegistry() {
3687
+ const entries = /* @__PURE__ */ new Map();
3688
+ const listeners = /* @__PURE__ */ new Set();
3689
+ const notify = () => {
3690
+ for (const listener of listeners) listener();
3691
+ };
3692
+ const list = () => [...entries.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
3693
+ return {
3694
+ register(panel) {
3695
+ panelRegister(entries, notify, panel);
3696
+ return () => {
3697
+ if (entries.get(panel.id) !== panel) return;
3698
+ entries.delete(panel.id);
3699
+ notify();
3700
+ };
3701
+ },
3702
+ list,
3703
+ subscribe(listener) {
3704
+ listeners.add(listener);
3705
+ return () => listeners.delete(listener);
3706
+ },
3707
+ get: (id) => entries.get(id)
3708
+ };
3709
+ }
3710
+ //#endregion
3711
+ //#region src/client/sidebar/panels/FileExplorer.ts
3712
+ /**
3713
+ * dsh-vscode-mode client — 侧边栏「文件管理」面板(懒加载目录树)。
3714
+ * 点目录展开/收起(首次经 edrv.listDir 拉取并缓存),点文件经 ctx.openFile 打开;
3715
+ * 差异角标取 pendingByPath,活动文件高亮;edrv:refresh 事件与手动刷新重载树。
3716
+ * 作者 ddj 2026-08-26
3717
+ */
3718
+ const DIR_CAP = 4e3;
3719
+ /**
3720
+ * 目录树面板主体。
3721
+ * @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath)
3722
+ */
3723
+ function FileExplorer(props) {
3724
+ const ctx = props?.ctx;
3725
+ const sessionId = ctx?.sessionId;
3726
+ const openFile = ctx?.openFile;
3727
+ const activePath = ctx?.activePath ?? null;
3728
+ const pendingByPath = ctx?.pendingByPath ?? {};
3729
+ const [root, setRoot] = react.default.useState(null);
3730
+ const [dirs, setDirs] = react.default.useState({});
3731
+ const [expanded, setExpanded] = react.default.useState({});
3732
+ const [loading, setLoading] = react.default.useState({});
3733
+ const [error, setError] = react.default.useState(null);
3734
+ const tokensRef = react.default.useRef({});
3735
+ const expandedRef = react.default.useRef({});
3736
+ expandedRef.current = expanded;
3737
+ const loadDir = (rel) => {
3738
+ const token = (tokensRef.current[rel] || 0) + 1;
3739
+ tokensRef.current[rel] = token;
3740
+ setLoading((prev) => Object.assign({}, prev, { [rel]: true }));
3741
+ setError(null);
3742
+ rpc("edrv.listDir", {
3743
+ sessionId,
3744
+ path: rel
3745
+ }).then((res) => {
3746
+ if (tokensRef.current[rel] !== token) return;
3747
+ if (res && res.ok && Array.isArray(res.entries)) {
3748
+ setDirs((prev) => Object.assign({}, prev, { [rel]: res.entries }));
3749
+ if (res.root && !root) setRoot(res.root);
3750
+ } else setError(res?.error ? String(res.error) : "读取目录失败");
3751
+ }).catch((e) => {
3752
+ if (tokensRef.current[rel] !== token) return;
3753
+ setError("读取目录异常:" + String(e));
3754
+ }).finally(() => {
3755
+ if (tokensRef.current[rel] !== token) return;
3756
+ setLoading((prev) => {
3757
+ const next = Object.assign({}, prev);
3758
+ delete next[rel];
3759
+ return next;
3760
+ });
3761
+ });
3762
+ };
3763
+ const toggle = (rel) => {
3764
+ if (expanded[rel] === true) {
3765
+ setExpanded((prev) => Object.assign({}, prev, { [rel]: false }));
3766
+ return;
3767
+ }
3768
+ if (!dirs[rel]) loadDir(rel);
3769
+ setExpanded((prev) => Object.assign({}, prev, { [rel]: true }));
3770
+ };
3771
+ const refresh = () => {
3772
+ const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
3773
+ tokensRef.current = {};
3774
+ setDirs({});
3775
+ setLoading({});
3776
+ setError(null);
3777
+ if (keep.length) {
3778
+ const next = {};
3779
+ for (const rel of keep) next[rel] = true;
3780
+ setExpanded(next);
3781
+ for (const rel of keep) loadDir(rel);
3782
+ } else setExpanded({});
3783
+ loadDir("");
3784
+ };
3785
+ react.default.useEffect(() => {
3786
+ tokensRef.current = {};
3787
+ setDirs({});
3788
+ setExpanded({});
3789
+ setLoading({});
3790
+ setError(null);
3791
+ setRoot(null);
3792
+ loadDir("");
3793
+ }, [sessionId]);
3794
+ react.default.useEffect(() => {
3795
+ const onRefresh = () => refresh();
3796
+ window.addEventListener("edrv:refresh", onRefresh);
3797
+ return () => window.removeEventListener("edrv:refresh", onRefresh);
3798
+ }, []);
3799
+ const rootName = root ? String(root).split(/[\\/]/).pop() || root : "";
3800
+ const rowEl = (e, depth, isDir, isOpen) => {
3801
+ const pending = isDir ? 0 : pendingByPath[e.path] ?? 0;
3802
+ const active = !isDir && e.path === activePath;
3803
+ const dim = e.type === "other";
3804
+ return react.default.createElement("div", {
3805
+ key: e.path,
3806
+ className: "edrv-tree-row" + (active ? " edrv-tree-active" : ""),
3807
+ title: e.path,
3808
+ style: { paddingLeft: 6 + depth * 14 },
3809
+ onClick: () => {
3810
+ if (isDir) toggle(e.path);
3811
+ else openFile(e.path);
3812
+ }
3813
+ }, 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);
3814
+ };
3815
+ const rowsOf = (rel, depth) => {
3816
+ const entries = dirs[rel];
3817
+ if (!entries) return [react.default.createElement("div", {
3818
+ key: rel + ":loading",
3819
+ className: "edrv-tree-loading",
3820
+ style: { paddingLeft: 6 + depth * 14 }
3821
+ }, loading[rel] ? "加载中…" : "无法加载")];
3822
+ const rows = [];
3823
+ const cap = entries.length;
3824
+ for (let i = 0; i < cap; i++) {
3825
+ const e = entries[i];
3826
+ const isDir = e.type === "directory";
3827
+ const isOpen = expanded[e.path] === true;
3828
+ rows.push(rowEl(e, depth, isDir, isOpen));
3829
+ if (isDir && isOpen) rows.push(...rowsOf(e.path, depth + 1));
3830
+ }
3831
+ if (cap >= DIR_CAP) rows.push(react.default.createElement("div", {
3832
+ key: rel + ":cap",
3833
+ className: "edrv-tree-loading",
3834
+ style: { paddingLeft: 6 + depth * 14 }
3835
+ }, "目录条目过多,仅显示前 4000 项"));
3836
+ return rows;
3837
+ };
3838
+ return react.default.createElement("div", { className: "edrv-side-panel" }, react.default.createElement("div", { className: "edrv-side-head" }, react.default.createElement("span", { className: "edrv-side-title" }, "资源管理器"), react.default.createElement("span", {
3839
+ className: "edrv-side-root",
3840
+ title: root || ""
3841
+ }, rootName), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("button", {
3842
+ className: "edrv-side-btn",
3843
+ title: "刷新目录树",
3844
+ onClick: refresh
3845
+ }, "⟳")), react.default.createElement("div", { className: "edrv-tree" }, error ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(error)), react.default.createElement("button", {
3846
+ className: "edrv-side-btn",
3847
+ onClick: () => refresh()
3848
+ }, "重试")) : null, rowsOf("", 0)));
3849
+ }
3850
+ //#endregion
3851
+ //#region src/client/sidebar/panels/index.ts
3852
+ /**
3853
+ * dsh-vscode-mode client — 「文件管理」面板定义(注册表一项)。
3854
+ * 作者 ddj 2026-08-26
3855
+ */
3856
+ /**
3857
+ * 构造文件管理面板定义。
3858
+ * @author ddj 2026年08月26号
3859
+ * @returns 面板定义(含活动栏徽标 = 差异文件总数)
3860
+ */
3861
+ function createFilePanel() {
3862
+ return {
3863
+ id: "explorer",
3864
+ title: "文件管理",
3865
+ icon: "📁",
3866
+ order: 10,
3867
+ badge: (ctx) => ctx.sum && ctx.sum.totalFiles > 0 ? ctx.sum.totalFiles : null,
3868
+ render: (ctx) => react.default.createElement(FileExplorer, { ctx })
3869
+ };
3870
+ }
3871
+ //#endregion
3872
+ //#region src/client/outline/parse.ts
3873
+ /** monaco SymbolKind 数值常量(与 LSP SymbolKind 一致,面板渲染按此分组)。 */
3874
+ const SK = {
3875
+ File: 0,
3876
+ Module: 1,
3877
+ Namespace: 2,
3878
+ Package: 3,
3879
+ Class: 4,
3880
+ Method: 5,
3881
+ Property: 6,
3882
+ Field: 7,
3883
+ Constructor: 8,
3884
+ Enum: 9,
3885
+ Interface: 10,
3886
+ Function: 11,
3887
+ Variable: 12,
3888
+ Constant: 13,
3889
+ String: 14,
3890
+ Number: 15,
3891
+ Boolean: 16,
3892
+ Array: 17,
3893
+ Object: 18,
3894
+ Key: 19,
3895
+ Null: 20,
3896
+ EnumMember: 21,
3897
+ Struct: 22,
3898
+ Event: 23,
3899
+ Operator: 24,
3900
+ TypeParameter: 25
3901
+ };
3902
+ /** 花括号语言的泛型函数名排除集(避免误把控制流/关键字当符号)。 */
3903
+ const BRACE_KW = /* @__PURE__ */ new Set([
3904
+ "if",
3905
+ "for",
3906
+ "while",
3907
+ "switch",
3908
+ "catch",
3909
+ "foreach",
3910
+ "using",
3911
+ "with",
3912
+ "when",
3913
+ "match",
3914
+ "return",
3915
+ "new",
3916
+ "in",
3917
+ "of",
3918
+ "do",
3919
+ "else",
3920
+ "elif",
3921
+ "then",
3922
+ "yield",
3923
+ "await",
3924
+ "typeof",
3925
+ "instanceof",
3926
+ "case",
3927
+ "default",
3928
+ "try",
3929
+ "finally",
3930
+ "throw"
3931
+ ]);
3932
+ /** 花括号语言的类型关键字 → SymbolKind。 */
3933
+ const BRACE_TYPE_KIND = {
3934
+ class: SK.Class,
3935
+ struct: SK.Struct,
3936
+ interface: SK.Interface,
3937
+ enum: SK.Enum,
3938
+ namespace: SK.Namespace,
3939
+ module: SK.Module,
3940
+ trait: SK.Interface,
3941
+ record: SK.Class
3942
+ };
3943
+ /** 按文档序(先序)补齐每个符号的 endLine:叶子取“下一个符号起行 - 1”,容器取末子 endLine。 */
3944
+ function computeEnds(symbols, lastLine) {
3945
+ const order = [];
3946
+ const collect = (list) => {
3947
+ for (const s of list) {
3948
+ order.push(s);
3949
+ if (s.children && s.children.length) collect(s.children);
3950
+ }
3951
+ };
3952
+ collect(symbols);
3953
+ for (let i = order.length - 1; i >= 0; i--) {
3954
+ const s = order[i];
3955
+ const kids = s.children ?? [];
3956
+ if (kids.length) s.endLine = Math.max(s.startLine, kids[kids.length - 1].endLine);
3957
+ else {
3958
+ const next = order[i + 1];
3959
+ const end = next ? Math.max(s.startLine, next.startLine - 1) : lastLine;
3960
+ s.endLine = Math.max(s.startLine, Math.min(end, lastLine));
3961
+ }
3962
+ }
3963
+ }
3964
+ /** 构造单行符号(起始行=跳转行=所在行;detail 为截断后的声明文本)。 */
3965
+ function mk(lineIndex, name, kind, raw) {
3966
+ const detail = raw.trim().replace(/\s+/g, " ").slice(0, 80);
3967
+ const line = lineIndex + 1;
3968
+ const out = {
3969
+ name,
3970
+ kind,
3971
+ startLine: line,
3972
+ endLine: line,
3973
+ selectLine: line
3974
+ };
3975
+ if (detail && detail !== name) out.detail = detail;
3976
+ return out;
3977
+ }
3978
+ /** Markdown/mdx:`#` 级标题,层级嵌套。 */
3979
+ function parseMarkdown(text) {
3980
+ const roots = [];
3981
+ const stack = [];
3982
+ const lines = text.split("\n");
3983
+ for (let i = 0; i < lines.length; i++) {
3984
+ const m = /^(\#{1,6})\s+(.+?)\s*#*\s*$/.exec(lines[i]);
3985
+ if (!m) continue;
3986
+ const item = {
3987
+ name: m[2].trim(),
3988
+ kind: SK.Namespace,
3989
+ startLine: i + 1,
3990
+ endLine: i + 1,
3991
+ selectLine: i + 1,
3992
+ children: []
3993
+ };
3994
+ while (stack.length && stack[stack.length - 1].level >= m[1].length) stack.pop();
3995
+ const parent = stack.length ? stack[stack.length - 1].item : null;
3996
+ (parent ? parent.children : roots).push(item);
3997
+ stack.push({
3998
+ level: m[1].length,
3999
+ item
4000
+ });
4001
+ }
4002
+ computeEnds(roots, lines.length);
4003
+ return roots;
4004
+ }
4005
+ /** Python:def/class 按缩进栈嵌套。 */
4006
+ function parsePython(text) {
4007
+ const roots = [];
4008
+ const stack = [];
4009
+ const lines = text.split("\n");
4010
+ for (let i = 0; i < lines.length; i++) {
4011
+ const m = /^([ \t]*)(?:class|def)\s+([A-Za-z_]\w*)\s*[(:]/.exec(lines[i]);
4012
+ if (!m) continue;
4013
+ const indent = m[1].replace(/\t/g, " ").length;
4014
+ const isClass = /class\s/.test(lines[i]);
4015
+ const item = {
4016
+ name: m[2],
4017
+ kind: isClass ? SK.Class : SK.Function,
4018
+ startLine: i + 1,
4019
+ endLine: i + 1,
4020
+ selectLine: i + 1,
4021
+ children: []
4022
+ };
4023
+ while (stack.length && stack[stack.length - 1].indent >= indent) stack.pop();
4024
+ const parent = stack.length ? stack[stack.length - 1].item : null;
4025
+ (parent ? parent.children : roots).push(item);
4026
+ stack.push({
4027
+ indent,
4028
+ item
4029
+ });
4030
+ }
4031
+ computeEnds(roots, lines.length);
4032
+ return roots;
4033
+ }
4034
+ /** Shell:`name() {` 函数。 */
4035
+ function parseShell(text) {
4036
+ const out = [];
4037
+ const lines = text.split("\n");
4038
+ for (let i = 0; i < lines.length; i++) {
4039
+ const m = /^[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]*\(\)[ \t]*\{/.exec(lines[i]);
4040
+ if (m) out.push(mk(i, m[1], SK.Function, lines[i]));
4041
+ }
4042
+ computeEnds(out, lines.length);
4043
+ return out;
4044
+ }
4045
+ /** PowerShell:`function Name {` / `filter Name {`。 */
4046
+ function parsePwsh(text) {
4047
+ const out = [];
4048
+ const lines = text.split("\n");
4049
+ for (let i = 0; i < lines.length; i++) {
4050
+ const m = /^[ \t]*(?:function|filter)[ \t]+([\w.-]+)/.exec(lines[i]);
4051
+ if (m) out.push(mk(i, m[1], SK.Function, lines[i]));
4052
+ }
4053
+ computeEnds(out, lines.length);
4054
+ return out;
4055
+ }
4056
+ /** Lua:`function Foo:bar` / `M.bar = class|function`。 */
4057
+ function parseLua(text) {
4058
+ const out = [];
4059
+ const lines = text.split("\n");
4060
+ for (let i = 0; i < lines.length; i++) {
4061
+ const line = lines[i];
4062
+ if (/^\s*--/.test(line)) continue;
4063
+ const fm = /^[ \t]*(?:local[ \t]+)?function[ \t]+([A-Za-z_][\w.:]*)/.exec(line);
4064
+ if (fm) {
4065
+ out.push(mk(i, fm[1], SK.Function, line));
4066
+ continue;
4067
+ }
4068
+ const cm = /^[ \t]*([A-Za-z_][\w.]*)[ \t]*=[ \t]*(?:class|function)\b/.exec(line);
4069
+ if (cm) out.push(mk(i, cm[1], /class\b/.test(line) ? SK.Class : SK.Function, line));
4070
+ }
4071
+ computeEnds(out, lines.length);
4072
+ return out;
4073
+ }
4074
+ /** Go:func(含 receiver)/ type。 */
4075
+ function parseGo(text) {
4076
+ const out = [];
4077
+ const lines = text.split("\n");
4078
+ for (let i = 0; i < lines.length; i++) {
4079
+ const fm = /^[ \t]*func[ \t]+(?:\([^)]*\)[ \t]*)?([A-Za-z_]\w*)/.exec(lines[i]);
4080
+ if (fm) {
4081
+ out.push(mk(i, fm[1], SK.Function, lines[i]));
4082
+ continue;
4083
+ }
4084
+ const tm = /^[ \t]*type[ \t]+([A-Za-z_]\w*)/.exec(lines[i]);
4085
+ if (tm) out.push(mk(i, tm[1], SK.Struct, lines[i]));
4086
+ }
4087
+ computeEnds(out, lines.length);
4088
+ return out;
4089
+ }
4090
+ /** Rust:fn/struct/enum/trait/impl/mod/type。 */
4091
+ function parseRust(text) {
4092
+ const out = [];
4093
+ const lines = text.split("\n");
4094
+ const kindOf = {
4095
+ fn: SK.Function,
4096
+ struct: SK.Struct,
4097
+ enum: SK.Enum,
4098
+ trait: SK.Interface,
4099
+ impl: SK.Module,
4100
+ mod: SK.Module,
4101
+ type: SK.TypeParameter
4102
+ };
4103
+ for (let i = 0; i < lines.length; i++) {
4104
+ const m = /^[ \t]*(?:pub(?:\([^)]*\))?[ \t]+)?(fn|struct|enum|trait|impl|mod|type)[ \t]+([A-Za-z_]\w*)/.exec(lines[i]);
4105
+ if (m && kindOf[m[1]]) out.push(mk(i, m[2], kindOf[m[1]], lines[i]));
4106
+ }
4107
+ computeEnds(out, lines.length);
4108
+ return out;
4109
+ }
4110
+ /** YAML:仅顶层键。 */
4111
+ function parseYaml(text) {
4112
+ const out = [];
4113
+ const lines = text.split("\n");
4114
+ for (let i = 0; i < lines.length; i++) {
4115
+ const line = lines[i];
4116
+ if (!line.trim() || /^\s/.test(line) || /^\s*[#-]/.test(line)) continue;
4117
+ const m = /^([A-Za-z0-9_.\-]+)\s*:/.exec(line);
4118
+ if (m) out.push(mk(i, m[1], SK.Key, line));
4119
+ }
4120
+ computeEnds(out, lines.length);
4121
+ return out;
4122
+ }
4123
+ /** INI/TOML:`[section]` + `key=value`。 */
4124
+ function parseIni(text) {
4125
+ const out = [];
4126
+ const lines = text.split("\n");
4127
+ for (let i = 0; i < lines.length; i++) {
4128
+ const line = lines[i];
4129
+ if (!line.trim() || /^\s*[;#]/.test(line)) continue;
4130
+ const sm = /^\[([^\]]+)\]/.exec(line);
4131
+ if (sm) {
4132
+ out.push(mk(i, sm[1], SK.Namespace, line));
4133
+ continue;
4134
+ }
4135
+ const km = /^([A-Za-z0-9_.\-]+)\s*[=:]/.exec(line.trim());
4136
+ if (km) out.push(mk(i, km[1], SK.Key, line));
4137
+ }
4138
+ computeEnds(out, lines.length);
4139
+ return out;
4140
+ }
4141
+ /** 花括号语言(C 族/JVM/.NET):类型声明 + 泛型函数扫描(扁平,best-effort)。 */
4142
+ function parseBrace(text) {
4143
+ const out = [];
4144
+ const lines = text.split("\n");
4145
+ const typeRe = /^[ \t]*(?:(?:public|private|protected|internal|static|final|abstract|sealed|readonly|export|declare|async|open|extern|virtual|override|const|global|pub|local)\s+)*(class|struct|interface|enum|namespace|module|trait|record)\s+([A-Za-z_]\w*)/;
4146
+ const funcRe = /^[ \t]*(?:[\w<>,*&\[\].:]+\s+)*([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\(/;
4147
+ for (let i = 0; i < lines.length; i++) {
4148
+ const line = lines[i];
4149
+ const t = line.trim();
4150
+ if (!t || /^\s*\*/.test(line)) continue;
4151
+ const tm = typeRe.exec(line);
4152
+ if (tm) {
4153
+ out.push(mk(i, tm[2], BRACE_TYPE_KIND[tm[1]] ?? SK.Class, line));
4154
+ continue;
4155
+ }
4156
+ const fm = funcRe.exec(line);
4157
+ if (!fm) continue;
4158
+ if (BRACE_KW.has(fm[1]) || /;\s*$/.test(t)) continue;
4159
+ out.push(mk(i, fm[1], SK.Function, line));
4160
+ }
4161
+ computeEnds(out, lines.length);
4162
+ return out;
4163
+ }
4164
+ /**
4165
+ * 按 Monaco languageId 解析大纲符号(纯函数)。
4166
+ * @author ddj 2026年08月27号
4167
+ * @param languageId Monaco 语言 id
4168
+ * @param text 文件全文
4169
+ * @returns 归一化大纲符号树
4170
+ */
4171
+ function parseOutline(languageId, text) {
4172
+ switch (languageId) {
4173
+ case "markdown":
4174
+ case "mdx": return parseMarkdown(text);
4175
+ case "python": return parsePython(text);
4176
+ case "shell": return parseShell(text);
4177
+ case "powershell": return parsePwsh(text);
4178
+ case "lua": return parseLua(text);
4179
+ case "go": return parseGo(text);
4180
+ case "rust": return parseRust(text);
4181
+ case "yaml": return parseYaml(text);
4182
+ case "ini": return parseIni(text);
4183
+ case "c":
4184
+ case "cpp":
4185
+ case "csharp":
4186
+ case "java":
4187
+ case "php":
4188
+ case "ruby":
4189
+ case "kotlin":
4190
+ case "swift":
4191
+ case "dart": return parseBrace(text);
4192
+ default: return [];
4193
+ }
4194
+ }
4195
+ //#endregion
4196
+ //#region src/client/outline/sources.ts
4197
+ /** 无内置 provider、由 parse.ts 兜底的语言。 */
4198
+ const OUTLINE_FALLBACK_LANGS = /* @__PURE__ */ new Set([
4199
+ "markdown",
4200
+ "mdx",
4201
+ "python",
4202
+ "shell",
4203
+ "powershell",
4204
+ "lua",
4205
+ "go",
4206
+ "rust",
4207
+ "yaml",
4208
+ "ini",
4209
+ "c",
4210
+ "cpp",
4211
+ "csharp",
4212
+ "java",
4213
+ "php",
4214
+ "ruby",
4215
+ "kotlin",
4216
+ "swift",
4217
+ "dart"
4218
+ ]);
4219
+ /**
4220
+ * 创建生命周期独立的大纲源注册表。
4221
+ * @author ddj 2026年08月27号
4222
+ * @returns 大纲源注册表
4223
+ */
4224
+ function createOutlineSourceRegistry() {
4225
+ const entries = /* @__PURE__ */ new Map();
4226
+ const listeners = /* @__PURE__ */ new Set();
4227
+ const notify = () => {
4228
+ for (const listener of listeners) listener();
4229
+ };
4230
+ const list = () => [...entries.values()].sort((a, b) => b.priority - a.priority || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
4231
+ return {
4232
+ register(source) {
4233
+ if (!source || !source.id || typeof source.get !== "function") throw new TypeError("大纲源必须提供 id 与 get");
4234
+ entries.set(source.id, source);
4235
+ notify();
4236
+ return () => {
4237
+ if (entries.get(source.id) !== source) return;
4238
+ entries.delete(source.id);
4239
+ notify();
4240
+ };
4241
+ },
4242
+ list,
4243
+ subscribe(listener) {
4244
+ listeners.add(listener);
4245
+ return () => listeners.delete(listener);
4246
+ },
4247
+ get: (id) => entries.get(id)
4248
+ };
4249
+ }
4250
+ /** 归一化 Monaco OutlineElement(getTopLevelSymbols 产物)→ OutlineSymbol。 */
4251
+ function normalizeMonaco(el) {
4252
+ if (!el || typeof el.name !== "string") return null;
4253
+ const range = el.range || {};
4254
+ const sel = el.selectionRange || range;
4255
+ const startLine = Math.max(1, Number(sel.startLineNumber ?? range.startLineNumber ?? 1));
4256
+ const endLine = Math.max(1, Number(range.endLineNumber ?? startLine));
4257
+ const out = {
4258
+ name: el.name,
4259
+ kind: typeof el.kind === "number" ? el.kind : 0,
4260
+ startLine: Math.max(1, Number(range.startLineNumber ?? startLine)),
4261
+ endLine,
4262
+ selectLine: startLine
4263
+ };
4264
+ if (typeof el.detail === "string" && el.detail) out.detail = el.detail;
4265
+ if (Array.isArray(el.children) && el.children.length) {
4266
+ const kids = el.children.map(normalizeMonaco).filter(Boolean);
4267
+ if (kids.length) out.children = kids;
4268
+ }
4269
+ return out;
4270
+ }
4271
+ /**
4272
+ * 按优先级解析大纲符号:首个非空结果生效(出错源跳过,落入下一优先级)。
4273
+ * @author ddj 2026年08月27号
4274
+ * @param sources 大纲源注册表
4275
+ * @param input 当前快照(languageId/model/editor/monaco)
4276
+ * @returns 归一化符号树(无符号时为空数组)
4277
+ */
4278
+ async function resolveOutline(sources, input) {
4279
+ for (const source of sources.list()) {
4280
+ let supports = false;
4281
+ try {
4282
+ supports = source.provides(input.languageId);
4283
+ } catch {
4284
+ supports = false;
4285
+ }
4286
+ if (!supports) continue;
4287
+ let items = null;
4288
+ try {
4289
+ items = await source.get(input);
4290
+ } catch {
4291
+ items = null;
4292
+ }
4293
+ if (items && items.length) return items;
4294
+ }
4295
+ return [];
4296
+ }
4297
+ /**
4298
+ * 注册内置大纲源(monaco=50 / fallback=30)。
4299
+ * @author ddj 2026年08月27号
4300
+ * @param registry 目标注册表
4301
+ * @returns 注销函数(同时注销全部内置源)
4302
+ */
4303
+ function registerBuiltinOutlineSources(registry) {
4304
+ const disposers = [registry.register({
4305
+ id: "monaco",
4306
+ priority: 50,
4307
+ provides: () => true,
4308
+ async get(input) {
4309
+ const ed = input.editor;
4310
+ const model = input.model;
4311
+ const cmd = ed?._commandService;
4312
+ if (!cmd || typeof cmd.executeCommand !== "function") return [];
4313
+ const uri = model?.uri;
4314
+ if (!uri) return [];
4315
+ const list = await cmd.executeCommand("_executeDocumentSymbolProvider", uri);
4316
+ return Array.isArray(list) ? list.map(normalizeMonaco).filter(Boolean) : [];
4317
+ }
4318
+ }), registry.register({
4319
+ id: "fallback",
4320
+ priority: 30,
4321
+ provides: (languageId) => OUTLINE_FALLBACK_LANGS.has(languageId),
4322
+ async get(input) {
4323
+ const model = input.model;
4324
+ if (!model || typeof model.getValue !== "function") return [];
4325
+ return parseOutline(input.languageId, model.getValue());
4326
+ }
4327
+ })];
4328
+ return () => {
4329
+ for (const dispose of disposers) dispose();
4330
+ };
4331
+ }
4332
+ //#endregion
4333
+ //#region src/client/outline/OutlinePanel.tsx
4334
+ /**
4335
+ * dsh-vscode-mode client — 侧边栏「大纲」面板。
4336
+ * 数据源经 resolveOutline(源注册表,优先级降序、首个非空生效)解析当前活动文件符号:
4337
+ * monaco 源吃原生 document symbol provider,fallback 源兜底无内置提供方的语言。
4338
+ * 交互:点击符号跳转编辑器、▸/▾ 折叠、光标所在符号高亮、空/加载/错误态。
4339
+ * 作者 ddj 2026-08-27
4340
+ */
4341
+ /** 渲染符号数上限(防超大文件卡顿,超出显示截断提示)。 */
4342
+ const RENDER_CAP = 800;
4343
+ /** kind 分组元信息(字形 + 样式类)。 */
4344
+ const KIND_GROUPS = {
4345
+ func: {
4346
+ glyph: "ƒ",
4347
+ cls: "edrv-ol-func"
4348
+ },
4349
+ type: {
4350
+ glyph: "C",
4351
+ cls: "edrv-ol-type"
4352
+ },
4353
+ data: {
4354
+ glyph: "•",
4355
+ cls: "edrv-ol-data"
4356
+ },
4357
+ ns: {
4358
+ glyph: "▤",
4359
+ cls: "edrv-ol-ns"
4360
+ },
4361
+ key: {
4362
+ glyph: "·",
4363
+ cls: "edrv-ol-key"
4364
+ }
4365
+ };
4366
+ /** SymbolKind 数值 → 分组(File0..Package3=ns;Class4/Enum9/Interface10/Struct22/TypeParameter25=type;Method5/Constructor8/Function11=func;Key19/EnumMember21=key)。 */
4367
+ function kindMeta(kind) {
4368
+ const k = kind | 0;
4369
+ if (k <= 3) return KIND_GROUPS.ns;
4370
+ if (k === 4 || k === 9 || k === 10 || k === 22 || k === 25) return KIND_GROUPS.type;
4371
+ if (k === 5 || k === 8 || k === 11) return KIND_GROUPS.func;
4372
+ if (k === 19 || k === 21) return KIND_GROUPS.key;
4373
+ return KIND_GROUPS.data;
4374
+ }
4375
+ /**
4376
+ * 大纲面板主体。
4377
+ * @param props.ctx 面板共享上下文(editor/outlineSources/activePath)
4378
+ */
4379
+ function OutlinePanel(props) {
4380
+ const ctx = props?.ctx;
4381
+ const activePath = ctx?.activePath ?? null;
4382
+ const [symbols, setSymbols] = react.default.useState(null);
4383
+ const [error, setError] = react.default.useState(null);
4384
+ const [collapsed, setCollapsed] = react.default.useState({});
4385
+ const [cursorLine, setCursorLine] = react.default.useState(null);
4386
+ const seqRef = react.default.useRef(0);
4387
+ const ctxRef = react.default.useRef(ctx);
4388
+ ctxRef.current = ctx;
4389
+ const activeRef = react.default.useRef(activePath);
4390
+ activeRef.current = activePath;
4391
+ const sourcesRef = react.default.useRef(ctx?.outlineSources);
4392
+ sourcesRef.current = ctx?.outlineSources;
4393
+ const refresh = react.default.useCallback(() => {
4394
+ const ed = ctxRef.current?.editor?.();
4395
+ const model = ed?.getModel?.();
4396
+ const seq = ++seqRef.current;
4397
+ if (!ed || !model) {
4398
+ setSymbols(null);
4399
+ setError(null);
4400
+ return;
4401
+ }
4402
+ const uriPath = model?.uri?.path;
4403
+ const modelPath = uriPath ? decodeURIComponent(String(uriPath).replace(/^\//, "")) : null;
4404
+ const active = activeRef.current;
4405
+ if (active && modelPath !== active) {
4406
+ setSymbols(null);
4407
+ setError(null);
4408
+ return;
4409
+ }
4410
+ const sources = sourcesRef.current;
4411
+ if (!sources) {
4412
+ setSymbols([]);
4413
+ setError(null);
4414
+ return;
4415
+ }
4416
+ setError(null);
4417
+ resolveOutline(sources, {
4418
+ languageId: model.getLanguageId(),
4419
+ model,
4420
+ editor: ed,
4421
+ monaco: window.monaco
4422
+ }).then((list) => {
4423
+ if (seq === seqRef.current) setSymbols(list || []);
4424
+ }).catch((e) => {
4425
+ if (seq === seqRef.current) {
4426
+ setError(String(e?.message ?? e));
4427
+ setSymbols(null);
4428
+ }
4429
+ });
4430
+ }, []);
4431
+ react.default.useEffect(() => {
4432
+ const disposers = [];
4433
+ let debounceTimer = null;
4434
+ let waitTimer = null;
4435
+ const attach = () => {
4436
+ const ed = ctxRef.current?.editor?.();
4437
+ if (!ed) return false;
4438
+ let contentDisposable = null;
4439
+ const subContent = (model) => {
4440
+ if (contentDisposable) {
4441
+ contentDisposable.dispose();
4442
+ contentDisposable = null;
4443
+ }
4444
+ contentDisposable = model?.onDidChangeContent?.(() => {
4445
+ if (debounceTimer) clearTimeout(debounceTimer);
4446
+ debounceTimer = setTimeout(() => {
4447
+ debounceTimer = null;
4448
+ refresh();
4449
+ }, 300);
4450
+ }) ?? null;
4451
+ };
4452
+ const onModel = () => {
4453
+ subContent(ed.getModel?.());
4454
+ refresh();
4455
+ };
4456
+ const onCursor = (e) => setCursorLine(e?.position?.lineNumber ?? null);
4457
+ subContent(ed.getModel?.());
4458
+ const subs = [
4459
+ ed.onDidChangeModel?.(onModel),
4460
+ ed.onDidChangeCursorPosition?.(onCursor),
4461
+ { dispose: () => {
4462
+ if (contentDisposable) contentDisposable.dispose();
4463
+ } }
4464
+ ];
4465
+ for (const s of subs) if (s) disposers.push(s);
4466
+ refresh();
4467
+ return true;
4468
+ };
4469
+ const onRefresh = () => refresh();
4470
+ window.addEventListener("edrv:refresh", onRefresh);
4471
+ if (!attach()) waitTimer = setInterval(() => {
4472
+ if (attach()) {
4473
+ clearInterval(waitTimer);
4474
+ waitTimer = null;
4475
+ }
4476
+ }, 400);
4477
+ return () => {
4478
+ if (debounceTimer) clearTimeout(debounceTimer);
4479
+ if (waitTimer) clearInterval(waitTimer);
4480
+ for (const d of disposers) if (d?.dispose) d.dispose();
4481
+ window.removeEventListener("edrv:refresh", onRefresh);
4482
+ };
4483
+ }, [refresh]);
4484
+ react.default.useEffect(() => {
4485
+ refresh();
4486
+ }, [refresh, activePath]);
4487
+ const jump = (sym) => {
4488
+ const ed = ctxRef.current?.editor?.();
4489
+ if (!ed) return;
4490
+ const line = Math.max(1, sym?.selectLine ?? sym?.startLine ?? 1);
4491
+ ed.revealLineInCenter(line);
4492
+ ed.setPosition({
4493
+ lineNumber: line,
4494
+ column: 1
4495
+ });
4496
+ ed.focus();
4497
+ };
4498
+ const toggleCollapse = (key) => {
4499
+ setCollapsed((prev) => Object.assign({}, prev, { [key]: prev[key] === true ? false : true }));
4500
+ };
4501
+ const allKeys = react.default.useMemo(() => {
4502
+ const keys = [];
4503
+ const walk = (list, prefix) => {
4504
+ for (let i = 0; i < list.length; i++) {
4505
+ const key = prefix ? prefix + "/" + i : String(i);
4506
+ const sym = list[i];
4507
+ if (sym.children && sym.children.length) {
4508
+ keys.push(key);
4509
+ walk(sym.children, key);
4510
+ }
4511
+ }
4512
+ };
4513
+ walk(symbols || [], "");
4514
+ return keys;
4515
+ }, [symbols]);
4516
+ const setAll = (value) => {
4517
+ const next = {};
4518
+ for (const key of allKeys) next[key] = value;
4519
+ setCollapsed(next);
4520
+ };
4521
+ const renderTree = (list, depth, prefix) => {
4522
+ const rows = [];
4523
+ const cap = Math.min(list.length, RENDER_CAP);
4524
+ for (let i = 0; i < cap; i++) {
4525
+ const sym = list[i];
4526
+ const key = prefix ? prefix + "/" + i : String(i);
4527
+ const kids = sym.children && sym.children.length ? sym.children : null;
4528
+ const isCollapsed = collapsed[key] === true;
4529
+ const meta = kindMeta(sym.kind);
4530
+ const active = cursorLine != null && sym.startLine <= cursorLine && cursorLine <= sym.endLine;
4531
+ rows.push(react.default.createElement("div", {
4532
+ key,
4533
+ className: "edrv-tree-row" + (active ? " edrv-tree-active" : ""),
4534
+ title: sym.detail || sym.name,
4535
+ style: { paddingLeft: 6 + depth * 14 },
4536
+ onClick: () => jump(sym)
4537
+ }, react.default.createElement("span", {
4538
+ className: "edrv-tree-chev",
4539
+ onClick: (e) => {
4540
+ e.stopPropagation();
4541
+ toggleCollapse(key);
4542
+ }
4543
+ }, kids ? isCollapsed ? "▸" : "▾" : ""), react.default.createElement("span", { className: "edrv-outline-kind " + meta.cls }, meta.glyph), react.default.createElement("span", { className: "edrv-tree-name" }, sym.name), sym.detail ? react.default.createElement("span", { className: "edrv-outline-detail" }, sym.detail) : null, react.default.createElement("span", { className: "edrv-outline-ln" }, String(sym.selectLine ?? sym.startLine ?? ""))));
4544
+ if (kids && !isCollapsed) rows.push(...renderTree(kids, depth + 1, key));
4545
+ }
4546
+ if (list.length > RENDER_CAP) rows.push(react.default.createElement("div", {
4547
+ key: "cap",
4548
+ className: "edrv-tree-loading"
4549
+ }, "符号过多,仅显示前 800 个"));
4550
+ return rows;
4551
+ };
4552
+ const basename = activePath ? String(activePath).split(/[\\/]/).pop() || activePath : "";
4553
+ let body;
4554
+ if (error) body = react.default.createElement("div", { className: "edrv-tree" }, react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(error)), react.default.createElement("button", {
4555
+ className: "edrv-side-btn",
4556
+ onClick: refresh
4557
+ }, "重试")));
4558
+ else if (!activePath) body = react.default.createElement("div", { className: "edrv-tree" }, react.default.createElement("div", { className: "edrv-tree-loading" }, "未打开文件"));
4559
+ else if (symbols === null) body = react.default.createElement("div", { className: "edrv-tree" }, react.default.createElement("div", { className: "edrv-tree-loading" }, "加载中…"));
4560
+ else if (symbols.length === 0) body = react.default.createElement("div", { className: "edrv-tree" }, react.default.createElement("div", { className: "edrv-tree-loading" }, "该语言暂不支持大纲或文件无符号"));
4561
+ else body = react.default.createElement("div", { className: "edrv-tree" }, ...renderTree(symbols, 0, ""));
4562
+ return react.default.createElement("div", { className: "edrv-side-panel" }, react.default.createElement("div", { className: "edrv-side-head" }, react.default.createElement("span", { className: "edrv-side-title" }, "大纲"), react.default.createElement("span", {
4563
+ className: "edrv-side-root",
4564
+ title: activePath || ""
4565
+ }, basename), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("button", {
4566
+ className: "edrv-side-btn",
4567
+ title: "折叠全部",
4568
+ disabled: !(symbols && symbols.length),
4569
+ onClick: () => setAll(true)
4570
+ }, "−"), react.default.createElement("button", {
4571
+ className: "edrv-side-btn",
4572
+ title: "展开全部",
4573
+ disabled: !(symbols && symbols.length),
4574
+ onClick: () => setAll(false)
4575
+ }, "+"), react.default.createElement("button", {
4576
+ className: "edrv-side-btn",
4577
+ title: "刷新大纲",
4578
+ onClick: refresh
4579
+ }, "⟳")), body);
4580
+ }
4581
+ //#endregion
4582
+ //#region src/client/outline/index.ts
4583
+ /**
4584
+ * dsh-vscode-mode client — 「大纲」面板定义(注册表一项)。
4585
+ * 数据源解析复用 outline/sources 的源注册表(ctx.outlineSources 注入)。
4586
+ * 作者 ddj 2026-08-27
4587
+ */
4588
+ /**
4589
+ * 构造大纲面板定义。
4590
+ * @author ddj 2026年08月27号
4591
+ * @returns 面板定义(无徽标;活动栏图标 📜)
4592
+ */
4593
+ function createOutlinePanel() {
4594
+ return {
4595
+ id: "outline",
4596
+ title: "大纲",
4597
+ icon: "📜",
4598
+ order: 20,
4599
+ render: (ctx) => react.default.createElement(OutlinePanel, { ctx })
4600
+ };
4601
+ }
4602
+ //#endregion
3253
4603
  //#region src/client/index.ts
3254
4604
  /**
3255
4605
  * dsh-vscode-mode client — 浏览器半入口:slot 注册 + 装配。
3256
- * 挂点:conversation.view「文件编辑」页签(中央 Monaco 编辑器)+ header 差异角标。
4606
+ * 挂点:conversation.view「文件编辑」页签(中央 Monaco 编辑器)+ conversation.input.dock 差异条 + header 差异角标。
3257
4607
  * 与 Host 通信:同源 fetch('/edrv/rpc')(shared/rpc 契约)。
3258
4608
  *
3259
4609
  * ⚠️ 跨版本 slot 装配(2026-08-21):新版 DSH 的 slots 系统要求 slot 必须由父 entry
@@ -3261,7 +4611,8 @@ window.__ModuleLoader__.load({
3261
4611
  * 声明未就绪时抛 `slot ... is not declared (a parent entry's children table must declare it)`。
3262
4612
  * 正确写法 = `ctx.slots.inject(name, () => ctx.slots.register(...))`:声明存在时同步
3263
4613
  * 执行,否则等待声明(官方 ui-conversation 自身即此模式);旧版(rc.8 及更早)同样支持,
3264
- * 故跨版本兼容。slot 名未变:conversation.view / conversation.session.header.utilities。
4614
+ * 故跨版本兼容。slot 名:conversation.view / conversation.session.header.utilities /
4615
+ * conversation.input.dock。
3265
4616
  * 作者 ddj 2026-08-20
3266
4617
  */
3267
4618
  const inject = [
@@ -3283,14 +4634,24 @@ window.__ModuleLoader__.load({
3283
4634
  */
3284
4635
  function apply(ctx) {
3285
4636
  const schedule = (fn, ms) => ctx.timeout(fn, ms);
3286
- if (typeof window !== "undefined") {
3287
- const preloadMonaco = () => loadMonaco(() => {}).catch(() => {});
3288
- if (typeof window.requestIdleCallback === "function") window.requestIdleCallback(preloadMonaco, { timeout: 2e3 });
3289
- else schedule(preloadMonaco, 300);
3290
- }
3291
4637
  const registry = createFileOpenerRegistry();
3292
4638
  const workspaces = ctx.get("workspaces");
3293
4639
  const sessions = ctx.get("sessions");
4640
+ const monacoList = sessions?.list;
4641
+ if (typeof window !== "undefined" && monacoList && typeof monacoList.subscribe === "function") {
4642
+ const list = monacoList;
4643
+ const schedulePreload = () => {
4644
+ const preload = () => loadMonaco(() => {}).catch(() => {});
4645
+ if (typeof window.requestIdleCallback === "function") window.requestIdleCallback(preload, { timeout: 2e3 });
4646
+ else schedule(preload, 300);
4647
+ };
4648
+ const onSessionEnter = () => {
4649
+ if (!list.getSnapshot()?.current) return;
4650
+ schedulePreload();
4651
+ };
4652
+ ctx.effect(() => list.subscribe(onSessionEnter), "vscode-mode: monaco session trigger");
4653
+ onSessionEnter();
4654
+ }
3294
4655
  const originalOpenPath = workspaces?.openPath;
3295
4656
  const binder = pickSettingsBinder(ctx);
3296
4657
  const settings = binder.scope;
@@ -3317,6 +4678,13 @@ window.__ModuleLoader__.load({
3317
4678
  ];
3318
4679
  ctx.provide("fileOpeners", registry);
3319
4680
  ctx.effect(() => registry.register(vscodeOpener()), "vscode-mode: file opener");
4681
+ const sidebarPanels = createSidebarPanelRegistry();
4682
+ ctx.provide("edrvSidebarPanels", sidebarPanels);
4683
+ ctx.effect(() => sidebarPanels.register(createFilePanel()), "vscode-mode: sidebar panel");
4684
+ const outlineSources = createOutlineSourceRegistry();
4685
+ ctx.provide("edrvOutlineSources", outlineSources);
4686
+ ctx.effect(() => registerBuiltinOutlineSources(outlineSources), "vscode-mode: outline sources");
4687
+ ctx.effect(() => sidebarPanels.register(createOutlinePanel()), "vscode-mode: sidebar panel outline");
3320
4688
  ctx.effect(() => registry.register({
3321
4689
  id: "system",
3322
4690
  label: "系统默认应用",
@@ -3360,8 +4728,17 @@ window.__ModuleLoader__.load({
3360
4728
  inject: (sessionId) => ({ sessionId })
3361
4729
  }, (props) => react.default.createElement(EditorView, Object.assign({}, props, {
3362
4730
  schedule,
3363
- addToConversation
4731
+ addToConversation,
4732
+ sidebarPanels,
4733
+ outlineSources
3364
4734
  })));
4735
+ registerSlotSafely(ctx, {
4736
+ name: "conversation.input.dock",
4737
+ id: "edrv-diff-dock",
4738
+ order: 30,
4739
+ label: "差异",
4740
+ inject: (sessionId) => ({ sessionId })
4741
+ }, (props) => react.default.createElement(ConversationDiffDock, Object.assign({}, props)));
3365
4742
  registerSlotSafely(ctx, {
3366
4743
  name: "conversation.session.header.utilities",
3367
4744
  id: "edrv-diff-badge",