dsh-vscode-mode 0.3.1 → 0.3.2

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
@@ -32,7 +32,7 @@ window.__ModuleLoader__.load({
32
32
  let react_dom_client = require("react-dom/client");
33
33
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
34
34
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
35
- const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 主题:跟随官方 --dsw-alias-* 令牌(DSH 明暗与第三方皮肤生效)。\n 本作用域不再重定义官方令牌(旧实现曾在此硬覆盖浅色盘,导致插件不跟随 DSH 主题);\n 旧环境/缺令牌场景由每条声明的 var(--dsw-alias-*, 回落值) 兜底。\n @author ddj 2026-09-10 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; }\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-star { color: var(--dsw-alias-brand-primary, #4f8cff); font-size: 13px; font-weight: 700; line-height: 1; flex-shrink: 0; } /* 未保存修改星号 @author ddj 2026年09月09号 */\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: var(--dsw-alias-interactive-bg-hover, 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: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress-message { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress { flex: 0 0 auto; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-lsp-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-lsp-status-dot.busy { background: var(--dsw-alias-state-warn-primary, #d97706); animation: edrv-lsp-pulse 1.2s ease-in-out infinite; }\n@keyframes edrv-lsp-pulse { 50% { opacity: .45; } }\n/* Ctrl+hover 可导航标识符下划线提示(绑定 Monaco inlineClassName 装饰) */\n[data-edrv-view] .edrv-nav-underline { text-decoration: underline; text-decoration-color: var(--dsw-alias-interactive-primary, #4fc1ff); text-underline-offset: 1px; cursor: pointer; }\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-chip-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-chip-btn:disabled:hover { background: transparent; }\n[data-edrv-view] .edrv-mn-add-line { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }\n[data-edrv-view] .edrv-mn-gutter-add { color: var(--dsw-alias-state-success-primary, #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: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); border-top: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); border-bottom: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); box-shadow: inset 0 0 0 1px var(--dsw-alias-interactive-bg-hover-danger, 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: var(--dsw-alias-state-error-primary, #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: var(--dsw-alias-state-error-primary, #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-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }\n[data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }\n[data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); }\n[data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-pdf-page { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; min-width: 52px; text-align: center; }\n[data-edrv-view] .edrv-pdf-zoom { min-width: 26px; text-align: center; padding: 3px 7px; }\n[data-edrv-view] .edrv-pdf-sep { width: 1px; height: 16px; background: var(--dsw-alias-border-l1, #e0e6e8); margin: 0 2px; }\n[data-edrv-view] .edrv-pdf-mode-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-brand-primary, #0f9d58); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-pdf-dirty { color: var(--dsw-alias-state-warn-primary, #d97706); border-color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-pdf-stage { position: absolute; top: 34px; left: 0; right: 0; bottom: 0; overflow: auto; }\n[data-edrv-view] .edrv-pdf-stage .edrv-pdf-viewer { margin: 0 auto; }\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: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-state-success-primary, #0f7a45); border: 1px solid var(--dsw-alias-state-success-primary, rgba(15,157,88,.4)); }\n[data-edrv-view] .edrv-pill-undo { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); color: var(--dsw-alias-state-error-primary, #c0392b); border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-pill-ghost { background: var(--dsw-alias-bg-layer-1, #ffffff); color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 编辑区 / Tab 右键菜单(添加文件/选中内容到对话) */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 200px; max-width: 320px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-item:disabled,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled { color: var(--dsw-alias-label-disabled, #b0b7c0); cursor: default; background: transparent; }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-primary, 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: 1; 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; display: inline-flex; align-items: center; justify-content: center; }\n/* 树行图标:官方原语(文件夹/文件类型),尺寸与行高对齐 @author ddj 2026-09-10 */\n[data-edrv-view] .edrv-tree-icon { flex-shrink: 0; display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active .edrv-tree-icon { color: var(--dsw-alias-label-secondary, #52606d); }\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: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-context { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); }\n[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: var(--dsw-alias-label-primary-inverted, #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 var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* 大纲面板:kind 字形 / detail / 行号 */\n[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== 侧栏形态(betterSidebar 面板内编辑区) ===== */\n[data-edrv-view][data-edrv-layout='side'] { box-sizing: border-box; height: 100%; }\n\n/* ===== 侧边栏引导条(旧页签形态、未装 betterSidebar 时) ===== */\n[data-edrv-view] .edrv-side-hint { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; padding: 6px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-side-hint-text { white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-cmd { font-size: 11px; padding: 2px 6px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #ffffff); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-close { margin-left: auto; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-hint-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== search panel (sidebar, VSCode search view) ===== */\n[data-edrv-view] .edrv-search-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-search-qrow { display: flex; gap: 4px; }\n[data-edrv-view] .edrv-search-input { flex: 1; min-width: 0; height: 26px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-opts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-search-opt { min-width: 24px; height: 22px; padding: 0 6px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); cursor: pointer; }\n[data-edrv-view] .edrv-search-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-opt.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-border-l2, #d5dde3); }\n[data-edrv-view] .edrv-search-spacer { flex: 1; }\n[data-edrv-view] .edrv-search-summary { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 2px 4px; }\n[data-edrv-view] .edrv-search-results { flex: 1; min-height: 0; overflow: auto; font-size: 12px; }\n[data-edrv-view] .edrv-search-group { margin-bottom: 2px; }\n[data-edrv-view] .edrv-search-file { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-search-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-search-fname { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-search-fdir { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-search-fcount { flex-shrink: 0; min-width: 16px; height: 15px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 15px; text-align: center; }\n[data-edrv-view] .edrv-search-line { display: flex; gap: 6px; padding: 1px 4px 1px 16px; border-radius: 4px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-search-line.alt { background: var(--dsw-alias-bg-layer-1, #ffffff); }\n[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }\n[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: var(--dsw-alias-label-primary-inverted, #1f2933); border-radius: 2px; padding: 0 1px; }\n[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-warn-primary, rgba(240,173,78,.45)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(240,173,78,.12)); color: var(--dsw-alias-state-warn-primary, #b8860b); font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n/* ===== rules panel (sidebar, Codebuddy rules view) ===== */\n[data-edrv-view] .edrv-rules-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-rules-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-tab { height: 24px; padding: 0 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-tab.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-rules-new { margin-left: auto; height: 24px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-new:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-ws-line { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 auto; user-select: none; }\n[data-edrv-view] .edrv-rules-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 0 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-list { flex: 1; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-rules-row { display: flex; align-items: center; gap: 4px; padding: 6px 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; }\n[data-edrv-view] .edrv-rules-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-row.off .edrv-rules-main { opacity: .5; }\n[data-edrv-view] .edrv-rules-main { flex: 1; min-width: 0; cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-rules-line1 { display: flex; align-items: center; gap: 6px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sub { display: flex; align-items: center; gap: 5px; margin-top: 3px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sublabel { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }\n[data-edrv-view] .edrv-rules-type-always { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.15)); color: var(--dsw-alias-state-success-primary, #0b7c46); }\n[data-edrv-view] .edrv-rules-type-auto { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(79,140,255,.16)); color: var(--dsw-alias-link, #3367c9); }\n[data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }\n[data-edrv-view] .edrv-rules-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }\n[data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); display: inline-flex; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: var(--dsw-alias-border-l2, #b9c4cc); cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }\n[data-edrv-view] .edrv-rules-switch.on { background: var(--dsw-alias-brand-primary, #7c5cff); }\n[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--dsw-alias-bg-layer-1, #fff); transition: left .15s; }\n[data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }\n[data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }\n[data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }\n[data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-editor-ctl { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-ctl-label { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select { height: 24px; padding: 0 4px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-desc-input { flex: 1; min-width: 0; }\n[data-edrv-view] .edrv-rules-input { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-body { flex: 1; min-height: 0; resize: none; padding: 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; line-height: 1.5; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }\n\n/* 命令栏(Ctrl+Shift+P / F1):body 级 portal 浮层,故不依赖编辑区几何。\n ⚠️ 浮层 z-index 阶梯(自下而上;改动务必保持顺序,tests/overlayZOrder.test.ts 守约):\n 编辑器内浮层 60(.edrv-search-pop) / 71(.edrv-ctxmenu)\n < 190 命令栏遮罩 < 200 命令栏\n < 195 片段浮窗遮罩 < 197 片段二级弹窗遮罩\n 命令栏必须**高于**片段浮窗:曾因片段遮罩(192) 压过命令栏(191),导致片段浮窗打开时\n 命令栏被整体盖住,按 Ctrl+Shift+P 因「已打开即早退」而毫无反应(表现为快捷键失效)。 */\n[data-edrv-view] .edrv-palette-mask { position: fixed; inset: 0; z-index: 190; background: rgba(0, 0, 0, .28); }\n[data-edrv-view] .edrv-palette { position: fixed; top: 12vh; left: 50%; transform: translateX(-50%); z-index: 200; width: min(580px, calc(100vw - 32px)); max-height: 64vh; display: flex; flex-direction: column; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 12px 34px rgba(31, 41, 51, .24); overflow: hidden; }\n[data-edrv-view] .edrv-palette-input { height: 38px; flex: 0 0 auto; padding: 0 12px; border: none; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: transparent; color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; outline: none; }\n[data-edrv-view] .edrv-palette-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 4px; }\n[data-edrv-view] .edrv-palette-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; }\n[data-edrv-view] .edrv-palette-row.edrv-palette-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 157, 88, .10)); }\n[data-edrv-view] .edrv-palette-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-palette-cat { flex: 0 0 auto; font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-palette-key { flex: 0 0 auto; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 10px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-palette-empty { padding: 14px 10px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #7b8794); text-align: center; }\n[data-edrv-view] .edrv-palette-notice { flex: 0 0 auto; padding: 6px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-state-warn-primary, #b7791f); }\n\n/* 代码片段选择器(配置 / 插入):居中浮窗 + 新建二级弹窗。\n 卡片复用 mcp.css 的 .vsm-mcp-dialog(与「添加 MCP」「工作区选择」同一视觉语言),\n 此处只补遮罩、尺寸与列表布局。⚠️ 根节点须带 data-edrv-view,否则这些规则全部失配。 */\n[data-edrv-view] .edrv-snip-mask { position: fixed; inset: 0; z-index: 195; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, rgba(0, 0, 0, .55)); }\n/* 二级弹窗叠在一级浮窗之上(仍在命令栏之下,见上方阶梯说明) */\n[data-edrv-view] .edrv-snip-mask-top { z-index: 197; }\n[data-edrv-view] .edrv-snip-dialog { display: flex; flex-direction: column; overflow: hidden; width: min(620px, calc(100vw - 48px)); }\n[data-edrv-view] .edrv-snip-hint { margin: 0 0 12px; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-list { display: flex; flex-direction: column; gap: 1px; min-height: 0; max-height: 46vh; overflow: auto; }\n[data-edrv-view] .edrv-snip-group { padding: 12px 2px 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-row { display: flex; align-items: center; gap: 4px; width: 100%; box-sizing: border-box; padding: 2px; border: none; border-radius: 8px; background: transparent; text-align: left; color: var(--dsw-alias-label-primary, #e6e6e6); font: inherit; }\n[data-edrv-view] .edrv-snip-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, .06)); }\n[data-edrv-view] button.edrv-snip-row { cursor: pointer; }\n[data-edrv-view] .edrv-snip-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border: none; border-radius: 6px; background: transparent; text-align: left; cursor: pointer; color: inherit; font: inherit; }\n[data-edrv-view] .edrv-snip-label { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #e6e6e6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-desc { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-src { flex: 0 0 auto; padding-right: 6px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-warn { flex: 0 0 auto; font-size: 11px; padding: 2px 7px; border-radius: 999px; background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .16)); color: var(--dsw-alias-state-warn-primary, #e5b93f); white-space: nowrap; }\n[data-edrv-view] .edrv-snip-act { flex: 0 0 auto; width: 26px; height: 26px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-snip-act:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n[data-edrv-view] .edrv-snip-empty { padding: 12px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n/* 当前文件语言提示条(片段按语言绑定,明确告知生效范围) */\n[data-edrv-view] .edrv-snip-langbar { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; margin: 0 0 10px; padding: 8px 10px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #2b2b2b); font-size: 12px; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-langbar b { color: var(--dsw-alias-label-primary, #e6e6e6); }\n/* 二级弹窗的动作按钮(原生 .vsm-mcp-dialog-actions 已提供排版与按钮样式) */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-dialog-actions { flex-wrap: wrap; }\n/* 删除图标按钮:.vsm-danger 的规则全部限定在 .vsm-mcp-actions button 之下,裸用无样式,此处补自有类 */\n[data-edrv-view] .edrv-snip-act.edrv-snip-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n/* 浮窗内的原生空态(.vsm-mcp-empty 默认为整页尺寸)压缩为对话框尺寸 */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-empty { padding: 26px 16px; border: 0; }\n\n\n\n\n";
35
+ const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 主题:跟随官方 --dsw-alias-* 令牌(DSH 明暗与第三方皮肤生效)。\n 本作用域不再重定义官方令牌(旧实现曾在此硬覆盖浅色盘,导致插件不跟随 DSH 主题);\n 旧环境/缺令牌场景由每条声明的 var(--dsw-alias-*, 回落值) 兜底。\n @author ddj 2026-09-10 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; }\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-star { color: var(--dsw-alias-brand-primary, #4f8cff); font-size: 13px; font-weight: 700; line-height: 1; flex-shrink: 0; } /* 未保存修改星号 @author ddj 2026年09月09号 */\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: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.12)); color: var(--dsw-alias-label-primary, #eee); }\n/* 固定页签:📌 标记 + 免关闭按钮(固定页签不渲染 ×,仅可经右键菜单取消固定) */\n[data-edrv-view] .edrv-tab .edrv-tab-pin { flex-shrink: 0; font-size: 10px; line-height: 1; opacity: .85; }\n[data-edrv-view] .edrv-tab.edrv-tab-pinned { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,.04)); }\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: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress-message { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress { flex: 0 0 auto; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-lsp-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-lsp-status-dot.busy { background: var(--dsw-alias-state-warn-primary, #d97706); animation: edrv-lsp-pulse 1.2s ease-in-out infinite; }\n@keyframes edrv-lsp-pulse { 50% { opacity: .45; } }\n/* Ctrl+hover 可导航标识符下划线提示(绑定 Monaco inlineClassName 装饰) */\n[data-edrv-view] .edrv-nav-underline { text-decoration: underline; text-decoration-color: var(--dsw-alias-interactive-primary, #4fc1ff); text-underline-offset: 1px; cursor: pointer; }\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-chip-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-chip-btn:disabled:hover { background: transparent; }\n[data-edrv-view] .edrv-mn-add-line { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }\n[data-edrv-view] .edrv-mn-gutter-add { color: var(--dsw-alias-state-success-primary, #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: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); border-top: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); border-bottom: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); box-shadow: inset 0 0 0 1px var(--dsw-alias-interactive-bg-hover-danger, 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: var(--dsw-alias-state-error-primary, #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: var(--dsw-alias-state-error-primary, #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-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }\n[data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }\n[data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); }\n[data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-pdf-page { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; min-width: 52px; text-align: center; }\n[data-edrv-view] .edrv-pdf-zoom { min-width: 26px; text-align: center; padding: 3px 7px; }\n[data-edrv-view] .edrv-pdf-sep { width: 1px; height: 16px; background: var(--dsw-alias-border-l1, #e0e6e8); margin: 0 2px; }\n[data-edrv-view] .edrv-pdf-mode-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-brand-primary, #0f9d58); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-pdf-dirty { color: var(--dsw-alias-state-warn-primary, #d97706); border-color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-pdf-stage { position: absolute; top: 34px; left: 0; right: 0; bottom: 0; overflow: auto; }\n[data-edrv-view] .edrv-pdf-stage .edrv-pdf-viewer { margin: 0 auto; }\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: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-state-success-primary, #0f7a45); border: 1px solid var(--dsw-alias-state-success-primary, rgba(15,157,88,.4)); }\n[data-edrv-view] .edrv-pill-undo { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); color: var(--dsw-alias-state-error-primary, #c0392b); border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-pill-ghost { background: var(--dsw-alias-bg-layer-1, #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 页签菜单 11 条 + 键位提示,故限高可滚(ContextMenu 以实测尺寸做 viewport clamp)。\n ⚠️ z-index 71 在既有浮层阶梯内(见文件末尾命令栏注释),不得上调越过命令栏/片段浮窗。 */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 220px; max-width: 360px; max-height: min(70vh, 520px); overflow-y: auto; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-item:disabled,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled { color: var(--dsw-alias-label-disabled, #b0b7c0); cursor: default; background: transparent; }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 菜单项文案(弹性占宽,超出省略)与右侧键位提示(不缩放,等宽字体) */\n[data-edrv-view] .edrv-ctxmenu-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-hint { flex: 0 0 auto; padding-left: 18px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-ctxmenu-item:disabled .edrv-ctxmenu-hint,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled .edrv-ctxmenu-hint { color: var(--dsw-alias-label-disabled, #b0b7c0); }\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: var(--dsw-alias-state-warn-primary, 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: 1; 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; display: inline-flex; align-items: center; justify-content: center; }\n/* 树行图标:官方原语(文件夹/文件类型),尺寸与行高对齐 @author ddj 2026-09-10 */\n[data-edrv-view] .edrv-tree-icon { flex-shrink: 0; display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active .edrv-tree-icon { color: var(--dsw-alias-label-secondary, #52606d); }\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: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-context { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); }\n/* 「在资源管理器视图中显示」的定位高亮(短时淡出,区别于常驻的 active 态) */\n[data-edrv-view] .edrv-tree-row.edrv-tree-reveal { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.18)); animation: edrv-tree-reveal-fade 2s ease-out; }\n@keyframes edrv-tree-reveal-fade { from { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.32)); } to { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.18)); } }\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: var(--dsw-alias-label-primary-inverted, #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 var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* 大纲面板:kind 字形 / detail / 行号 */\n[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== 侧栏形态(betterSidebar 面板内编辑区) ===== */\n[data-edrv-view][data-edrv-layout='side'] { box-sizing: border-box; height: 100%; }\n\n/* ===== 侧边栏引导条(旧页签形态、未装 betterSidebar 时) ===== */\n[data-edrv-view] .edrv-side-hint { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; padding: 6px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-side-hint-text { white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-cmd { font-size: 11px; padding: 2px 6px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #ffffff); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-close { margin-left: auto; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-hint-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== search panel (sidebar, VSCode search view) ===== */\n[data-edrv-view] .edrv-search-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-search-qrow { display: flex; gap: 4px; }\n[data-edrv-view] .edrv-search-input { flex: 1; min-width: 0; height: 26px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-opts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-search-opt { min-width: 24px; height: 22px; padding: 0 6px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); cursor: pointer; }\n[data-edrv-view] .edrv-search-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-opt.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-border-l2, #d5dde3); }\n[data-edrv-view] .edrv-search-spacer { flex: 1; }\n[data-edrv-view] .edrv-search-summary { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 2px 4px; }\n[data-edrv-view] .edrv-search-results { flex: 1; min-height: 0; overflow: auto; font-size: 12px; }\n[data-edrv-view] .edrv-search-group { margin-bottom: 2px; }\n[data-edrv-view] .edrv-search-file { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-search-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-search-fname { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-search-fdir { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-search-fcount { flex-shrink: 0; min-width: 16px; height: 15px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 15px; text-align: center; }\n[data-edrv-view] .edrv-search-line { display: flex; gap: 6px; padding: 1px 4px 1px 16px; border-radius: 4px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-search-line.alt { background: var(--dsw-alias-bg-layer-1, #ffffff); }\n[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }\n[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: var(--dsw-alias-label-primary-inverted, #1f2933); border-radius: 2px; padding: 0 1px; }\n[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-warn-primary, rgba(240,173,78,.45)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(240,173,78,.12)); color: var(--dsw-alias-state-warn-primary, #b8860b); font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n/* ===== rules panel (sidebar, Codebuddy rules view) ===== */\n[data-edrv-view] .edrv-rules-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-rules-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-tab { height: 24px; padding: 0 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-tab.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-rules-new { margin-left: auto; height: 24px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-new:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-ws-line { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 auto; user-select: none; }\n[data-edrv-view] .edrv-rules-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 0 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-list { flex: 1; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-rules-row { display: flex; align-items: center; gap: 4px; padding: 6px 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; }\n[data-edrv-view] .edrv-rules-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-row.off .edrv-rules-main { opacity: .5; }\n[data-edrv-view] .edrv-rules-main { flex: 1; min-width: 0; cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-rules-line1 { display: flex; align-items: center; gap: 6px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sub { display: flex; align-items: center; gap: 5px; margin-top: 3px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sublabel { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }\n[data-edrv-view] .edrv-rules-type-always { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.15)); color: var(--dsw-alias-state-success-primary, #0b7c46); }\n[data-edrv-view] .edrv-rules-type-auto { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(79,140,255,.16)); color: var(--dsw-alias-link, #3367c9); }\n[data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }\n[data-edrv-view] .edrv-rules-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }\n[data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); display: inline-flex; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: var(--dsw-alias-border-l2, #b9c4cc); cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }\n[data-edrv-view] .edrv-rules-switch.on { background: var(--dsw-alias-brand-primary, #7c5cff); }\n[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--dsw-alias-bg-layer-1, #fff); transition: left .15s; }\n[data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }\n[data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }\n[data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }\n[data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-editor-ctl { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-ctl-label { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select { height: 24px; padding: 0 4px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-desc-input { flex: 1; min-width: 0; }\n[data-edrv-view] .edrv-rules-input { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-body { flex: 1; min-height: 0; resize: none; padding: 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; line-height: 1.5; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }\n\n/* 命令栏(Ctrl+Shift+P / F1):body 级 portal 浮层,故不依赖编辑区几何。\n ⚠️ 浮层 z-index 阶梯(自下而上;改动务必保持顺序,tests/overlayZOrder.test.ts 守约):\n 编辑器内浮层 60(.edrv-search-pop) / 71(.edrv-ctxmenu)\n < 190 命令栏遮罩 < 200 命令栏\n < 195 片段浮窗遮罩 < 197 片段二级弹窗遮罩\n 命令栏必须**高于**片段浮窗:曾因片段遮罩(192) 压过命令栏(191),导致片段浮窗打开时\n 命令栏被整体盖住,按 Ctrl+Shift+P 因「已打开即早退」而毫无反应(表现为快捷键失效)。 */\n[data-edrv-view] .edrv-palette-mask { position: fixed; inset: 0; z-index: 190; background: rgba(0, 0, 0, .28); }\n[data-edrv-view] .edrv-palette { position: fixed; top: 12vh; left: 50%; transform: translateX(-50%); z-index: 200; width: min(580px, calc(100vw - 32px)); max-height: 64vh; display: flex; flex-direction: column; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 12px 34px rgba(31, 41, 51, .24); overflow: hidden; }\n[data-edrv-view] .edrv-palette-input { height: 38px; flex: 0 0 auto; padding: 0 12px; border: none; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: transparent; color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; outline: none; }\n[data-edrv-view] .edrv-palette-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 4px; }\n[data-edrv-view] .edrv-palette-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; }\n[data-edrv-view] .edrv-palette-row.edrv-palette-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 157, 88, .10)); }\n[data-edrv-view] .edrv-palette-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-palette-cat { flex: 0 0 auto; font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-palette-key { flex: 0 0 auto; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 10px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-palette-empty { padding: 14px 10px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #7b8794); text-align: center; }\n[data-edrv-view] .edrv-palette-notice { flex: 0 0 auto; padding: 6px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-state-warn-primary, #b7791f); }\n\n/* 代码片段选择器(配置 / 插入):居中浮窗 + 新建二级弹窗。\n 卡片复用 mcp.css 的 .vsm-mcp-dialog(与「添加 MCP」「工作区选择」同一视觉语言),\n 此处只补遮罩、尺寸与列表布局。⚠️ 根节点须带 data-edrv-view,否则这些规则全部失配。 */\n[data-edrv-view] .edrv-snip-mask { position: fixed; inset: 0; z-index: 195; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, rgba(0, 0, 0, .55)); }\n/* 二级弹窗叠在一级浮窗之上(仍在命令栏之下,见上方阶梯说明) */\n[data-edrv-view] .edrv-snip-mask-top { z-index: 197; }\n[data-edrv-view] .edrv-snip-dialog { display: flex; flex-direction: column; overflow: hidden; width: min(620px, calc(100vw - 48px)); }\n[data-edrv-view] .edrv-snip-hint { margin: 0 0 12px; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-list { display: flex; flex-direction: column; gap: 1px; min-height: 0; max-height: 46vh; overflow: auto; }\n[data-edrv-view] .edrv-snip-group { padding: 12px 2px 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-row { display: flex; align-items: center; gap: 4px; width: 100%; box-sizing: border-box; padding: 2px; border: none; border-radius: 8px; background: transparent; text-align: left; color: var(--dsw-alias-label-primary, #e6e6e6); font: inherit; }\n[data-edrv-view] .edrv-snip-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, .06)); }\n[data-edrv-view] button.edrv-snip-row { cursor: pointer; }\n[data-edrv-view] .edrv-snip-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border: none; border-radius: 6px; background: transparent; text-align: left; cursor: pointer; color: inherit; font: inherit; }\n[data-edrv-view] .edrv-snip-label { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #e6e6e6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-desc { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-src { flex: 0 0 auto; padding-right: 6px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-warn { flex: 0 0 auto; font-size: 11px; padding: 2px 7px; border-radius: 999px; background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .16)); color: var(--dsw-alias-state-warn-primary, #e5b93f); white-space: nowrap; }\n[data-edrv-view] .edrv-snip-act { flex: 0 0 auto; width: 26px; height: 26px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-snip-act:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n[data-edrv-view] .edrv-snip-empty { padding: 12px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n/* 当前文件语言提示条(片段按语言绑定,明确告知生效范围) */\n[data-edrv-view] .edrv-snip-langbar { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; margin: 0 0 10px; padding: 8px 10px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #2b2b2b); font-size: 12px; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-langbar b { color: var(--dsw-alias-label-primary, #e6e6e6); }\n/* 二级弹窗的动作按钮(原生 .vsm-mcp-dialog-actions 已提供排版与按钮样式) */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-dialog-actions { flex-wrap: wrap; }\n/* 删除图标按钮:.vsm-danger 的规则全部限定在 .vsm-mcp-actions button 之下,裸用无样式,此处补自有类 */\n[data-edrv-view] .edrv-snip-act.edrv-snip-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n/* 浮窗内的原生空态(.vsm-mcp-empty 默认为整页尺寸)压缩为对话框尺寸 */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-empty { padding: 26px 16px; border: 0; }\n\n\n\n\n";
36
36
  const tagId$1 = "dsh-vscode-mode/editor.css";
37
37
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
38
38
  const tag = document.createElement("style");
@@ -2525,7 +2525,8 @@ window.__ModuleLoader__.load({
2525
2525
  "edrv.showCommands": "Ctrl+Shift+P|F1",
2526
2526
  "edrv.nextEditorRow": "Ctrl+Alt+ArrowDown",
2527
2527
  "edrv.prevEditorRow": "Ctrl+Alt+ArrowUp",
2528
- "edrv.addSelectionRef": "Ctrl+U"
2528
+ "edrv.addSelectionRef": "Ctrl+U",
2529
+ "edrv.closeTab": "Ctrl+F4"
2529
2530
  };
2530
2531
  /**
2531
2532
  * 返回默认键位的独立副本(调用方修改不污染目录)。
@@ -2553,6 +2554,11 @@ window.__ModuleLoader__.load({
2553
2554
  /** Monaco 编辑器实例选择器(限定在插件编辑器行内,排除官方预览等其它 Monaco)。 */
2554
2555
  const EDITOR_MODEL_SELECTOR = ".edrv-editor-row .monaco-editor";
2555
2556
  /**
2557
+ * 已打开文件页签选择器(页签栏在编辑器行内;空态时无此节点)。
2558
+ * 判据同样不依赖 Monaco:图片/PDF 页签没有 Monaco 实例,但仍是「有页签可关」。
2559
+ */
2560
+ const TAB_SELECTOR = ".edrv-editor-row .edrv-tabs .edrv-tab";
2561
+ /**
2556
2562
  * 当前文档是否已有 Monaco 活动编辑器(编辑器打开着文件而非空态)。
2557
2563
  * 判据为插件编辑器行内存在 Monaco 实例:与输入实现(textarea / EditContext)无关,
2558
2564
  * 空态(无文件页签)时 Monaco 未创建 → 返回 false,与旧语义一致。
@@ -2563,6 +2569,17 @@ window.__ModuleLoader__.load({
2563
2569
  if (typeof document === "undefined") return false;
2564
2570
  return document.querySelector(EDITOR_MODEL_SELECTOR) !== null;
2565
2571
  }
2572
+ /**
2573
+ * 当前是否已打开文件页签(「关闭当前页签」等页签级命令的可用性判据)。
2574
+ * 用页签 DOM 而非 Monaco model 判定:图片/PDF 页签没有 Monaco 实例但可以关闭;
2575
+ * 无页签时返回 false,让 Ctrl+F4 之类按键放行给浏览器(与 addSelectionRef「不吞键」同款约定)。
2576
+ * @author ddj 2026年09月11号
2577
+ * @returns 是否存在已打开的文件页签
2578
+ */
2579
+ function hasOpenTabs() {
2580
+ if (typeof document === "undefined") return false;
2581
+ return document.querySelector(TAB_SELECTOR) !== null;
2582
+ }
2566
2583
  //#endregion
2567
2584
  //#region src/client/ui/commandCatalog.ts
2568
2585
  /**
@@ -2641,6 +2658,24 @@ window.__ModuleLoader__.load({
2641
2658
  };
2642
2659
  }
2643
2660
  /**
2661
+ * 关闭当前页签(参考图 Ctrl+F4)。
2662
+ * 可用性按「是否已打开页签」判定而非编辑器模型:图片/PDF 页签无 Monaco 实例但可关闭;
2663
+ * 无页签时判定不可用 → 指令桥放行按键,不吞掉浏览器/系统对 Ctrl+F4 的默认行为。
2664
+ * @author ddj 2026年09月11号
2665
+ * @returns 命令定义
2666
+ */
2667
+ function closeTabDef() {
2668
+ return {
2669
+ id: "edrv.closeTab",
2670
+ label: "关闭当前页签",
2671
+ category: "文件",
2672
+ order: 40,
2673
+ keybinding: "Ctrl+F4",
2674
+ available: hasOpenTabs,
2675
+ run: () => emit("closeTab")
2676
+ };
2677
+ }
2678
+ /**
2644
2679
  * 编辑器内置指令目录(顺序 = 快捷键设置页展示顺序)。
2645
2680
  * 前置 8 条的键位由 EditorView / QuickOpen 自行 capture 监听(历史实现),
2646
2681
  * 故不进 BRIDGE_COMMANDS,避免同一按键双执行。
@@ -2776,7 +2811,8 @@ window.__ModuleLoader__.load({
2776
2811
  const BRIDGE_COMMANDS = [
2777
2812
  nextEditorRowDef(),
2778
2813
  prevEditorRowDef(),
2779
- addSelectionRefDef()
2814
+ addSelectionRefDef(),
2815
+ closeTabDef()
2780
2816
  ];
2781
2817
  /**
2782
2818
  * 命令栏自身的命令(避免循环依赖,由 commandBridge 注入 run 后注册)。
@@ -4246,8 +4282,10 @@ window.__ModuleLoader__.load({
4246
4282
  entries: "edrv.cache.entries.v2.",
4247
4283
  /** 编辑区视图状态(viewStateCache v1)。 */
4248
4284
  viewstate: "edrv.cache.viewstate.v1.",
4249
- /** 编辑器页签/活动文件(EditorView v2)。 */
4250
- editor: "edrv.editor.v2.",
4285
+ /** 编辑器页签/活动文件(EditorView v3:页签带 pinned 固定标记)。 */
4286
+ editor: "edrv.editor.v3.",
4287
+ /** 旧编辑器页签键(v2:页签为纯路径数组)——仅用于一次性回读迁移,不再写入。 */
4288
+ editorLegacy: "edrv.editor.v2.",
4251
4289
  /** 侧边栏状态(EditorView;按布局 side/central 追加段)。 */
4252
4290
  sidebar: "edrv.sidebar.",
4253
4291
  /** 搜索面板条件(SearchPanel v1)。 */
@@ -4402,7 +4440,7 @@ window.__ModuleLoader__.load({
4402
4440
  }
4403
4441
  /** 参与迁移的 localStorage 键前缀(编辑区全部按会话持久化的旧键)。 */
4404
4442
  const MIGRATE_PREFIXES = [
4405
- CACHE_KEY.editor,
4443
+ CACHE_KEY.editorLegacy,
4406
4444
  CACHE_KEY.viewstate,
4407
4445
  CACHE_KEY.sidebar + "side.",
4408
4446
  CACHE_KEY.sidebar,
@@ -4625,19 +4663,128 @@ window.__ModuleLoader__.load({
4625
4663
  //#endregion
4626
4664
  //#region src/client/addToConversation.ts
4627
4665
  /** 把追加引用结果映射为可读文案(ok 用 okText;busy 提示已降级纯文本;unavailable 提示不可用)。
4628
- * @author ddj 2026年09月03号
4666
+ * @author ddj 2026年09月03号 / 2026年09月11号
4629
4667
  * @param outcome 追加结果状态
4630
4668
  * @param okText 成功文案(如「已添加文件引用」/「已添加文件夹引用」)
4631
4669
  * @returns 状态栏/通知用文案
4632
4670
  */
4633
4671
  function statusOfAdd(outcome, okText) {
4634
4672
  if (outcome === "ok") return okText;
4635
- if (outcome === "busy") return okText + "(输入框忙,已降级纯文本)";
4673
+ if (outcome === "busy") return okText + "(已降级纯文本)";
4674
+ if (outcome === "failed") return "添加引用失败(输入框忙或未就绪,请重试)";
4636
4675
  return "无法添加到对话(无会话或输入框不可用)";
4637
4676
  }
4638
4677
  /** DSH reference source 名(dsh-client-ui-reference 注册的 @file/@session 统一源)。 */
4639
4678
  const REF_SOURCE = "reference";
4640
4679
  /**
4680
+ * 把 clipboard 投影的末尾折算成 **detect 投影**的末尾。
4681
+ *
4682
+ * ⚠️ 两个投影对 chip 的计长不同,混用会直接毁掉已插入的引用(真实缺陷,勿改回 `draft.length`):
4683
+ * DSH 的 `$composerLayout()` 中 chip 在 detect 投影里只贡献 **1 个 U+FFFC**,
4684
+ * 在 clipboard 投影里贡献 `clipboardText` 全文
4685
+ * (`pushLeaf('chip', kid, '', kid.getTextContent())`)。
4686
+ * 而 `insertReference(ref, span)` 的 span 走 detect 坐标并经 `selectSpan` 校验:
4687
+ * `span.end > layout.detectLength` 时返回 null → 插入失败。
4688
+ *
4689
+ * 旧实现直接用 `draft.length`(clipboard 长度)当 detect 起点:草稿里一旦有 chip,
4690
+ * 该值就超出 detect 长度 → 插入被拒 → 落入 `setDraft` 兜底,
4691
+ * 而 `setDraft` 会整篇重建并剔除 U+FFFC(`text.replace(REFERENCE_PLACEHOLDER_RE, '')`),
4692
+ * 于是**既有 chip 被销毁、引用退化为重复的纯文本**(即「连续添加多个引用显示异常」)。
4693
+ *
4694
+ * 换算:每个 chip 在 clipboard 中多算了 `length - 1`,逐个扣回即得 detect 末尾。
4695
+ * 无 occurrences(旧版 DSH)时退化为 `draft.length`,与旧行为一致。
4696
+ * @author ddj 2026年09月11号
4697
+ * @param snapshot 输入快照
4698
+ * @returns detect 投影中的文档末尾偏移
4699
+ */
4700
+ function detectEndOf(snapshot) {
4701
+ const total = String(snapshot?.draft ?? "").length;
4702
+ const occurrences = snapshot?.occurrences;
4703
+ if (!Array.isArray(occurrences) || !occurrences.length) return total;
4704
+ let clipped = 0;
4705
+ for (const occurrence of occurrences) {
4706
+ const length = Number(occurrence?.length ?? 0);
4707
+ if (!Number.isFinite(length) || length <= 1) continue;
4708
+ clipped += length - 1;
4709
+ }
4710
+ return Math.max(0, total - clipped);
4711
+ }
4712
+ /** chip 列表按 clipboard 偏移升序(官方已排序,此处防御性归一,丢弃非法项)。 */
4713
+ function chipsOf(snapshot) {
4714
+ const occurrences = snapshot?.occurrences;
4715
+ if (!Array.isArray(occurrences) || !occurrences.length) return [];
4716
+ const out = [];
4717
+ for (const occurrence of occurrences) {
4718
+ const offset = Number(occurrence?.offset ?? NaN);
4719
+ const length = Number(occurrence?.length ?? NaN);
4720
+ if (!Number.isFinite(offset) || !Number.isFinite(length) || length <= 1) continue;
4721
+ out.push({
4722
+ offset: Math.max(0, offset),
4723
+ length
4724
+ });
4725
+ }
4726
+ return out.sort((a, b) => a.offset - b.offset);
4727
+ }
4728
+ /**
4729
+ * detect 偏移 → clipboard 偏移(paddingAt 用:draft 是 clipboard 投影,不能直接按下标取字符)。
4730
+ * 每个位于该偏移之前的 chip 在 clipboard 中多占 `length − 1`,累计补上即可。
4731
+ * @author ddj 2026年09月11号
4732
+ * @param snapshot 输入快照
4733
+ * @param detectOffset detect 投影偏移
4734
+ * @returns clipboard 投影偏移(已夹到 draft 范围内)
4735
+ */
4736
+ function detectToClipboardOf(snapshot, detectOffset) {
4737
+ const total = String(snapshot?.draft ?? "").length;
4738
+ const target = Math.max(0, Math.min(Number(detectOffset) || 0, detectEndOf(snapshot)));
4739
+ let shift = 0;
4740
+ for (const chip of chipsOf(snapshot)) {
4741
+ if (chip.offset - shift >= target) break;
4742
+ shift += chip.length - 1;
4743
+ }
4744
+ return Math.max(0, Math.min(target + shift, total));
4745
+ }
4746
+ /**
4747
+ * 插入点两侧是否需要补空格(仅纯文本通道需要;chip 由 facade 自行补尾随空格)。
4748
+ *
4749
+ * 在 detect 坐标的任意位置求值,故**末尾追加的行为与旧实现逐字符一致**
4750
+ * (末尾前为非空白 → 前后各补一个空格;末尾前已是空白 → 只补尾随空格),
4751
+ * 同时正确支持「插在文本中间」不再贴字。
4752
+ * @author ddj 2026年09月11号
4753
+ * @param snapshot 输入快照
4754
+ * @param detectOffset detect 投影插入点
4755
+ * @returns 是否需要前导/尾随空格
4756
+ */
4757
+ function paddingAt(snapshot, detectOffset) {
4758
+ const draft = String(snapshot?.draft ?? "");
4759
+ const at = detectToClipboardOf(snapshot, detectOffset);
4760
+ const before = at > 0 ? draft.slice(at - 1, at) : "";
4761
+ const after = at < draft.length ? draft.slice(at, at + 1) : "";
4762
+ return {
4763
+ lead: before !== "" && !/\s/.test(before),
4764
+ tail: after === "" || !/\s/.test(after)
4765
+ };
4766
+ }
4767
+ /**
4768
+ * 解析插入点(detect 坐标):优先用输入框**当前光标/选区**(`caretSpan()`,
4769
+ * 官方契约即 detect 坐标,无选区时回落文档末尾),并夹到 `[0, detectLength]`。
4770
+ *
4771
+ * 选区非塌缩时取 `end`(右边界)—— 插入而非替换,绝不删除用户已选中的内容。
4772
+ * caret 缺失/非法(旧版 facade 无该方法、抛异常、NaN 等)一律回落到文档末尾。
4773
+ * @author ddj 2026年09月11号
4774
+ * @param caret caretSpan() 的返回值(可为 null/非法)
4775
+ * @param detectLength detect 投影文档长度
4776
+ * @returns detect 投影插入偏移
4777
+ */
4778
+ function insertOffsetOf(caret, detectLength) {
4779
+ const max = Math.max(0, Number(detectLength) || 0);
4780
+ if (!caret || typeof caret !== "object") return max;
4781
+ const span = caret;
4782
+ const raw = Number(span.end);
4783
+ const value = Number.isFinite(raw) ? raw : Number(span.start);
4784
+ if (!Number.isFinite(value)) return max;
4785
+ return Math.max(0, Math.min(value, max));
4786
+ }
4787
+ /**
4641
4788
  * 生成 DSH @file 语法引用串:cwd 相对化、\ → /、含空白时按 @"path" 语法加引号。
4642
4789
  * cwd 外/无法相对化的路径回退原路径。
4643
4790
  * @author ddj 2026年08月25号
@@ -4706,20 +4853,21 @@ window.__ModuleLoader__.load({
4706
4853
  const conversation = ctx.get("conversation");
4707
4854
  const actx = sessions?.scope?.(sessionId);
4708
4855
  const shell = actx && conversation?.input?.for?.(actx);
4709
- return shell && typeof shell.setDraft === "function" ? shell : void 0;
4856
+ return shell && typeof shell.insertReference === "function" ? shell : void 0;
4710
4857
  } catch {
4711
4858
  return;
4712
4859
  }
4713
4860
  }
4714
- /** 取当前草稿长度与版本;无输入门面时返回 null。
4715
- * @author ddj 2026年08月25号 */
4861
+ /** 取当前草稿快照(长度/版本/chip 列表);无输入门面时返回 null。
4862
+ * @author ddj 2026年08月25号 / 2026年09月11号 */
4716
4863
  function draftCursor(input) {
4717
4864
  try {
4718
4865
  const s = input?.state?.getSnapshot?.();
4719
4866
  if (!s) return null;
4720
4867
  return {
4721
4868
  draft: String(s.draft ?? ""),
4722
- draftRev: Number(s.draftRev ?? 0)
4869
+ draftRev: Number(s.draftRev ?? 0),
4870
+ occurrences: Array.isArray(s.occurrences) ? s.occurrences : void 0
4723
4871
  };
4724
4872
  } catch {
4725
4873
  return null;
@@ -4744,9 +4892,38 @@ window.__ModuleLoader__.load({
4744
4892
  return false;
4745
4893
  }
4746
4894
  }
4895
+ /** 输入门面插入纯文本(官方 plain-text 通道;不存在或异常视作未应用)。
4896
+ * @author ddj 2026年09月11号 */
4897
+ function safeInsertText(input, text, span) {
4898
+ if (typeof input.insertText !== "function") return false;
4899
+ try {
4900
+ return input.insertText(text, span) === true;
4901
+ } catch {
4902
+ return false;
4903
+ }
4904
+ }
4905
+ /**
4906
+ * 读输入框当前光标(detect 坐标);门面缺失/异常返回 null(由调用方回落到文档末尾)。
4907
+ * @author ddj 2026年09月11号
4908
+ * @param input 输入门面
4909
+ * @returns caretSpan 结果或 null
4910
+ */
4911
+ function caretOf(input) {
4912
+ if (typeof input.caretSpan !== "function") return null;
4913
+ try {
4914
+ return input.caretSpan() ?? null;
4915
+ } catch {
4916
+ return null;
4917
+ }
4918
+ }
4747
4919
  /**
4748
4920
  * 创建「添加到对话」动作集(apply 阶段构建一次,随 props 传给 EditorView)。
4749
- * @author ddj 2026年08月25号
4921
+ *
4922
+ * 插入位置 = 输入框**当前光标**(`caretSpan()`,detect 坐标;无光标提示时回落文档末尾),
4923
+ * 不再固定追加到最末尾。坐标一律用 **detect 投影**(见 detectEndOf 的缺陷说明);
4924
+ * 两条写入通道都失败时**不写任何内容**并返回 'failed' —— 旧实现在此处退回 `setDraft`
4925
+ * 整篇重建,会把既有 chip 全部销毁(连续添加多个引用即触发)。
4926
+ * @author ddj 2026年08月25号 / 2026年09月11号
4750
4927
  * @param ctx 客户端服务上下文(sessions + conversation)
4751
4928
  * @returns 动作集
4752
4929
  */
@@ -4757,16 +4934,16 @@ window.__ModuleLoader__.load({
4757
4934
  const cur = draftCursor(input);
4758
4935
  if (!cur) return "unavailable";
4759
4936
  const { reference, mention } = buildFileRef(path, cwdOf(ctx, sessionId), range, appearance);
4760
- if (!safeInsert(input, reference, {
4761
- start: cur.draft.length,
4762
- end: cur.draft.length,
4937
+ const at = insertOffsetOf(caretOf(input), detectEndOf(cur));
4938
+ const span = {
4939
+ start: at,
4940
+ end: at,
4763
4941
  draftRev: cur.draftRev
4764
- })) {
4765
- const gap = cur.draft.length > 0 && !/\s$/.test(cur.draft) ? " " : "";
4766
- input.setDraft(cur.draft + gap + mention + " ");
4767
- return "busy";
4768
- }
4769
- return "ok";
4942
+ };
4943
+ if (safeInsert(input, reference, span)) return "ok";
4944
+ const pad = paddingAt(cur, at);
4945
+ if (safeInsertText(input, (pad.lead ? " " : "") + mention + (pad.tail ? " " : ""), span)) return "busy";
4946
+ return "failed";
4770
4947
  };
4771
4948
  return { appendReference };
4772
4949
  }
@@ -6285,6 +6462,567 @@ window.__ModuleLoader__.load({
6285
6462
  return (0, react_dom.createPortal)(react.default.createElement("div", { "data-edrv-view": "1" }, dialog, createDialog), document.body);
6286
6463
  }
6287
6464
  //#endregion
6465
+ //#region src/client/tabActions.ts
6466
+ /**
6467
+ * 关闭指定集合:活动页签被关时按「右侧优先、否则左侧末位」补位。
6468
+ * @author ddj 2026年09月11号
6469
+ * @param tabs 当前页签
6470
+ * @param closing 待关闭路径集合
6471
+ * @param active 当前活动页签路径
6472
+ * @returns 剩余页签与新的活动页签
6473
+ */
6474
+ function applyClose(tabs, closing, active) {
6475
+ const remaining = tabs.filter((tab) => !closing.has(tab.path));
6476
+ if (remaining.length === tabs.length) return {
6477
+ tabs,
6478
+ active
6479
+ };
6480
+ if (active && !closing.has(active)) return {
6481
+ tabs: remaining,
6482
+ active
6483
+ };
6484
+ return {
6485
+ tabs: remaining,
6486
+ active: pickNeighbor(tabs, remaining, active)
6487
+ };
6488
+ }
6489
+ /**
6490
+ * 关闭其他:保留目标页签与全部固定页签。
6491
+ * @author ddj 2026年09月11号
6492
+ * @param tabs 当前页签
6493
+ * @param target 保留的目标页签路径
6494
+ * @param active 当前活动页签路径
6495
+ * @returns 关闭结果
6496
+ */
6497
+ function closeOthers(tabs, target, active) {
6498
+ return applyClose(tabs, closingExcept(tabs, (tab) => tab.path === target || tab.pinned === true), active);
6499
+ }
6500
+ /**
6501
+ * 关闭右侧:关闭目标之后、未被固定的页签。
6502
+ * @author ddj 2026年09月11号
6503
+ * @param tabs 当前页签
6504
+ * @param target 基准页签路径(其右侧才关)
6505
+ * @param active 当前活动页签路径
6506
+ * @returns 关闭结果
6507
+ */
6508
+ function closeRight(tabs, target, active) {
6509
+ const at = tabs.findIndex((tab) => tab.path === target);
6510
+ const closing = /* @__PURE__ */ new Set();
6511
+ for (let i = at + 1; i > 0 && i < tabs.length; i += 1) if (tabs[i].pinned !== true) closing.add(tabs[i].path);
6512
+ return applyClose(tabs, closing, active);
6513
+ }
6514
+ /**
6515
+ * 关闭已保存:关闭未固定且无未保存修改的页签。
6516
+ * @author ddj 2026年09月11号
6517
+ * @param tabs 当前页签
6518
+ * @param dirty 路径 → 是否有未保存修改
6519
+ * @param active 当前活动页签路径
6520
+ * @returns 关闭结果
6521
+ */
6522
+ function closeSaved(tabs, dirty, active) {
6523
+ return applyClose(tabs, closingExcept(tabs, (tab) => tab.pinned === true || dirty[tab.path] === true), active);
6524
+ }
6525
+ /**
6526
+ * 全部关闭:仅关未固定页签(全部固定时无操作)。
6527
+ * @author ddj 2026年09月11号
6528
+ * @param tabs 当前页签
6529
+ * @param active 当前活动页签路径
6530
+ * @returns 关闭结果
6531
+ */
6532
+ function closeAll(tabs, active) {
6533
+ return applyClose(tabs, closingExcept(tabs, (tab) => tab.pinned === true), active);
6534
+ }
6535
+ /**
6536
+ * 反选可关闭集合(保留 keep 命中的页签,其余进关闭集)。
6537
+ * @author ddj 2026年09月11号
6538
+ * @param tabs 当前页签
6539
+ * @param keep 保留判定
6540
+ * @returns 待关闭路径集合
6541
+ */
6542
+ function closingExcept(tabs, keep) {
6543
+ const closing = /* @__PURE__ */ new Set();
6544
+ for (const tab of tabs) if (!keep(tab)) closing.add(tab.path);
6545
+ return closing;
6546
+ }
6547
+ /**
6548
+ * 选补位页签:先向右找最近的存活页签,再向左找,都没有则取剩余首个。
6549
+ * @author ddj 2026年09月11号
6550
+ * @param tabs 关闭前的页签(用于取原索引)
6551
+ * @param remaining 关闭后剩余的页签
6552
+ * @param active 被关掉的活动页签路径
6553
+ * @returns 补位页签路径;无剩余返回 null
6554
+ */
6555
+ function pickNeighbor(tabs, remaining, active) {
6556
+ const first = remaining[0];
6557
+ if (!first) return null;
6558
+ const at = tabs.findIndex((tab) => tab.path === active);
6559
+ if (at < 0) return first.path;
6560
+ const alive = new Set(remaining.map((tab) => tab.path));
6561
+ for (let i = at + 1; i < tabs.length; i += 1) if (alive.has(tabs[i].path)) return tabs[i].path;
6562
+ for (let i = at - 1; i >= 0; i -= 1) if (alive.has(tabs[i].path)) return tabs[i].path;
6563
+ return first.path;
6564
+ }
6565
+ /**
6566
+ * 切换页签固定态:固定页签整体前移(两侧各自保持相对顺序)。
6567
+ * @author ddj 2026年09月11号
6568
+ * @param tabs 当前页签
6569
+ * @param target 目标页签路径
6570
+ * @returns 重排后的页签(目标不存在时原样返回)
6571
+ */
6572
+ function togglePin(tabs, target) {
6573
+ if (!tabs.some((tab) => tab.path === target)) return tabs;
6574
+ return pinnedFirst(tabs.map((tab) => tab.path === target ? {
6575
+ path: tab.path,
6576
+ pinned: tab.pinned !== true
6577
+ } : tab));
6578
+ }
6579
+ /**
6580
+ * 新增页签:已存在则原样返回;否则插到最后一个固定页签之后。
6581
+ * @author ddj 2026年09月11号
6582
+ * @param tabs 当前页签
6583
+ * @param path 新页签路径
6584
+ * @returns 新页签数组
6585
+ */
6586
+ function insertTab(tabs, path) {
6587
+ if (tabs.some((tab) => tab.path === path)) return tabs;
6588
+ let at = tabs.length;
6589
+ for (let i = tabs.length - 1; i >= 0; i -= 1) {
6590
+ if (tabs[i].pinned !== true) continue;
6591
+ at = i + 1;
6592
+ break;
6593
+ }
6594
+ return tabs.slice(0, at).concat([{ path }], tabs.slice(at));
6595
+ }
6596
+ /**
6597
+ * 固定分区:固定页签在前,两侧各自保持原相对顺序。
6598
+ * @author ddj 2026年09月11号
6599
+ * @param tabs 页签
6600
+ * @returns 重排后的页签
6601
+ */
6602
+ function pinnedFirst(tabs) {
6603
+ return tabs.filter(isPinned).concat(tabs.filter((tab) => !isPinned(tab)));
6604
+ }
6605
+ /**
6606
+ * 是否固定页签。
6607
+ * @author ddj 2026年09月11号
6608
+ * @param tab 页签
6609
+ * @returns 是否固定
6610
+ */
6611
+ function isPinned(tab) {
6612
+ return tab.pinned === true;
6613
+ }
6614
+ /**
6615
+ * 归一化持久化的页签数据:兼容旧版 `string[]`、去重、固定分区。
6616
+ * 损坏项(非字符串/非对象/无 path)直接丢弃。
6617
+ * @author ddj 2026年09月11号
6618
+ * @param raw localStorage 解析结果(任意形状)
6619
+ * @returns 归一化后的页签数组
6620
+ */
6621
+ function normalizeTabs(raw) {
6622
+ if (!Array.isArray(raw)) return [];
6623
+ const seen = /* @__PURE__ */ new Set();
6624
+ const out = [];
6625
+ for (const item of raw) {
6626
+ const tab = tabOf(item);
6627
+ if (!tab || seen.has(tab.path)) continue;
6628
+ seen.add(tab.path);
6629
+ out.push(tab);
6630
+ }
6631
+ return pinnedFirst(out);
6632
+ }
6633
+ /**
6634
+ * 解释单条持久化页签:字符串 = 路径(旧版),对象取 path/pinned。
6635
+ * @author ddj 2026年09月11号
6636
+ * @param item 原始条目
6637
+ * @returns 页签或 null(无法解释)
6638
+ */
6639
+ function tabOf(item) {
6640
+ if (typeof item === "string") return item ? { path: item } : null;
6641
+ if (!item || typeof item !== "object") return null;
6642
+ const raw = item;
6643
+ if (typeof raw.path !== "string" || !raw.path) return null;
6644
+ return raw.pinned === true ? {
6645
+ path: raw.path,
6646
+ pinned: true
6647
+ } : { path: raw.path };
6648
+ }
6649
+ /**
6650
+ * 选活动页签:恢复值仍存在则用它,否则取首个(无页签返回 null)。
6651
+ * @author ddj 2026年09月11号
6652
+ * @param tabs 页签
6653
+ * @param wanted 持久化的活动路径
6654
+ * @returns 活动页签路径
6655
+ */
6656
+ function pickActive(tabs, wanted) {
6657
+ const first = tabs[0];
6658
+ if (!first) return null;
6659
+ if (typeof wanted === "string" && tabs.some((tab) => tab.path === wanted)) return wanted;
6660
+ return first.path;
6661
+ }
6662
+ /** Windows 盘符 / UNC / POSIX 根:视为工作区外的绝对路径。 */
6663
+ const ABSOLUTE_RE = /^(?:[a-z]:[\\/]|\\\\|\/)/i;
6664
+ /**
6665
+ * 是否绝对路径(工作区外)。
6666
+ * @author ddj 2026年09月11号
6667
+ * @param path 路径
6668
+ * @returns 是否绝对路径
6669
+ */
6670
+ function isAbsolutePath(path) {
6671
+ return ABSOLUTE_RE.test(String(path ?? ""));
6672
+ }
6673
+ /**
6674
+ * 是否可在资源管理器视图中定位(工作区相对且不含 `..` 上跳段)。
6675
+ * @author ddj 2026年09月11号
6676
+ * @param path 路径
6677
+ * @returns 是否可定位
6678
+ */
6679
+ function isTreeRevealable(path) {
6680
+ const text = String(path ?? "").trim();
6681
+ if (!text || isAbsolutePath(text)) return false;
6682
+ return !text.replace(/\\/g, "/").split("/").includes("..");
6683
+ }
6684
+ /**
6685
+ * 相对路径:cwd 内路径去掉工作区前缀;工作区外/无 cwd 回退规范化原路径。
6686
+ * @author ddj 2026年09月11号
6687
+ * @param path 路径
6688
+ * @param cwd 会话工作区目录(可空)
6689
+ * @returns 展示/复制用的相对路径
6690
+ */
6691
+ function relativeOf(path, cwd) {
6692
+ const target = normalizeSlashes(path);
6693
+ const base = normalizeSlashes(cwd).replace(/\/+$/, "");
6694
+ if (base && target.toLowerCase().startsWith((base + "/").toLowerCase())) return target.slice(base.length + 1);
6695
+ return target;
6696
+ }
6697
+ /**
6698
+ * 绝对路径:相对路径按 cwd 拼接;已是绝对路径或没有 cwd 时回退规范化原路径。
6699
+ * @author ddj 2026年09月11号
6700
+ * @param path 路径
6701
+ * @param cwd 会话工作区目录(可空)
6702
+ * @returns 展示/复制用的绝对路径
6703
+ */
6704
+ function absoluteOf(path, cwd) {
6705
+ const target = normalizeSlashes(path);
6706
+ if (isAbsolutePath(target)) return target;
6707
+ const base = normalizeSlashes(cwd).replace(/\/+$/, "");
6708
+ return base ? base + "/" + target : target;
6709
+ }
6710
+ /**
6711
+ * 反斜杠归一为斜杠(页签/引用/剪贴板统一用正斜杠,与既有 mentionOf 口径一致)。
6712
+ * @author ddj 2026年09月11号
6713
+ * @param path 路径
6714
+ * @returns 正斜杠路径
6715
+ */
6716
+ function normalizeSlashes(path) {
6717
+ return String(path ?? "").replace(/\\/g, "/");
6718
+ }
6719
+ /**
6720
+ * 文件路径的祖先目录(由浅到深):`a/b/c.ts` → `['a', 'a/b']`。
6721
+ * 根级文件、绝对路径与含 `..` 的路径返回空数组。
6722
+ * @author ddj 2026年09月11号
6723
+ * @param path 工作区相对路径
6724
+ * @returns 祖先目录相对路径数组
6725
+ */
6726
+ function ancestorDirsOf(path) {
6727
+ if (!isTreeRevealable(path)) return [];
6728
+ const segs = normalizeSlashes(path).split("/").filter(Boolean);
6729
+ const out = [];
6730
+ for (let i = 1; i < segs.length; i += 1) {
6731
+ const prev = segs[i - 1];
6732
+ out.push(out.length ? out[out.length - 1] + "/" + prev : prev);
6733
+ }
6734
+ return out;
6735
+ }
6736
+ /**
6737
+ * 文件名(页签/状态栏展示与 tooltip 用)。
6738
+ * @author ddj 2026年09月11号
6739
+ * @param path 路径
6740
+ * @returns 末段文件名;空路径返回空串
6741
+ */
6742
+ function baseNameOf(path) {
6743
+ return normalizeSlashes(path).split("/").filter(Boolean).pop() ?? "";
6744
+ }
6745
+ //#endregion
6746
+ //#region src/client/tabMenu.ts
6747
+ /**
6748
+ * dsh-vscode-mode client — 文件页签右键菜单模型(纯函数,可单测)。
6749
+ * 菜单是「数据」而非 JSX:条目顺序、分组分隔线、禁用规则与键位提示全部在此收敛,
6750
+ * EditorView 只把结果映射成 ContextMenu 的 entries 并派发动作。
6751
+ *
6752
+ * 与参考图(VS Code / CodeBuddy 页签右键菜单)的对齐口径:
6753
+ * 保留其分组顺序(对话 / 关闭族 / 路径 / 定位 / 固定),**省略**本架构无法实现的
6754
+ * 「向右拆分 / Split & Move / 移动到新窗口 / 复制到新窗口」(浏览器内单编辑器实例)。
6755
+ * 只显示**真实已绑定**的键位;参考图里的两步弦(Ctrl+K W 等)引擎不支持,故不伪造。
6756
+ *
6757
+ * 「固定 = 保护」:关闭其他 / 关闭右侧 / 关闭已保存 / 全部关闭 一律不关固定页签,
6758
+ * 故这些条目在「固定页签是唯一可关对象」时判定为禁用(与 tabActions 语义同源)。
6759
+ * 作者 ddj 2026年09月11号
6760
+ */
6761
+ /**
6762
+ * 构建页签右键菜单条目。
6763
+ * @author ddj 2026年09月11号
6764
+ * @param state 菜单状态快照
6765
+ * @returns 菜单条目(已按参考图分组排序)
6766
+ */
6767
+ function buildTabMenu(state) {
6768
+ const { path, tabs, active, dirty, cwd, hasSession, canAddToConversation, closeChord } = state;
6769
+ const pinned = tabs.find((tab) => tab.path === path)?.pinned === true;
6770
+ return [
6771
+ {
6772
+ id: "add-to-conversation",
6773
+ label: "添加到对话",
6774
+ disabled: !(hasSession && canAddToConversation)
6775
+ },
6776
+ {
6777
+ id: "close",
6778
+ label: "关闭",
6779
+ separator: true,
6780
+ ...closeChord ? { hint: closeChord } : {}
6781
+ },
6782
+ {
6783
+ id: "close-others",
6784
+ label: "关闭其他",
6785
+ disabled: nothingToClose(closeOthers(tabs, path, active), tabs)
6786
+ },
6787
+ {
6788
+ id: "close-right",
6789
+ label: "关闭右侧标签页",
6790
+ disabled: nothingToClose(closeRight(tabs, path, active), tabs)
6791
+ },
6792
+ {
6793
+ id: "close-saved",
6794
+ label: "关闭已保存",
6795
+ disabled: nothingToClose(closeSaved(tabs, dirty, active), tabs)
6796
+ },
6797
+ {
6798
+ id: "close-all",
6799
+ label: "全部关闭",
6800
+ disabled: nothingToClose(closeAll(tabs, active), tabs)
6801
+ },
6802
+ {
6803
+ id: "copy-path",
6804
+ label: "复制路径",
6805
+ separator: true
6806
+ },
6807
+ {
6808
+ id: "copy-relative-path",
6809
+ label: "复制相对路径",
6810
+ disabled: !hasCwd(cwd)
6811
+ },
6812
+ {
6813
+ id: "reveal-in-os",
6814
+ label: "在文件资源管理器中显示",
6815
+ separator: true,
6816
+ disabled: !hasSession
6817
+ },
6818
+ {
6819
+ id: "reveal-in-view",
6820
+ label: "在资源管理器视图中显示",
6821
+ disabled: !isTreeRevealable(path)
6822
+ },
6823
+ {
6824
+ id: "toggle-pinned",
6825
+ label: pinned ? "取消固定" : "固定",
6826
+ separator: true
6827
+ }
6828
+ ];
6829
+ }
6830
+ /**
6831
+ * 关闭族是否无可关对象(结果页签数与关闭前一致 = 一个也没关掉)。
6832
+ * @author ddj 2026年09月11号
6833
+ * @param result 关闭结果
6834
+ * @param tabs 关闭前的页签
6835
+ * @returns 是否无可关对象
6836
+ */
6837
+ function nothingToClose(result, tabs) {
6838
+ return result.tabs.length >= tabs.length;
6839
+ }
6840
+ /**
6841
+ * 是否有可用工作区目录(复制相对路径需要)。
6842
+ * @author ddj 2026年09月11号
6843
+ * @param cwd 会话工作区目录
6844
+ * @returns 是否可用
6845
+ */
6846
+ function hasCwd(cwd) {
6847
+ return typeof cwd === "string" && cwd.trim() !== "";
6848
+ }
6849
+ //#endregion
6850
+ //#region src/client/saveDebounce.ts
6851
+ /**
6852
+ * 创建防抖保存槽。
6853
+ * @author ddj 2026年09月11号
6854
+ * @returns 保存槽句柄
6855
+ */
6856
+ function createSaveTimer() {
6857
+ let pending = null;
6858
+ return {
6859
+ arm(schedule, delay, run) {
6860
+ if (pending) {
6861
+ pending.cancel?.();
6862
+ pending = null;
6863
+ }
6864
+ const cancel = schedule(() => {
6865
+ pending = null;
6866
+ run();
6867
+ }, delay);
6868
+ pending = {
6869
+ cancel: typeof cancel === "function" ? cancel : void 0,
6870
+ run
6871
+ };
6872
+ },
6873
+ flush() {
6874
+ const slot = pending;
6875
+ if (!slot) return;
6876
+ pending = null;
6877
+ slot.cancel?.();
6878
+ slot.run();
6879
+ },
6880
+ cancel() {
6881
+ const slot = pending;
6882
+ if (!slot) return;
6883
+ pending = null;
6884
+ slot.cancel?.();
6885
+ },
6886
+ isPending() {
6887
+ return pending !== null;
6888
+ }
6889
+ };
6890
+ }
6891
+ //#endregion
6892
+ //#region src/client/ui/menuPosition.ts
6893
+ /**
6894
+ * 将菜单坐标限制在 viewport 内。
6895
+ * @author ddj 2026年08月28号
6896
+ * @param x 期望的 viewport x 坐标
6897
+ * @param y 期望的 viewport y 坐标
6898
+ * @param viewportWidth viewport 宽度
6899
+ * @param viewportHeight viewport 高度
6900
+ * @param menuWidth 菜单估算宽度
6901
+ * @param menuHeight 菜单估算高度
6902
+ * @returns 限制后的菜单位置
6903
+ */
6904
+ function clampMenuPosition(x, y, viewportWidth, viewportHeight, menuWidth, menuHeight) {
6905
+ const maxLeft = Math.max(4, viewportWidth - menuWidth);
6906
+ const maxTop = Math.max(4, viewportHeight - menuHeight);
6907
+ return {
6908
+ left: Math.max(4, Math.min(x, maxLeft)),
6909
+ top: Math.max(4, Math.min(y, maxTop))
6910
+ };
6911
+ }
6912
+ //#endregion
6913
+ //#region src/client/ui/ContextMenu.ts
6914
+ /**
6915
+ * dsh-vscode-mode client — 通用浮动右键菜单(createElement 风格、类型化)。
6916
+ * 非阻塞式关闭:window capture 监听(点击菜单外/右键菜单外/Esc 关闭),
6917
+ * 不拦截菜单外事件,允许同一事件内由发起方切换/重开菜单。
6918
+ * 条目支持 danger/disabled/separator,复用既有 edrv-ctxmenu* 样式类。
6919
+ * 作者 ddj 2026-08-27
6920
+ */
6921
+ /** 估算宽高(实测前的回退值;条目数变化大时以实测为准,见 useLayoutEffect)。 */
6922
+ const MENU_W = 224;
6923
+ const MENU_H = 176;
6924
+ /**
6925
+ * 把条目表展开为渲染行:`separator` 是**前置分隔线**,条目本身仍然渲染。
6926
+ *
6927
+ * ⚠️ 旧实现把 `separator` 当成「本条是分隔线」而 `continue` 跳过条目本身 ——
6928
+ * 因历史调用方都没用过该字段,缺陷一直潜伏;页签菜单首次使用后表现为
6929
+ * 「关闭 / 复制路径 / 在文件资源管理器中显示 / 固定」四条主条目整条消失。
6930
+ * 首条带 separator 时不渲染分隔线(菜单顶部不应有横线,与参考图一致)。
6931
+ * @author ddj 2026年09月11号
6932
+ * @param entries 菜单项列表
6933
+ * @returns 渲染行列表
6934
+ */
6935
+ function menuRows(entries) {
6936
+ const rows = [];
6937
+ for (const entry of entries) {
6938
+ if (entry.separator && rows.length) rows.push({
6939
+ kind: "sep",
6940
+ entry
6941
+ });
6942
+ rows.push({
6943
+ kind: "item",
6944
+ entry
6945
+ });
6946
+ }
6947
+ return rows;
6948
+ }
6949
+ /**
6950
+ * 浮动右键菜单。
6951
+ * @param props.x 视口 x 坐标
6952
+ * @param props.y 视口 y 坐标
6953
+ * @param props.entries 菜单项列表
6954
+ * @param props.onClose 关闭回调(backdrop 点击/右键/Esc/点击项后触发)
6955
+ */
6956
+ function ContextMenu(props) {
6957
+ const { x, y, entries, onClose } = props;
6958
+ const menuRef = react.default.useRef(null);
6959
+ const [size, setSize] = react.default.useState(null);
6960
+ react.default.useEffect(() => {
6961
+ const onKey = (e) => {
6962
+ if (e.key === "Escape") onClose();
6963
+ };
6964
+ const onPointerDown = (e) => {
6965
+ if (e.button !== 0) return;
6966
+ if (menuRef.current && menuRef.current.contains(e.target)) return;
6967
+ onClose();
6968
+ };
6969
+ const onContextMenu = (e) => {
6970
+ if (menuRef.current && menuRef.current.contains(e.target)) e.preventDefault();
6971
+ onClose();
6972
+ };
6973
+ window.addEventListener("keydown", onKey, true);
6974
+ window.addEventListener("pointerdown", onPointerDown, true);
6975
+ window.addEventListener("contextmenu", onContextMenu, true);
6976
+ return () => {
6977
+ window.removeEventListener("keydown", onKey, true);
6978
+ window.removeEventListener("pointerdown", onPointerDown, true);
6979
+ window.removeEventListener("contextmenu", onContextMenu, true);
6980
+ };
6981
+ }, [onClose]);
6982
+ react.default.useLayoutEffect(() => {
6983
+ const el = menuRef.current;
6984
+ if (!el) return;
6985
+ const w = el.offsetWidth;
6986
+ const h = el.offsetHeight;
6987
+ if (!w || !h) return;
6988
+ setSize((prev) => prev && prev.w === w && prev.h === h ? prev : {
6989
+ w,
6990
+ h
6991
+ });
6992
+ }, [entries]);
6993
+ const position = clampMenuPosition(Number.isFinite(x) ? x : 4, Number.isFinite(y) ? y : 4, window.innerWidth || 800, window.innerHeight || 600, size?.w ?? MENU_W, size?.h ?? MENU_H);
6994
+ const children = [];
6995
+ for (const row of menuRows(entries)) {
6996
+ if (row.kind === "sep") {
6997
+ children.push(react.default.createElement("div", {
6998
+ key: "sep-" + row.entry.id,
6999
+ className: "edrv-ctxmenu-sep"
7000
+ }));
7001
+ continue;
7002
+ }
7003
+ const entry = row.entry;
7004
+ const cls = "edrv-ctxmenu-item" + (entry.danger ? " edrv-ctxmenu-danger" : "") + (entry.disabled ? " edrv-ctxmenu-disabled" : "");
7005
+ children.push(react.default.createElement("button", {
7006
+ key: entry.id,
7007
+ className: cls,
7008
+ disabled: entry.disabled,
7009
+ title: entry.hint ? entry.label + " (" + entry.hint + ")" : entry.label,
7010
+ onClick: () => {
7011
+ if (!entry.disabled) {
7012
+ entry.onClick?.();
7013
+ onClose();
7014
+ }
7015
+ }
7016
+ }, react.default.createElement("span", { className: "edrv-ctxmenu-label" }, entry.label), entry.hint ? react.default.createElement("span", { className: "edrv-ctxmenu-hint" }, entry.hint) : null));
7017
+ }
7018
+ const overlay = react.default.createElement("div", { "data-edrv-view": "1" }, react.default.createElement("div", {
7019
+ className: "edrv-ctxmenu",
7020
+ ref: menuRef,
7021
+ style: position
7022
+ }, ...children));
7023
+ return typeof document === "undefined" ? overlay : (0, react_dom.createPortal)(overlay, document.body);
7024
+ }
7025
+ //#endregion
6288
7026
  //#region src/client/ui/EditorView.ts
6289
7027
  /**
6290
7028
  * dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
@@ -6388,7 +7126,8 @@ window.__ModuleLoader__.load({
6388
7126
  const modelsRef = react.default.useRef(null);
6389
7127
  if (!modelsRef.current) modelsRef.current = modelsForScope(scope);
6390
7128
  const viewStatesRef = react.default.useRef({});
6391
- const saveTimerRef = react.default.useRef(null);
7129
+ const saveTimerRef = react.default.useRef(createSaveTimer());
7130
+ const savingRef = react.default.useRef(/* @__PURE__ */ new Set());
6392
7131
  const loadSeqRef = react.default.useRef(0);
6393
7132
  const programmaticRef = react.default.useRef(false);
6394
7133
  const restoredScopeRef = react.default.useRef(null);
@@ -6404,6 +7143,10 @@ window.__ModuleLoader__.load({
6404
7143
  const rowNavMoveRef = react.default.useRef(false);
6405
7144
  const activeRef = react.default.useRef(null);
6406
7145
  activeRef.current = active;
7146
+ const tabsRef = react.default.useRef([]);
7147
+ tabsRef.current = tabs;
7148
+ const dirtyRef = react.default.useRef({});
7149
+ dirtyRef.current = dirtyMap;
6407
7150
  const tabsHostRef = react.default.useRef(null);
6408
7151
  const [navTick, setNavTick] = react.default.useState(0);
6409
7152
  const hoverRegionsRef = react.default.useRef([]);
@@ -6452,10 +7195,7 @@ window.__ModuleLoader__.load({
6452
7195
  }, [pendingRegions]);
6453
7196
  const sum = react.default.useMemo(() => summarize(Object.values(records)), [records]);
6454
7197
  const addTab = (path, select) => {
6455
- setTabs((prev) => {
6456
- if (prev.some((t) => t.path === path)) return prev;
6457
- return prev.concat([{ path }]);
6458
- });
7198
+ setTabs((prev) => insertTab(prev, path));
6459
7199
  if (select) setActive(path);
6460
7200
  };
6461
7201
  /**
@@ -6581,9 +7321,14 @@ window.__ModuleLoader__.load({
6581
7321
  };
6582
7322
  navBackRef.current = navBack;
6583
7323
  navForwardRef.current = navForward;
6584
- const closeTab = (path) => {
6585
- flushSave();
6586
- if (path === active) {
7324
+ /**
7325
+ * 关闭页签的收尾(不动作状态):活动页签先存视图状态与导航历史;
7326
+ * PDF 页签销毁面板控制器并清 base64 缓存(未保存注释随之丢弃,脏点已提示)。
7327
+ * @author ddj 2026年09月11号
7328
+ * @param path 待关闭页签路径
7329
+ */
7330
+ const releaseTab = (path) => {
7331
+ if (path === activeRef.current) {
6587
7332
  saveViewState(path);
6588
7333
  recordNav();
6589
7334
  }
@@ -6593,13 +7338,44 @@ window.__ModuleLoader__.load({
6593
7338
  pdfCtlRef.current.delete(path);
6594
7339
  }
6595
7340
  pdfB64CacheRef.current.delete(path);
6596
- setTabs((prev) => {
6597
- const idx = prev.findIndex((t) => t.path === path);
6598
- if (idx < 0) return prev;
6599
- const next = prev.filter((t) => t.path !== path);
6600
- if (active === path) setActive(next[idx] ? next[idx].path : next[idx - 1] ? next[idx - 1].path : null);
6601
- return next;
6602
- });
7341
+ };
7342
+ /**
7343
+ * 提交关闭结果:一次性更新页签与活动页签(补位规则见 tabActions.applyClose)。
7344
+ * @author ddj 2026年09月11号
7345
+ * @param result tabActions 产出的关闭结果
7346
+ */
7347
+ const commitClose = (result) => {
7348
+ setTabs((prev) => prev === result.tabs ? prev : result.tabs);
7349
+ if (result.active !== activeRef.current) setActive(result.active);
7350
+ };
7351
+ /**
7352
+ * 立即关闭页签(页签 × 按钮与「关闭」菜单项共用;批量关闭走 closeTabs)。
7353
+ * @author ddj 2026年08月25号 / 2026年09月11号
7354
+ * @param path 待关闭页签路径
7355
+ */
7356
+ const closeTab = (path) => {
7357
+ flushSave();
7358
+ persistDirty([path]);
7359
+ releaseTab(path);
7360
+ commitClose(applyClose(tabsRef.current, /* @__PURE__ */ new Set([path]), activeRef.current));
7361
+ };
7362
+ /**
7363
+ * 批量关闭:先落盘全部待关闭的脏页签,再逐个收尾并一次性提交。
7364
+ * @author ddj 2026年09月11号
7365
+ * @param result 关闭结果(来自 tabActions 的 closeOthers/closeRight/closeSaved/closeAll)
7366
+ * @param label 状态栏文案前缀(如「已关闭其他」)
7367
+ */
7368
+ const closeTabs = (result, label) => {
7369
+ flushSave();
7370
+ const closing = tabsRef.current.filter((t) => !result.tabs.some((r) => r.path === t.path));
7371
+ if (!closing.length) {
7372
+ setStatus("无可关闭的页签");
7373
+ return;
7374
+ }
7375
+ persistDirty(closing.map((t) => t.path));
7376
+ for (const tab of closing) releaseTab(tab.path);
7377
+ commitClose(result);
7378
+ setStatus(label + "(" + closing.length + " 个)");
6603
7379
  };
6604
7380
  /**
6605
7381
  * 在已打开页签间循环切换(文件分页归编辑器自带页签栏)。
@@ -6994,12 +7770,13 @@ window.__ModuleLoader__.load({
6994
7770
  modelsRef.current = modelsForScope(scope);
6995
7771
  viewStatesRef.current = viewStatesLoad(scope);
6996
7772
  try {
6997
- const raw = localStorage.getItem(CACHE_KEY.editor + String(scope));
7773
+ const raw = localStorage.getItem(CACHE_KEY.editor + String(scope)) ?? localStorage.getItem(CACHE_KEY.editorLegacy + String(scope));
6998
7774
  if (raw) {
6999
7775
  const saved = JSON.parse(raw);
7000
- if (Array.isArray(saved.tabs) && saved.tabs.length) {
7001
- setTabs(saved.tabs.map((p) => ({ path: p })));
7002
- if (typeof saved.active === "string") setActive(saved.active);
7776
+ const restored = normalizeTabs(saved?.tabs);
7777
+ if (restored.length) {
7778
+ setTabs(restored);
7779
+ setActive(pickActive(restored, saved?.active));
7003
7780
  }
7004
7781
  }
7005
7782
  } catch (e) {}
@@ -7016,10 +7793,14 @@ window.__ModuleLoader__.load({
7016
7793
  react.default.useEffect(() => {
7017
7794
  if (!sessionId) return;
7018
7795
  try {
7019
- localStorage.setItem(CACHE_KEY.editor + String(scope), JSON.stringify({
7020
- tabs: tabs.map((t) => t.path),
7796
+ const payload = {
7797
+ tabs: tabs.map((t) => t.pinned ? {
7798
+ path: t.path,
7799
+ pinned: true
7800
+ } : { path: t.path }),
7021
7801
  active
7022
- }));
7802
+ };
7803
+ localStorage.setItem(CACHE_KEY.editor + String(scope), JSON.stringify(payload));
7023
7804
  } catch (e) {}
7024
7805
  }, [
7025
7806
  tabs,
@@ -7172,6 +7953,7 @@ window.__ModuleLoader__.load({
7172
7953
  ["edrv.command.navigateForward", () => navForwardRef.current?.()],
7173
7954
  ["edrv.command.nextTab", () => cycleTabRef.current?.(1)],
7174
7955
  ["edrv.command.prevTab", () => cycleTabRef.current?.(-1)],
7956
+ ["edrv.command.closeTab", () => closeActiveTab()],
7175
7957
  ["edrv.command.nextEditorRow", () => moveRow(1)],
7176
7958
  ["edrv.command.prevEditorRow", () => moveRow(-1)],
7177
7959
  ["edrv.command.goToDefinition", () => {
@@ -7287,14 +8069,6 @@ window.__ModuleLoader__.load({
7287
8069
  setStatus("加载中…");
7288
8070
  loadContent(active, sessionId);
7289
8071
  }, [active, sessionId]);
7290
- react.default.useEffect(() => {
7291
- if (!tabMenu) return;
7292
- const onKey = (e) => {
7293
- if (e.key === "Escape") dismissMenus();
7294
- };
7295
- window.addEventListener("keydown", onKey, true);
7296
- return () => window.removeEventListener("keydown", onKey, true);
7297
- }, [tabMenu]);
7298
8072
  react.default.useEffect(() => {
7299
8073
  if (monaco || monacoErr) return;
7300
8074
  let alive = true;
@@ -7354,11 +8128,14 @@ window.__ModuleLoader__.load({
7354
8128
  }
7355
8129
  return model;
7356
8130
  };
8131
+ /**
8132
+ * 提交待执行的防抖保存(**真正执行保存**,不是取消)。
8133
+ * 语义与缺陷背景见 saveDebounce.ts:`schedule` 的返回值是只 clearTimeout 的 disposer,
8134
+ * 旧实现把它当「立即保存」调用 → 防抖窗口内切页签/关闭文件会静默丢改动。
8135
+ * @author ddj 2026年09月11号
8136
+ */
7357
8137
  const flushSave = () => {
7358
- if (saveTimerRef.current) {
7359
- saveTimerRef.current();
7360
- saveTimerRef.current = null;
7361
- }
8138
+ saveTimerRef.current?.flush();
7362
8139
  };
7363
8140
  const doSave = (silent) => {
7364
8141
  if (!active) return;
@@ -7383,6 +8160,7 @@ window.__ModuleLoader__.load({
7383
8160
  }
7384
8161
  const text = ed.getValue();
7385
8162
  if (!silent) setStatus("保存中…");
8163
+ savingRef.current.add(active);
7386
8164
  rpc("edrv.save", {
7387
8165
  sessionId,
7388
8166
  path: active,
@@ -7403,15 +8181,53 @@ window.__ModuleLoader__.load({
7403
8181
  }).catch((e) => {
7404
8182
  setStatus("保存失败");
7405
8183
  setError("保存异常:" + String(e));
8184
+ }).finally(() => {
8185
+ savingRef.current.delete(active);
7406
8186
  });
7407
8187
  };
7408
8188
  doSaveRef.current = doSave;
8189
+ /**
8190
+ * 关闭前落盘:把「待关闭且仍标脏」的页签静默保存。
8191
+ *
8192
+ * 调用方须先 `flushSave()`:它已把活动页签的待执行保存真正提交(见 saveDebounce 的缺陷说明)。
8193
+ * 这里再处理**仍标脏**的页签 —— 包括活动页签(其保存可能在途或失败),
8194
+ * 用 model 里的当前文本补一次,保证关闭前内容一定写到磁盘。
8195
+ * 保存失败只保留脏标记(不丢用户编辑),并在状态栏给出提示。
8196
+ * @author ddj 2026年09月11号
8197
+ * @param paths 即将关闭的页签路径
8198
+ */
8199
+ const persistDirty = (paths) => {
8200
+ for (const path of paths) {
8201
+ if (!dirtyRef.current[path]) continue;
8202
+ if (savingRef.current.has(path)) continue;
8203
+ const pdfCtl = pdfCtlRef.current.get(path);
8204
+ if (pdfCtl) {
8205
+ if (pdfCtl.isDirty()) pdfCtl.savePdf();
8206
+ continue;
8207
+ }
8208
+ const model = modelsRef.current.get(path);
8209
+ if (!model) {
8210
+ setStatus("未保存的修改无法落盘:" + baseNameOf(path));
8211
+ continue;
8212
+ }
8213
+ const content = model.getValue();
8214
+ savingRef.current.add(path);
8215
+ rpc("edrv.save", {
8216
+ sessionId,
8217
+ path,
8218
+ content
8219
+ }).then((res) => {
8220
+ if (res && res.ok) setDirtyMap((d) => Object.assign({}, d, { [path]: false }));
8221
+ }).catch((e) => dbg(sessionId, "关闭前落盘失败:" + path + " · " + String(e))).finally(() => {
8222
+ savingRef.current.delete(path);
8223
+ });
8224
+ }
8225
+ };
7409
8226
  const onEdit = () => {
7410
8227
  if (!editorRef.current || !active) return;
7411
8228
  setDirtyMap((d) => Object.assign({}, d, { [active]: true }));
7412
8229
  setStatus("编辑中…");
7413
- if (saveTimerRef.current) saveTimerRef.current();
7414
- saveTimerRef.current = schedule(() => doSave(true), 700);
8230
+ saveTimerRef.current?.arm(schedule, 700, () => doSave(true));
7415
8231
  };
7416
8232
  onEditRef.current = onEdit;
7417
8233
  react.default.useEffect(() => {
@@ -8033,6 +8849,97 @@ window.__ModuleLoader__.load({
8033
8849
  openInExplorer
8034
8850
  };
8035
8851
  /**
8852
+ * 复制文本到剪贴板(状态栏反馈;浏览器拒绝时提示,不抛异常)。
8853
+ * @author ddj 2026年09月11号
8854
+ * @param text 待复制文本
8855
+ * @param okText 成功文案
8856
+ */
8857
+ const copyText = (text, okText) => {
8858
+ const value = String(text ?? "");
8859
+ if (!value) {
8860
+ setStatus("无可复制内容");
8861
+ return;
8862
+ }
8863
+ if (!navigator.clipboard?.writeText) {
8864
+ setStatus("剪贴板不可用");
8865
+ return;
8866
+ }
8867
+ navigator.clipboard.writeText(value).then(() => setStatus(okText + ":" + value)).catch(() => setStatus("复制失败(浏览器拒绝剪贴板写入)"));
8868
+ };
8869
+ /**
8870
+ * 在资源管理器视图中显示:展开侧栏文件树到目标文件并高亮。
8871
+ * 工作区外文件(片段/全局规则)无树节点,判定为不可定位并给出提示。
8872
+ * @author ddj 2026年09月11号
8873
+ * @param path 工作区相对路径
8874
+ */
8875
+ const revealTabInView = (path) => {
8876
+ if (!isTreeRevealable(path)) {
8877
+ setStatus("该文件不在工作区内,无法在资源管理器中定位");
8878
+ return;
8879
+ }
8880
+ setSidebarOn(true);
8881
+ setActivePanel("explorer");
8882
+ window.dispatchEvent(new CustomEvent("edrv:reveal-path", { detail: { path } }));
8883
+ setStatus("已在资源管理器中定位:" + baseNameOf(path));
8884
+ };
8885
+ /**
8886
+ * 切换页签固定态(固定页签整体前移;固定后隐藏 ×,仅可经菜单取消固定)。
8887
+ * @author ddj 2026年09月11号
8888
+ * @param path 目标页签路径
8889
+ */
8890
+ const toggleTabPin = (path) => {
8891
+ const target = tabsRef.current.find((t) => t.path === path);
8892
+ if (!target) return;
8893
+ setTabs((prev) => togglePin(prev, path));
8894
+ setStatus(target.pinned ? "已取消固定:" + baseNameOf(path) : "已固定:" + baseNameOf(path));
8895
+ };
8896
+ /** 关闭当前活动页签(Ctrl+F4 与菜单动作共用;无页签时提示)。 */
8897
+ const closeActiveTab = () => {
8898
+ if (!activeRef.current) {
8899
+ setStatus("无打开的文件");
8900
+ return;
8901
+ }
8902
+ closeTab(activeRef.current);
8903
+ };
8904
+ /**
8905
+ * 页签右键菜单动作表(按菜单条目 id 分派;全部读 ref 取最新状态,防陈旧闭包)。
8906
+ * @author ddj 2026年09月11号
8907
+ * @param path 右键目标页签路径
8908
+ * @returns id → 动作
8909
+ */
8910
+ const tabMenuActions = (path) => ({
8911
+ "add-to-conversation": () => addRefToChat(path),
8912
+ close: () => closeTab(path),
8913
+ "close-others": () => closeTabs(closeOthers(tabsRef.current, path, activeRef.current), "已关闭其他页签"),
8914
+ "close-right": () => closeTabs(closeRight(tabsRef.current, path, activeRef.current), "已关闭右侧页签"),
8915
+ "close-saved": () => closeTabs(closeSaved(tabsRef.current, dirtyRef.current, activeRef.current), "已关闭已保存页签"),
8916
+ "close-all": () => closeTabs(closeAll(tabsRef.current, activeRef.current), "已关闭全部页签"),
8917
+ "copy-path": () => copyText(absoluteOf(path, cwd), "已复制路径"),
8918
+ "copy-relative-path": () => copyText(relativeOf(path, cwd), "已复制相对路径"),
8919
+ "reveal-in-os": () => openInExplorer(path),
8920
+ "reveal-in-view": () => revealTabInView(path),
8921
+ "toggle-pinned": () => toggleTabPin(path)
8922
+ });
8923
+ /**
8924
+ * 构建页签右键菜单条目(数据来自 tabMenu 纯函数;此处只注入键位与可用性快照)。
8925
+ * @author ddj 2026年09月11号
8926
+ * @returns ContextMenu 的 entries
8927
+ */
8928
+ const tabMenuEntries = () => {
8929
+ if (!tabMenu) return [];
8930
+ const actions = tabMenuActions(tabMenu.path);
8931
+ return buildTabMenu({
8932
+ path: tabMenu.path,
8933
+ tabs: tabsRef.current,
8934
+ active: activeRef.current,
8935
+ dirty: dirtyRef.current,
8936
+ cwd,
8937
+ hasSession: Boolean(sessionId),
8938
+ canAddToConversation: Boolean(addToConversation),
8939
+ closeChord: chordOf("edrv.closeTab")
8940
+ }).map((entry) => Object.assign({}, entry, { onClick: actions[entry.id] }));
8941
+ };
8942
+ /**
8036
8943
  * 在光标处按片段语法展开插入(Monaco 原生 snippet 控制器解析 ${1:占位} 与 $TM_* 变量)。
8037
8944
  * @author ddj 2026年09月10号
8038
8945
  * @param entry 片段条目
@@ -8125,8 +9032,8 @@ window.__ModuleLoader__.load({
8125
9032
  }
8126
9033
  }, tabs.map((t) => react.default.createElement("div", {
8127
9034
  key: t.path,
8128
- className: "edrv-tab" + (t.path === active ? " edrv-tab-active" : ""),
8129
- title: t.path,
9035
+ className: "edrv-tab" + (t.path === active ? " edrv-tab-active" : "") + (t.pinned ? " edrv-tab-pinned" : ""),
9036
+ title: t.pinned ? t.path + "(已固定)" : t.path,
8130
9037
  onClick: () => {
8131
9038
  if (t.path !== active) {
8132
9039
  flushSave();
@@ -8144,15 +9051,20 @@ window.__ModuleLoader__.load({
8144
9051
  path: t.path
8145
9052
  });
8146
9053
  }
8147
- }, react.default.createElement("span", { style: {
9054
+ }, t.pinned ? react.default.createElement("span", {
9055
+ className: "edrv-tab-pin",
9056
+ title: "已固定",
9057
+ "aria-label": "已固定"
9058
+ }, "📌") : null, react.default.createElement("span", { style: {
8148
9059
  overflow: "hidden",
8149
9060
  textOverflow: "ellipsis",
8150
9061
  maxWidth: 180
8151
9062
  } }, t.path.split(/[\\/]/).pop() || t.path), dirtyMap[t.path] ? react.default.createElement("span", {
8152
9063
  className: "edrv-tab-star",
8153
9064
  title: "未保存修改"
8154
- }, "*") : null, react.default.createElement("span", {
9065
+ }, "*") : null, t.pinned ? null : react.default.createElement("span", {
8155
9066
  className: "edrv-tab-x",
9067
+ title: "关闭",
8156
9068
  onClick: (e) => {
8157
9069
  e.stopPropagation();
8158
9070
  closeTab(t.path);
@@ -8414,42 +9326,13 @@ window.__ModuleLoader__.load({
8414
9326
  setLauncherOpen(false);
8415
9327
  }
8416
9328
  })) : null;
8417
- /**
8418
- * 计算右键菜单固定定位(clamp 防越出视口)。
8419
- * @author ddj 2026年08月25号
8420
- */
8421
- const menuPos = (x, y) => ({
8422
- left: Math.max(4, Math.min(x, (window.innerWidth || 800) - 224)),
8423
- top: Math.max(4, Math.min(y, (window.innerHeight || 600) - 176))
8424
- });
8425
- const menuBackdrop = tabMenu ? react.default.createElement("div", {
8426
- style: {
8427
- position: "fixed",
8428
- inset: 0,
8429
- zIndex: 70
8430
- },
8431
- onClick: dismissMenus,
8432
- onContextMenu: (e) => {
8433
- e.preventDefault();
8434
- dismissMenus();
8435
- }
9329
+ const tabMenuEl = tabMenu ? react.default.createElement(ContextMenu, {
9330
+ key: "edrv-tab-menu",
9331
+ x: tabMenu.x,
9332
+ y: tabMenu.y,
9333
+ entries: tabMenuEntries(),
9334
+ onClose: dismissMenus
8436
9335
  }) : null;
8437
- const tabMenuEl = tabMenu ? react.default.createElement("div", {
8438
- className: "edrv-ctxmenu",
8439
- style: menuPos(tabMenu.x, tabMenu.y)
8440
- }, react.default.createElement("button", {
8441
- className: "edrv-ctxmenu-item",
8442
- onClick: () => {
8443
- addRefToChat(tabMenu.path);
8444
- dismissMenus();
8445
- }
8446
- }, "添加文件到对话"), react.default.createElement("div", { className: "edrv-ctxmenu-sep" }), react.default.createElement("button", {
8447
- className: "edrv-ctxmenu-item edrv-ctxmenu-danger",
8448
- onClick: () => {
8449
- closeTab(tabMenu.path);
8450
- dismissMenus();
8451
- }
8452
- }, "关闭标签页")) : null;
8453
9336
  const snippetPickerEl = snippetPicker ? react.default.createElement(SnippetsPicker, {
8454
9337
  mode: snippetPicker,
8455
9338
  sessionId,
@@ -8543,14 +9426,14 @@ window.__ModuleLoader__.load({
8543
9426
  "data-edrv-layout": "side",
8544
9427
  className: "edrv-view-side",
8545
9428
  style: Object.assign({}, baseStyle, { height: "100%" })
8546
- }, editorRow, menuBackdrop, tabMenuEl, paletteEl, snippetPickerEl) : react.default.createElement("div", {
9429
+ }, editorRow, tabMenuEl, paletteEl, snippetPickerEl) : react.default.createElement("div", {
8547
9430
  ref: viewRootRef,
8548
9431
  "data-edrv-view": "1",
8549
9432
  style: Object.assign({}, baseStyle, {
8550
9433
  height: "var(--edrv-editor-height, 100%)",
8551
9434
  maxHeight: "var(--edrv-editor-height, 100%)"
8552
9435
  })
8553
- }, editorRow, menuBackdrop, tabMenuEl, paletteEl, snippetPickerEl);
9436
+ }, editorRow, tabMenuEl, paletteEl, snippetPickerEl);
8554
9437
  }
8555
9438
  //#endregion
8556
9439
  //#region src/client/ui/DiffBadge.ts
@@ -11895,7 +12778,8 @@ window.__ModuleLoader__.load({
11895
12778
  },
11896
12779
  openSession: (id) => service("sessions").open(id),
11897
12780
  reference: async (sessionId, path, appearance) => {
11898
- return await add.appendReference(sessionId, path, void 0, appearance) !== "unavailable";
12781
+ const outcome = await add.appendReference(sessionId, path, void 0, appearance);
12782
+ return outcome === "ok" || outcome === "busy";
11899
12783
  },
11900
12784
  choose: (title, folder) => chooseWorkspace(title, folder),
11901
12785
  openEditor: (path, line, column) => {
@@ -12281,101 +13165,6 @@ window.__ModuleLoader__.load({
12281
13165
  };
12282
13166
  }
12283
13167
  //#endregion
12284
- //#region src/client/ui/menuPosition.ts
12285
- /**
12286
- * 将菜单坐标限制在 viewport 内。
12287
- * @author ddj 2026年08月28号
12288
- * @param x 期望的 viewport x 坐标
12289
- * @param y 期望的 viewport y 坐标
12290
- * @param viewportWidth viewport 宽度
12291
- * @param viewportHeight viewport 高度
12292
- * @param menuWidth 菜单估算宽度
12293
- * @param menuHeight 菜单估算高度
12294
- * @returns 限制后的菜单位置
12295
- */
12296
- function clampMenuPosition(x, y, viewportWidth, viewportHeight, menuWidth, menuHeight) {
12297
- const maxLeft = Math.max(4, viewportWidth - menuWidth);
12298
- const maxTop = Math.max(4, viewportHeight - menuHeight);
12299
- return {
12300
- left: Math.max(4, Math.min(x, maxLeft)),
12301
- top: Math.max(4, Math.min(y, maxTop))
12302
- };
12303
- }
12304
- //#endregion
12305
- //#region src/client/ui/ContextMenu.ts
12306
- /**
12307
- * dsh-vscode-mode client — 通用浮动右键菜单(createElement 风格、类型化)。
12308
- * 非阻塞式关闭:window capture 监听(点击菜单外/右键菜单外/Esc 关闭),
12309
- * 不拦截菜单外事件,允许同一事件内由发起方切换/重开菜单。
12310
- * 条目支持 danger/disabled/separator,复用既有 edrv-ctxmenu* 样式类。
12311
- * 作者 ddj 2026-08-27
12312
- */
12313
- /** 菜单估算宽高(clamp 防越出视口,与 EditorView menuPos 常量对齐)。 */
12314
- const MENU_W = 224;
12315
- const MENU_H = 176;
12316
- /**
12317
- * 浮动右键菜单。
12318
- * @param props.x 视口 x 坐标
12319
- * @param props.y 视口 y 坐标
12320
- * @param props.entries 菜单项列表
12321
- * @param props.onClose 关闭回调(backdrop 点击/右键/Esc/点击项后触发)
12322
- */
12323
- function ContextMenu(props) {
12324
- const { x, y, entries, onClose } = props;
12325
- const menuRef = react.default.useRef(null);
12326
- react.default.useEffect(() => {
12327
- const onKey = (e) => {
12328
- if (e.key === "Escape") onClose();
12329
- };
12330
- const onPointerDown = (e) => {
12331
- if (e.button !== 0) return;
12332
- if (menuRef.current && menuRef.current.contains(e.target)) return;
12333
- onClose();
12334
- };
12335
- const onContextMenu = (e) => {
12336
- if (menuRef.current && menuRef.current.contains(e.target)) e.preventDefault();
12337
- onClose();
12338
- };
12339
- window.addEventListener("keydown", onKey, true);
12340
- window.addEventListener("pointerdown", onPointerDown, true);
12341
- window.addEventListener("contextmenu", onContextMenu, true);
12342
- return () => {
12343
- window.removeEventListener("keydown", onKey, true);
12344
- window.removeEventListener("pointerdown", onPointerDown, true);
12345
- window.removeEventListener("contextmenu", onContextMenu, true);
12346
- };
12347
- }, [onClose]);
12348
- const position = clampMenuPosition(Number.isFinite(x) ? x : 4, Number.isFinite(y) ? y : 4, window.innerWidth || 800, window.innerHeight || 600, MENU_W, MENU_H);
12349
- const children = [];
12350
- for (const entry of entries) {
12351
- if (entry.separator) {
12352
- children.push(react.default.createElement("div", {
12353
- key: "sep-" + entry.id,
12354
- className: "edrv-ctxmenu-sep"
12355
- }));
12356
- continue;
12357
- }
12358
- const cls = "edrv-ctxmenu-item" + (entry.danger ? " edrv-ctxmenu-danger" : "") + (entry.disabled ? " edrv-ctxmenu-disabled" : "");
12359
- children.push(react.default.createElement("button", {
12360
- key: entry.id,
12361
- className: cls,
12362
- disabled: entry.disabled,
12363
- onClick: () => {
12364
- if (!entry.disabled) {
12365
- entry.onClick?.();
12366
- onClose();
12367
- }
12368
- }
12369
- }, entry.label));
12370
- }
12371
- const overlay = react.default.createElement("div", { "data-edrv-view": "1" }, react.default.createElement("div", {
12372
- className: "edrv-ctxmenu",
12373
- ref: menuRef,
12374
- style: position
12375
- }, ...children));
12376
- return typeof document === "undefined" ? overlay : (0, react_dom.createPortal)(overlay, document.body);
12377
- }
12378
- //#endregion
12379
13168
  //#region src/client/sidebar/contextMenu.ts
12380
13169
  /** 校验菜单项并写入注册表(缺 id/run/label 抛 TypeError)。 */
12381
13170
  function itemRegister(entries, notify, item) {
@@ -12706,6 +13495,10 @@ window.__ModuleLoader__.load({
12706
13495
  ".pnpm",
12707
13496
  ".pnpm-store"
12708
13497
  ]);
13498
+ /** 「在资源管理器视图中显示」高亮时长与定位重试上限(目录懒加载需等待行渲染)。 */
13499
+ const REVEAL_HIGHLIGHT_MS = 2e3;
13500
+ const REVEAL_RETRY_MAX = 6;
13501
+ const REVEAL_RETRY_MS = 120;
12709
13502
  /**
12710
13503
  * 目录图标元素(官方 IconFolderOpen16/Close16)。
12711
13504
  * @author ddj 2026年09月10号
@@ -12759,6 +13552,15 @@ window.__ModuleLoader__.load({
12759
13552
  return react.default.createElement(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 14 });
12760
13553
  }
12761
13554
  /**
13555
+ * 转义 CSS 属性选择器的值(路径含引号/反斜杠时避免选择器语法错误)。
13556
+ * @author ddj 2026年09月11号
13557
+ * @param value 原始值
13558
+ * @returns 可安全嵌入 `[attr="…"]` 的字符串
13559
+ */
13560
+ function cssEscape(value) {
13561
+ return String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
13562
+ }
13563
+ /**
12762
13564
  * 目录树面板主体(SWR:有缓存先渲染,无缓存才显示加载态;加载总在后台)。
12763
13565
  * @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath/fileMenuItems/notify)
12764
13566
  */
@@ -12775,6 +13577,7 @@ window.__ModuleLoader__.load({
12775
13577
  const [loading, setLoading] = react.default.useState({});
12776
13578
  const [errors, setErrors] = react.default.useState({});
12777
13579
  const [menu, setMenu] = react.default.useState(null);
13580
+ const [revealPath, setRevealPath] = react.default.useState(null);
12778
13581
  const tokensRef = react.default.useRef({});
12779
13582
  const expandedRef = react.default.useRef({});
12780
13583
  expandedRef.current = expanded;
@@ -12785,6 +13588,10 @@ window.__ModuleLoader__.load({
12785
13588
  const saveTimerRef = react.default.useRef(null);
12786
13589
  const loadDirRef = react.default.useRef(null);
12787
13590
  const refreshRef = react.default.useRef(null);
13591
+ const revealTimerRef = react.default.useRef(null);
13592
+ const revealTryRef = react.default.useRef(0);
13593
+ const revealInTreeRef = react.default.useRef(null);
13594
+ const treeRef = react.default.useRef(null);
12788
13595
  /** 渲染取数:内存态 → 本地条目缓存 → null(显示加载态)。 */
12789
13596
  const entriesOf = (rel) => dirsRef.current[rel] ?? entriesCacheGet(scope, rel) ?? null;
12790
13597
  /** 预取子目录:≤4 个、排除重型目录、缓存新鲜跳过、已加载跳过、不级联。 */
@@ -12850,6 +13657,45 @@ window.__ModuleLoader__.load({
12850
13657
  setExpanded((prev) => Object.assign({}, prev, { [rel]: true }));
12851
13658
  loadDir(rel, { prefetch: true });
12852
13659
  };
13660
+ /**
13661
+ * 尝试把目标行滚入可见并高亮;行尚未渲染(祖先目录仍在加载)时按上限重试。
13662
+ * @author ddj 2026年09月11号
13663
+ * @param path 目标相对路径
13664
+ */
13665
+ const tryReveal = (path) => {
13666
+ const row = treeRef.current?.querySelector?.("[data-edrv-path=\"" + cssEscape(path) + "\"]");
13667
+ if (!row) {
13668
+ revealTryRef.current += 1;
13669
+ if (revealTryRef.current < REVEAL_RETRY_MAX) window.setTimeout(() => tryReveal(path), REVEAL_RETRY_MS);
13670
+ return;
13671
+ }
13672
+ row.scrollIntoView?.({ block: "center" });
13673
+ setRevealPath(path);
13674
+ if (revealTimerRef.current) clearTimeout(revealTimerRef.current);
13675
+ revealTimerRef.current = window.setTimeout(() => {
13676
+ revealTimerRef.current = null;
13677
+ setRevealPath(null);
13678
+ }, REVEAL_HIGHLIGHT_MS);
13679
+ };
13680
+ /**
13681
+ * 在资源管理器视图中显示:展开全部祖先目录并定位高亮目标文件。
13682
+ * 目录条目懒加载,故先展开祖先并触发加载,再由 tryReveal 轮询等行渲染。
13683
+ * @author ddj 2026年09月11号
13684
+ * @param path 目标相对路径
13685
+ */
13686
+ const revealInTree = (path) => {
13687
+ if (!path) return;
13688
+ const ancestors = ancestorDirsOf(path);
13689
+ revealTryRef.current = 0;
13690
+ setExpanded((prev) => {
13691
+ const next = Object.assign({}, prev);
13692
+ for (const dir of ancestors) next[dir] = true;
13693
+ return next;
13694
+ });
13695
+ for (const dir of ancestors) loadDir(dir, { prefetch: false });
13696
+ tryReveal(path);
13697
+ };
13698
+ revealInTreeRef.current = revealInTree;
12853
13699
  const refresh = () => {
12854
13700
  const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
12855
13701
  tokensRef.current = {};
@@ -12879,6 +13725,7 @@ window.__ModuleLoader__.load({
12879
13725
  setErrors({});
12880
13726
  setMenu(null);
12881
13727
  setRoot(null);
13728
+ setRevealPath(null);
12882
13729
  const cached = scope ? explorerLoad(scope) : null;
12883
13730
  const restored = cached?.expanded ?? [];
12884
13731
  if (cached?.root) setRoot(cached.root);
@@ -12920,6 +13767,20 @@ window.__ModuleLoader__.load({
12920
13767
  window.addEventListener("edrv:refresh", onRefresh);
12921
13768
  return () => window.removeEventListener("edrv:refresh", onRefresh);
12922
13769
  }, []);
13770
+ react.default.useEffect(() => {
13771
+ const onReveal = (event) => {
13772
+ const path = event?.detail?.path;
13773
+ if (typeof path === "string" && path) revealInTreeRef.current?.(path);
13774
+ };
13775
+ window.addEventListener("edrv:reveal-path", onReveal);
13776
+ return () => {
13777
+ window.removeEventListener("edrv:reveal-path", onReveal);
13778
+ if (revealTimerRef.current) {
13779
+ clearTimeout(revealTimerRef.current);
13780
+ revealTimerRef.current = null;
13781
+ }
13782
+ };
13783
+ }, []);
12923
13784
  react.default.useEffect(() => {
12924
13785
  if (!scope) return;
12925
13786
  const timer = setInterval(() => {
@@ -12937,8 +13798,9 @@ window.__ModuleLoader__.load({
12937
13798
  const dim = e.type === "other";
12938
13799
  return react.default.createElement("div", {
12939
13800
  key: e.path,
12940
- className: "edrv-tree-row" + (active ? " edrv-tree-active" : "") + (contextTarget ? " edrv-tree-context" : ""),
13801
+ className: "edrv-tree-row" + (active ? " edrv-tree-active" : "") + (contextTarget ? " edrv-tree-context" : "") + (revealPath === e.path ? " edrv-tree-reveal" : ""),
12941
13802
  title: e.path,
13803
+ "data-edrv-path": e.path,
12942
13804
  style: { paddingLeft: 6 + depth * 14 },
12943
13805
  onClick: () => {
12944
13806
  if (isDir) toggle(e.path);
@@ -13014,7 +13876,10 @@ window.__ModuleLoader__.load({
13014
13876
  className: "edrv-side-btn",
13015
13877
  title: "刷新目录树",
13016
13878
  onClick: refresh
13017
- }, refreshIconEl())), react.default.createElement("div", { className: "edrv-tree" }, errorText ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(errorText)), react.default.createElement("button", {
13879
+ }, refreshIconEl())), react.default.createElement("div", {
13880
+ className: "edrv-tree",
13881
+ ref: treeRef
13882
+ }, errorText ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(errorText)), react.default.createElement("button", {
13018
13883
  className: "edrv-side-btn",
13019
13884
  onClick: () => refresh()
13020
13885
  }, "重试")) : null, rowsOf("", 0)), menu && menuEntries.length ? react.default.createElement(ContextMenu, {