dsh-vscode-mode 0.3.1 → 0.3.3

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/* 键盘高亮项(↑↓ 选择):与 hover 同特异性但声明在后,故选中态稳定可辨 @author ddj 2026年09月11号 */\n[data-edrv-view] .edrv-search-item.edrv-search-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.14)); }\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 后注册)。
@@ -3025,16 +3061,41 @@ window.__ModuleLoader__.load({
3025
3061
  return version;
3026
3062
  }
3027
3063
  //#endregion
3064
+ //#region src/client/ui/searchNav.ts
3065
+ /**
3066
+ * dsh-vscode-mode client — 候选列表键盘导航纯函数。
3067
+ * 从搜索浮窗(QuickOpen)的按键处理中拆出,便于单测;不依赖 React 与 DOM。
3068
+ * 导航语义:步进越界**循环**(末位 ↓ 回首位、首位 ↑ 到末位),与命令栏
3069
+ * (CommandPalette 的 (min+step) % len)保持一致。
3070
+ * 作者 ddj 2026年09月11号
3071
+ */
3072
+ /**
3073
+ * 计算候选列表的下一选中下标(循环;任何非法输入都收敛到合法下标)。
3074
+ * 入参与返回值恒在 `[0, length - 1]`(`length <= 0` 时返回 0)。
3075
+ * 作者 ddj 2026年09月11号
3076
+ * @param current 当前选中下标(越界/非有限值按首位处理)
3077
+ * @param delta 步进(+1 下一项 / -1 上一项)
3078
+ * @param length 候选总数
3079
+ * @returns 步进后的选中下标;无候选时返回 0
3080
+ */
3081
+ function stepIndex(current, delta, length) {
3082
+ if (!Number.isFinite(length) || length <= 0) return 0;
3083
+ const size = Math.floor(length);
3084
+ return ((Math.min(Math.max(Number.isFinite(current) ? Math.floor(current) : 0, 0), size - 1) + (Number.isFinite(delta) ? Math.trunc(delta) : 0)) % size + size) % size;
3085
+ }
3086
+ //#endregion
3028
3087
  //#region src/client/ui/QuickOpen.ts
3029
3088
  /**
3030
3089
  * dsh-vscode-mode client — QuickOpen:顶部搜索框(Ctrl+P 打开文件)。
3031
3090
  * 快捷键随配置(edrv.quickOpen),占位文案同步当前键位。
3091
+ * 候选浮窗支持鼠标点击与键盘导航(↑↓ 切换高亮、Enter 打开高亮项、Esc 关闭);
3092
+ * 输入法组词中不消费按键(中文候选词确认优先)。
3032
3093
  * 迁移自原 src/client/index.ts 的 QuickOpen,语义不改。
3033
- * 作者 ddj 2026-08-20 / 2026-08-26
3094
+ * 作者 ddj 2026-08-20 / 2026-08-26 / 2026年09月11号
3034
3095
  */
3035
3096
  /**
3036
3097
  * 快速打开搜索框:Ctrl+P 聚焦,输入 ≥2 字符经 edrv.searchFiles 搜索,
3037
- * 结果列表点击/回车打开文件。
3098
+ * 结果列表点击 / ↑↓ 选择 + 回车打开高亮项。
3038
3099
  * @param props.sessionId 会话 id
3039
3100
  * @param props.onOpen 打开回调(path)
3040
3101
  */
@@ -3044,10 +3105,16 @@ window.__ModuleLoader__.load({
3044
3105
  const [q, setQ] = react.default.useState("");
3045
3106
  const [results, setResults] = react.default.useState(null);
3046
3107
  const [open, setOpen] = react.default.useState(false);
3108
+ const [active, setActive] = react.default.useState(0);
3047
3109
  const inputRef = react.default.useRef(null);
3110
+ const popRef = react.default.useRef(null);
3111
+ const listId = react.default.useId();
3048
3112
  const seq = react.default.useRef(0);
3049
3113
  const timer = react.default.useRef(null);
3050
3114
  useKeybindingsVersion();
3115
+ const list = Array.isArray(results) ? results : [];
3116
+ const showPop = open && Array.isArray(results) && q.trim().length >= 2;
3117
+ const activeIdx = list.length ? Math.min(active, list.length - 1) : 0;
3051
3118
  const search = (query) => {
3052
3119
  const s = ++seq.current;
3053
3120
  const clean = String(query || "").trim();
@@ -3067,6 +3134,7 @@ window.__ModuleLoader__.load({
3067
3134
  };
3068
3135
  const onChange = (v) => {
3069
3136
  setQ(v);
3137
+ setActive(0);
3070
3138
  setOpen(true);
3071
3139
  if (timer.current) clearTimeout(timer.current);
3072
3140
  timer.current = setTimeout(() => search(v), 180);
@@ -3075,6 +3143,7 @@ window.__ModuleLoader__.load({
3075
3143
  onOpen(p);
3076
3144
  setQ("");
3077
3145
  setResults(null);
3146
+ setActive(0);
3078
3147
  setOpen(false);
3079
3148
  inputRef.current?.blur?.();
3080
3149
  };
@@ -3100,34 +3169,79 @@ window.__ModuleLoader__.load({
3100
3169
  window.removeEventListener("edrv.command.quickOpen", openBox);
3101
3170
  };
3102
3171
  }, []);
3172
+ react.default.useEffect(() => {
3173
+ if (!showPop) return;
3174
+ const node = popRef.current?.querySelector?.(".edrv-search-item.edrv-search-sel");
3175
+ if (typeof node?.scrollIntoView === "function") node.scrollIntoView({ block: "nearest" });
3176
+ }, [
3177
+ showPop,
3178
+ activeIdx,
3179
+ list.length
3180
+ ]);
3103
3181
  const dirText = (p) => {
3104
3182
  const base = String(p).split(/[\\/]/).pop() || String(p);
3105
3183
  return String(p).slice(0, Math.max(0, String(p).length - base.length)).replace(/[\\/]+$/, "") || String(p);
3106
3184
  };
3185
+ /**
3186
+ * 输入框按键:浮窗有候选时消费 ↑↓/Enter,其余情况放行(保留原生光标行为)。
3187
+ * 输入法组词中一律放行——中文候选词的 Enter 确认不得被当成「打开文件」。
3188
+ * @author ddj 2026年09月11号
3189
+ * @param e 输入框键盘事件
3190
+ */
3191
+ const onInputKey = (e) => {
3192
+ if (e.nativeEvent?.isComposing) return;
3193
+ if (e.key === "Escape") {
3194
+ setOpen(false);
3195
+ return;
3196
+ }
3197
+ if (!showPop || !list.length) return;
3198
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
3199
+ e.preventDefault();
3200
+ setActive((v) => stepIndex(v, e.key === "ArrowDown" ? 1 : -1, list.length));
3201
+ return;
3202
+ }
3203
+ if (e.key === "Enter") {
3204
+ e.preventDefault();
3205
+ pick(list[activeIdx]);
3206
+ }
3207
+ };
3107
3208
  let pop = null;
3108
- if (open && Array.isArray(results) && q.trim().length >= 2) {
3109
- if (results.length) {
3110
- const items = results.map((p) => react.default.createElement("div", {
3209
+ if (showPop) {
3210
+ if (list.length) {
3211
+ const items = list.map((p, i) => react.default.createElement("div", {
3111
3212
  key: p,
3112
- className: "edrv-search-item",
3213
+ id: listId + "-" + i,
3214
+ role: "option",
3215
+ "aria-selected": i === activeIdx,
3216
+ className: "edrv-search-item" + (i === activeIdx ? " edrv-search-sel" : ""),
3217
+ onMouseEnter: () => setActive(i),
3113
3218
  onClick: () => pick(p)
3114
3219
  }, react.default.createElement("span", { className: "n" }, String(p).split(/[\\/]/).pop() || p), react.default.createElement("span", { className: "d" }, dirText(p))));
3115
- pop = react.default.createElement("div", { className: "edrv-search-pop" }, ...items);
3116
- } else pop = react.default.createElement("div", { className: "edrv-search-pop" }, react.default.createElement("div", { className: "edrv-search-empty" }, "无匹配文件"));
3220
+ pop = react.default.createElement("div", {
3221
+ ref: popRef,
3222
+ id: listId + "-list",
3223
+ className: "edrv-search-pop",
3224
+ role: "listbox"
3225
+ }, ...items);
3226
+ } else pop = react.default.createElement("div", {
3227
+ ref: popRef,
3228
+ className: "edrv-search-pop"
3229
+ }, react.default.createElement("div", { className: "edrv-search-empty" }, "无匹配文件"));
3117
3230
  }
3118
3231
  return react.default.createElement("div", { className: "edrv-search-wrap" }, react.default.createElement("input", {
3119
3232
  ref: inputRef,
3120
3233
  className: "edrv-search",
3121
3234
  placeholder: "搜索文件 (" + (chordOf("edrv.quickOpen") ?? "Ctrl+P") + ")",
3122
3235
  value: q,
3236
+ role: "combobox",
3237
+ "aria-expanded": showPop && list.length > 0,
3238
+ "aria-controls": showPop && list.length ? listId + "-list" : void 0,
3239
+ "aria-activedescendant": showPop && list.length ? listId + "-" + activeIdx : void 0,
3123
3240
  onChange: (e) => onChange(e.target.value),
3124
3241
  onFocus: () => {
3125
3242
  if (q.trim().length >= 2 && !results) setOpen(true);
3126
3243
  },
3127
- onKeyDown: (e) => {
3128
- if (e.key === "Enter" && Array.isArray(results) && results.length) pick(results[0]);
3129
- if (e.key === "Escape") setOpen(false);
3130
- }
3244
+ onKeyDown: onInputKey
3131
3245
  }), pop);
3132
3246
  }
3133
3247
  //#endregion
@@ -4246,8 +4360,10 @@ window.__ModuleLoader__.load({
4246
4360
  entries: "edrv.cache.entries.v2.",
4247
4361
  /** 编辑区视图状态(viewStateCache v1)。 */
4248
4362
  viewstate: "edrv.cache.viewstate.v1.",
4249
- /** 编辑器页签/活动文件(EditorView v2)。 */
4250
- editor: "edrv.editor.v2.",
4363
+ /** 编辑器页签/活动文件(EditorView v3:页签带 pinned 固定标记)。 */
4364
+ editor: "edrv.editor.v3.",
4365
+ /** 旧编辑器页签键(v2:页签为纯路径数组)——仅用于一次性回读迁移,不再写入。 */
4366
+ editorLegacy: "edrv.editor.v2.",
4251
4367
  /** 侧边栏状态(EditorView;按布局 side/central 追加段)。 */
4252
4368
  sidebar: "edrv.sidebar.",
4253
4369
  /** 搜索面板条件(SearchPanel v1)。 */
@@ -4402,7 +4518,7 @@ window.__ModuleLoader__.load({
4402
4518
  }
4403
4519
  /** 参与迁移的 localStorage 键前缀(编辑区全部按会话持久化的旧键)。 */
4404
4520
  const MIGRATE_PREFIXES = [
4405
- CACHE_KEY.editor,
4521
+ CACHE_KEY.editorLegacy,
4406
4522
  CACHE_KEY.viewstate,
4407
4523
  CACHE_KEY.sidebar + "side.",
4408
4524
  CACHE_KEY.sidebar,
@@ -4625,19 +4741,128 @@ window.__ModuleLoader__.load({
4625
4741
  //#endregion
4626
4742
  //#region src/client/addToConversation.ts
4627
4743
  /** 把追加引用结果映射为可读文案(ok 用 okText;busy 提示已降级纯文本;unavailable 提示不可用)。
4628
- * @author ddj 2026年09月03号
4744
+ * @author ddj 2026年09月03号 / 2026年09月11号
4629
4745
  * @param outcome 追加结果状态
4630
4746
  * @param okText 成功文案(如「已添加文件引用」/「已添加文件夹引用」)
4631
4747
  * @returns 状态栏/通知用文案
4632
4748
  */
4633
4749
  function statusOfAdd(outcome, okText) {
4634
4750
  if (outcome === "ok") return okText;
4635
- if (outcome === "busy") return okText + "(输入框忙,已降级纯文本)";
4751
+ if (outcome === "busy") return okText + "(已降级纯文本)";
4752
+ if (outcome === "failed") return "添加引用失败(输入框忙或未就绪,请重试)";
4636
4753
  return "无法添加到对话(无会话或输入框不可用)";
4637
4754
  }
4638
4755
  /** DSH reference source 名(dsh-client-ui-reference 注册的 @file/@session 统一源)。 */
4639
4756
  const REF_SOURCE = "reference";
4640
4757
  /**
4758
+ * 把 clipboard 投影的末尾折算成 **detect 投影**的末尾。
4759
+ *
4760
+ * ⚠️ 两个投影对 chip 的计长不同,混用会直接毁掉已插入的引用(真实缺陷,勿改回 `draft.length`):
4761
+ * DSH 的 `$composerLayout()` 中 chip 在 detect 投影里只贡献 **1 个 U+FFFC**,
4762
+ * 在 clipboard 投影里贡献 `clipboardText` 全文
4763
+ * (`pushLeaf('chip', kid, '', kid.getTextContent())`)。
4764
+ * 而 `insertReference(ref, span)` 的 span 走 detect 坐标并经 `selectSpan` 校验:
4765
+ * `span.end > layout.detectLength` 时返回 null → 插入失败。
4766
+ *
4767
+ * 旧实现直接用 `draft.length`(clipboard 长度)当 detect 起点:草稿里一旦有 chip,
4768
+ * 该值就超出 detect 长度 → 插入被拒 → 落入 `setDraft` 兜底,
4769
+ * 而 `setDraft` 会整篇重建并剔除 U+FFFC(`text.replace(REFERENCE_PLACEHOLDER_RE, '')`),
4770
+ * 于是**既有 chip 被销毁、引用退化为重复的纯文本**(即「连续添加多个引用显示异常」)。
4771
+ *
4772
+ * 换算:每个 chip 在 clipboard 中多算了 `length - 1`,逐个扣回即得 detect 末尾。
4773
+ * 无 occurrences(旧版 DSH)时退化为 `draft.length`,与旧行为一致。
4774
+ * @author ddj 2026年09月11号
4775
+ * @param snapshot 输入快照
4776
+ * @returns detect 投影中的文档末尾偏移
4777
+ */
4778
+ function detectEndOf(snapshot) {
4779
+ const total = String(snapshot?.draft ?? "").length;
4780
+ const occurrences = snapshot?.occurrences;
4781
+ if (!Array.isArray(occurrences) || !occurrences.length) return total;
4782
+ let clipped = 0;
4783
+ for (const occurrence of occurrences) {
4784
+ const length = Number(occurrence?.length ?? 0);
4785
+ if (!Number.isFinite(length) || length <= 1) continue;
4786
+ clipped += length - 1;
4787
+ }
4788
+ return Math.max(0, total - clipped);
4789
+ }
4790
+ /** chip 列表按 clipboard 偏移升序(官方已排序,此处防御性归一,丢弃非法项)。 */
4791
+ function chipsOf(snapshot) {
4792
+ const occurrences = snapshot?.occurrences;
4793
+ if (!Array.isArray(occurrences) || !occurrences.length) return [];
4794
+ const out = [];
4795
+ for (const occurrence of occurrences) {
4796
+ const offset = Number(occurrence?.offset ?? NaN);
4797
+ const length = Number(occurrence?.length ?? NaN);
4798
+ if (!Number.isFinite(offset) || !Number.isFinite(length) || length <= 1) continue;
4799
+ out.push({
4800
+ offset: Math.max(0, offset),
4801
+ length
4802
+ });
4803
+ }
4804
+ return out.sort((a, b) => a.offset - b.offset);
4805
+ }
4806
+ /**
4807
+ * detect 偏移 → clipboard 偏移(paddingAt 用:draft 是 clipboard 投影,不能直接按下标取字符)。
4808
+ * 每个位于该偏移之前的 chip 在 clipboard 中多占 `length − 1`,累计补上即可。
4809
+ * @author ddj 2026年09月11号
4810
+ * @param snapshot 输入快照
4811
+ * @param detectOffset detect 投影偏移
4812
+ * @returns clipboard 投影偏移(已夹到 draft 范围内)
4813
+ */
4814
+ function detectToClipboardOf(snapshot, detectOffset) {
4815
+ const total = String(snapshot?.draft ?? "").length;
4816
+ const target = Math.max(0, Math.min(Number(detectOffset) || 0, detectEndOf(snapshot)));
4817
+ let shift = 0;
4818
+ for (const chip of chipsOf(snapshot)) {
4819
+ if (chip.offset - shift >= target) break;
4820
+ shift += chip.length - 1;
4821
+ }
4822
+ return Math.max(0, Math.min(target + shift, total));
4823
+ }
4824
+ /**
4825
+ * 插入点两侧是否需要补空格(仅纯文本通道需要;chip 由 facade 自行补尾随空格)。
4826
+ *
4827
+ * 在 detect 坐标的任意位置求值,故**末尾追加的行为与旧实现逐字符一致**
4828
+ * (末尾前为非空白 → 前后各补一个空格;末尾前已是空白 → 只补尾随空格),
4829
+ * 同时正确支持「插在文本中间」不再贴字。
4830
+ * @author ddj 2026年09月11号
4831
+ * @param snapshot 输入快照
4832
+ * @param detectOffset detect 投影插入点
4833
+ * @returns 是否需要前导/尾随空格
4834
+ */
4835
+ function paddingAt(snapshot, detectOffset) {
4836
+ const draft = String(snapshot?.draft ?? "");
4837
+ const at = detectToClipboardOf(snapshot, detectOffset);
4838
+ const before = at > 0 ? draft.slice(at - 1, at) : "";
4839
+ const after = at < draft.length ? draft.slice(at, at + 1) : "";
4840
+ return {
4841
+ lead: before !== "" && !/\s/.test(before),
4842
+ tail: after === "" || !/\s/.test(after)
4843
+ };
4844
+ }
4845
+ /**
4846
+ * 解析插入点(detect 坐标):优先用输入框**当前光标/选区**(`caretSpan()`,
4847
+ * 官方契约即 detect 坐标,无选区时回落文档末尾),并夹到 `[0, detectLength]`。
4848
+ *
4849
+ * 选区非塌缩时取 `end`(右边界)—— 插入而非替换,绝不删除用户已选中的内容。
4850
+ * caret 缺失/非法(旧版 facade 无该方法、抛异常、NaN 等)一律回落到文档末尾。
4851
+ * @author ddj 2026年09月11号
4852
+ * @param caret caretSpan() 的返回值(可为 null/非法)
4853
+ * @param detectLength detect 投影文档长度
4854
+ * @returns detect 投影插入偏移
4855
+ */
4856
+ function insertOffsetOf(caret, detectLength) {
4857
+ const max = Math.max(0, Number(detectLength) || 0);
4858
+ if (!caret || typeof caret !== "object") return max;
4859
+ const span = caret;
4860
+ const raw = Number(span.end);
4861
+ const value = Number.isFinite(raw) ? raw : Number(span.start);
4862
+ if (!Number.isFinite(value)) return max;
4863
+ return Math.max(0, Math.min(value, max));
4864
+ }
4865
+ /**
4641
4866
  * 生成 DSH @file 语法引用串:cwd 相对化、\ → /、含空白时按 @"path" 语法加引号。
4642
4867
  * cwd 外/无法相对化的路径回退原路径。
4643
4868
  * @author ddj 2026年08月25号
@@ -4706,20 +4931,21 @@ window.__ModuleLoader__.load({
4706
4931
  const conversation = ctx.get("conversation");
4707
4932
  const actx = sessions?.scope?.(sessionId);
4708
4933
  const shell = actx && conversation?.input?.for?.(actx);
4709
- return shell && typeof shell.setDraft === "function" ? shell : void 0;
4934
+ return shell && typeof shell.insertReference === "function" ? shell : void 0;
4710
4935
  } catch {
4711
4936
  return;
4712
4937
  }
4713
4938
  }
4714
- /** 取当前草稿长度与版本;无输入门面时返回 null。
4715
- * @author ddj 2026年08月25号 */
4939
+ /** 取当前草稿快照(长度/版本/chip 列表);无输入门面时返回 null。
4940
+ * @author ddj 2026年08月25号 / 2026年09月11号 */
4716
4941
  function draftCursor(input) {
4717
4942
  try {
4718
4943
  const s = input?.state?.getSnapshot?.();
4719
4944
  if (!s) return null;
4720
4945
  return {
4721
4946
  draft: String(s.draft ?? ""),
4722
- draftRev: Number(s.draftRev ?? 0)
4947
+ draftRev: Number(s.draftRev ?? 0),
4948
+ occurrences: Array.isArray(s.occurrences) ? s.occurrences : void 0
4723
4949
  };
4724
4950
  } catch {
4725
4951
  return null;
@@ -4744,9 +4970,38 @@ window.__ModuleLoader__.load({
4744
4970
  return false;
4745
4971
  }
4746
4972
  }
4973
+ /** 输入门面插入纯文本(官方 plain-text 通道;不存在或异常视作未应用)。
4974
+ * @author ddj 2026年09月11号 */
4975
+ function safeInsertText(input, text, span) {
4976
+ if (typeof input.insertText !== "function") return false;
4977
+ try {
4978
+ return input.insertText(text, span) === true;
4979
+ } catch {
4980
+ return false;
4981
+ }
4982
+ }
4983
+ /**
4984
+ * 读输入框当前光标(detect 坐标);门面缺失/异常返回 null(由调用方回落到文档末尾)。
4985
+ * @author ddj 2026年09月11号
4986
+ * @param input 输入门面
4987
+ * @returns caretSpan 结果或 null
4988
+ */
4989
+ function caretOf(input) {
4990
+ if (typeof input.caretSpan !== "function") return null;
4991
+ try {
4992
+ return input.caretSpan() ?? null;
4993
+ } catch {
4994
+ return null;
4995
+ }
4996
+ }
4747
4997
  /**
4748
4998
  * 创建「添加到对话」动作集(apply 阶段构建一次,随 props 传给 EditorView)。
4749
- * @author ddj 2026年08月25号
4999
+ *
5000
+ * 插入位置 = 输入框**当前光标**(`caretSpan()`,detect 坐标;无光标提示时回落文档末尾),
5001
+ * 不再固定追加到最末尾。坐标一律用 **detect 投影**(见 detectEndOf 的缺陷说明);
5002
+ * 两条写入通道都失败时**不写任何内容**并返回 'failed' —— 旧实现在此处退回 `setDraft`
5003
+ * 整篇重建,会把既有 chip 全部销毁(连续添加多个引用即触发)。
5004
+ * @author ddj 2026年08月25号 / 2026年09月11号
4750
5005
  * @param ctx 客户端服务上下文(sessions + conversation)
4751
5006
  * @returns 动作集
4752
5007
  */
@@ -4757,16 +5012,16 @@ window.__ModuleLoader__.load({
4757
5012
  const cur = draftCursor(input);
4758
5013
  if (!cur) return "unavailable";
4759
5014
  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,
5015
+ const at = insertOffsetOf(caretOf(input), detectEndOf(cur));
5016
+ const span = {
5017
+ start: at,
5018
+ end: at,
4763
5019
  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";
5020
+ };
5021
+ if (safeInsert(input, reference, span)) return "ok";
5022
+ const pad = paddingAt(cur, at);
5023
+ if (safeInsertText(input, (pad.lead ? " " : "") + mention + (pad.tail ? " " : ""), span)) return "busy";
5024
+ return "failed";
4770
5025
  };
4771
5026
  return { appendReference };
4772
5027
  }
@@ -6285,6 +6540,567 @@ window.__ModuleLoader__.load({
6285
6540
  return (0, react_dom.createPortal)(react.default.createElement("div", { "data-edrv-view": "1" }, dialog, createDialog), document.body);
6286
6541
  }
6287
6542
  //#endregion
6543
+ //#region src/client/tabActions.ts
6544
+ /**
6545
+ * 关闭指定集合:活动页签被关时按「右侧优先、否则左侧末位」补位。
6546
+ * @author ddj 2026年09月11号
6547
+ * @param tabs 当前页签
6548
+ * @param closing 待关闭路径集合
6549
+ * @param active 当前活动页签路径
6550
+ * @returns 剩余页签与新的活动页签
6551
+ */
6552
+ function applyClose(tabs, closing, active) {
6553
+ const remaining = tabs.filter((tab) => !closing.has(tab.path));
6554
+ if (remaining.length === tabs.length) return {
6555
+ tabs,
6556
+ active
6557
+ };
6558
+ if (active && !closing.has(active)) return {
6559
+ tabs: remaining,
6560
+ active
6561
+ };
6562
+ return {
6563
+ tabs: remaining,
6564
+ active: pickNeighbor(tabs, remaining, active)
6565
+ };
6566
+ }
6567
+ /**
6568
+ * 关闭其他:保留目标页签与全部固定页签。
6569
+ * @author ddj 2026年09月11号
6570
+ * @param tabs 当前页签
6571
+ * @param target 保留的目标页签路径
6572
+ * @param active 当前活动页签路径
6573
+ * @returns 关闭结果
6574
+ */
6575
+ function closeOthers(tabs, target, active) {
6576
+ return applyClose(tabs, closingExcept(tabs, (tab) => tab.path === target || tab.pinned === true), active);
6577
+ }
6578
+ /**
6579
+ * 关闭右侧:关闭目标之后、未被固定的页签。
6580
+ * @author ddj 2026年09月11号
6581
+ * @param tabs 当前页签
6582
+ * @param target 基准页签路径(其右侧才关)
6583
+ * @param active 当前活动页签路径
6584
+ * @returns 关闭结果
6585
+ */
6586
+ function closeRight(tabs, target, active) {
6587
+ const at = tabs.findIndex((tab) => tab.path === target);
6588
+ const closing = /* @__PURE__ */ new Set();
6589
+ for (let i = at + 1; i > 0 && i < tabs.length; i += 1) if (tabs[i].pinned !== true) closing.add(tabs[i].path);
6590
+ return applyClose(tabs, closing, active);
6591
+ }
6592
+ /**
6593
+ * 关闭已保存:关闭未固定且无未保存修改的页签。
6594
+ * @author ddj 2026年09月11号
6595
+ * @param tabs 当前页签
6596
+ * @param dirty 路径 → 是否有未保存修改
6597
+ * @param active 当前活动页签路径
6598
+ * @returns 关闭结果
6599
+ */
6600
+ function closeSaved(tabs, dirty, active) {
6601
+ return applyClose(tabs, closingExcept(tabs, (tab) => tab.pinned === true || dirty[tab.path] === true), active);
6602
+ }
6603
+ /**
6604
+ * 全部关闭:仅关未固定页签(全部固定时无操作)。
6605
+ * @author ddj 2026年09月11号
6606
+ * @param tabs 当前页签
6607
+ * @param active 当前活动页签路径
6608
+ * @returns 关闭结果
6609
+ */
6610
+ function closeAll(tabs, active) {
6611
+ return applyClose(tabs, closingExcept(tabs, (tab) => tab.pinned === true), active);
6612
+ }
6613
+ /**
6614
+ * 反选可关闭集合(保留 keep 命中的页签,其余进关闭集)。
6615
+ * @author ddj 2026年09月11号
6616
+ * @param tabs 当前页签
6617
+ * @param keep 保留判定
6618
+ * @returns 待关闭路径集合
6619
+ */
6620
+ function closingExcept(tabs, keep) {
6621
+ const closing = /* @__PURE__ */ new Set();
6622
+ for (const tab of tabs) if (!keep(tab)) closing.add(tab.path);
6623
+ return closing;
6624
+ }
6625
+ /**
6626
+ * 选补位页签:先向右找最近的存活页签,再向左找,都没有则取剩余首个。
6627
+ * @author ddj 2026年09月11号
6628
+ * @param tabs 关闭前的页签(用于取原索引)
6629
+ * @param remaining 关闭后剩余的页签
6630
+ * @param active 被关掉的活动页签路径
6631
+ * @returns 补位页签路径;无剩余返回 null
6632
+ */
6633
+ function pickNeighbor(tabs, remaining, active) {
6634
+ const first = remaining[0];
6635
+ if (!first) return null;
6636
+ const at = tabs.findIndex((tab) => tab.path === active);
6637
+ if (at < 0) return first.path;
6638
+ const alive = new Set(remaining.map((tab) => tab.path));
6639
+ for (let i = at + 1; i < tabs.length; i += 1) if (alive.has(tabs[i].path)) return tabs[i].path;
6640
+ for (let i = at - 1; i >= 0; i -= 1) if (alive.has(tabs[i].path)) return tabs[i].path;
6641
+ return first.path;
6642
+ }
6643
+ /**
6644
+ * 切换页签固定态:固定页签整体前移(两侧各自保持相对顺序)。
6645
+ * @author ddj 2026年09月11号
6646
+ * @param tabs 当前页签
6647
+ * @param target 目标页签路径
6648
+ * @returns 重排后的页签(目标不存在时原样返回)
6649
+ */
6650
+ function togglePin(tabs, target) {
6651
+ if (!tabs.some((tab) => tab.path === target)) return tabs;
6652
+ return pinnedFirst(tabs.map((tab) => tab.path === target ? {
6653
+ path: tab.path,
6654
+ pinned: tab.pinned !== true
6655
+ } : tab));
6656
+ }
6657
+ /**
6658
+ * 新增页签:已存在则原样返回;否则插到最后一个固定页签之后。
6659
+ * @author ddj 2026年09月11号
6660
+ * @param tabs 当前页签
6661
+ * @param path 新页签路径
6662
+ * @returns 新页签数组
6663
+ */
6664
+ function insertTab(tabs, path) {
6665
+ if (tabs.some((tab) => tab.path === path)) return tabs;
6666
+ let at = tabs.length;
6667
+ for (let i = tabs.length - 1; i >= 0; i -= 1) {
6668
+ if (tabs[i].pinned !== true) continue;
6669
+ at = i + 1;
6670
+ break;
6671
+ }
6672
+ return tabs.slice(0, at).concat([{ path }], tabs.slice(at));
6673
+ }
6674
+ /**
6675
+ * 固定分区:固定页签在前,两侧各自保持原相对顺序。
6676
+ * @author ddj 2026年09月11号
6677
+ * @param tabs 页签
6678
+ * @returns 重排后的页签
6679
+ */
6680
+ function pinnedFirst(tabs) {
6681
+ return tabs.filter(isPinned).concat(tabs.filter((tab) => !isPinned(tab)));
6682
+ }
6683
+ /**
6684
+ * 是否固定页签。
6685
+ * @author ddj 2026年09月11号
6686
+ * @param tab 页签
6687
+ * @returns 是否固定
6688
+ */
6689
+ function isPinned(tab) {
6690
+ return tab.pinned === true;
6691
+ }
6692
+ /**
6693
+ * 归一化持久化的页签数据:兼容旧版 `string[]`、去重、固定分区。
6694
+ * 损坏项(非字符串/非对象/无 path)直接丢弃。
6695
+ * @author ddj 2026年09月11号
6696
+ * @param raw localStorage 解析结果(任意形状)
6697
+ * @returns 归一化后的页签数组
6698
+ */
6699
+ function normalizeTabs(raw) {
6700
+ if (!Array.isArray(raw)) return [];
6701
+ const seen = /* @__PURE__ */ new Set();
6702
+ const out = [];
6703
+ for (const item of raw) {
6704
+ const tab = tabOf(item);
6705
+ if (!tab || seen.has(tab.path)) continue;
6706
+ seen.add(tab.path);
6707
+ out.push(tab);
6708
+ }
6709
+ return pinnedFirst(out);
6710
+ }
6711
+ /**
6712
+ * 解释单条持久化页签:字符串 = 路径(旧版),对象取 path/pinned。
6713
+ * @author ddj 2026年09月11号
6714
+ * @param item 原始条目
6715
+ * @returns 页签或 null(无法解释)
6716
+ */
6717
+ function tabOf(item) {
6718
+ if (typeof item === "string") return item ? { path: item } : null;
6719
+ if (!item || typeof item !== "object") return null;
6720
+ const raw = item;
6721
+ if (typeof raw.path !== "string" || !raw.path) return null;
6722
+ return raw.pinned === true ? {
6723
+ path: raw.path,
6724
+ pinned: true
6725
+ } : { path: raw.path };
6726
+ }
6727
+ /**
6728
+ * 选活动页签:恢复值仍存在则用它,否则取首个(无页签返回 null)。
6729
+ * @author ddj 2026年09月11号
6730
+ * @param tabs 页签
6731
+ * @param wanted 持久化的活动路径
6732
+ * @returns 活动页签路径
6733
+ */
6734
+ function pickActive(tabs, wanted) {
6735
+ const first = tabs[0];
6736
+ if (!first) return null;
6737
+ if (typeof wanted === "string" && tabs.some((tab) => tab.path === wanted)) return wanted;
6738
+ return first.path;
6739
+ }
6740
+ /** Windows 盘符 / UNC / POSIX 根:视为工作区外的绝对路径。 */
6741
+ const ABSOLUTE_RE = /^(?:[a-z]:[\\/]|\\\\|\/)/i;
6742
+ /**
6743
+ * 是否绝对路径(工作区外)。
6744
+ * @author ddj 2026年09月11号
6745
+ * @param path 路径
6746
+ * @returns 是否绝对路径
6747
+ */
6748
+ function isAbsolutePath(path) {
6749
+ return ABSOLUTE_RE.test(String(path ?? ""));
6750
+ }
6751
+ /**
6752
+ * 是否可在资源管理器视图中定位(工作区相对且不含 `..` 上跳段)。
6753
+ * @author ddj 2026年09月11号
6754
+ * @param path 路径
6755
+ * @returns 是否可定位
6756
+ */
6757
+ function isTreeRevealable(path) {
6758
+ const text = String(path ?? "").trim();
6759
+ if (!text || isAbsolutePath(text)) return false;
6760
+ return !text.replace(/\\/g, "/").split("/").includes("..");
6761
+ }
6762
+ /**
6763
+ * 相对路径:cwd 内路径去掉工作区前缀;工作区外/无 cwd 回退规范化原路径。
6764
+ * @author ddj 2026年09月11号
6765
+ * @param path 路径
6766
+ * @param cwd 会话工作区目录(可空)
6767
+ * @returns 展示/复制用的相对路径
6768
+ */
6769
+ function relativeOf(path, cwd) {
6770
+ const target = normalizeSlashes(path);
6771
+ const base = normalizeSlashes(cwd).replace(/\/+$/, "");
6772
+ if (base && target.toLowerCase().startsWith((base + "/").toLowerCase())) return target.slice(base.length + 1);
6773
+ return target;
6774
+ }
6775
+ /**
6776
+ * 绝对路径:相对路径按 cwd 拼接;已是绝对路径或没有 cwd 时回退规范化原路径。
6777
+ * @author ddj 2026年09月11号
6778
+ * @param path 路径
6779
+ * @param cwd 会话工作区目录(可空)
6780
+ * @returns 展示/复制用的绝对路径
6781
+ */
6782
+ function absoluteOf(path, cwd) {
6783
+ const target = normalizeSlashes(path);
6784
+ if (isAbsolutePath(target)) return target;
6785
+ const base = normalizeSlashes(cwd).replace(/\/+$/, "");
6786
+ return base ? base + "/" + target : target;
6787
+ }
6788
+ /**
6789
+ * 反斜杠归一为斜杠(页签/引用/剪贴板统一用正斜杠,与既有 mentionOf 口径一致)。
6790
+ * @author ddj 2026年09月11号
6791
+ * @param path 路径
6792
+ * @returns 正斜杠路径
6793
+ */
6794
+ function normalizeSlashes(path) {
6795
+ return String(path ?? "").replace(/\\/g, "/");
6796
+ }
6797
+ /**
6798
+ * 文件路径的祖先目录(由浅到深):`a/b/c.ts` → `['a', 'a/b']`。
6799
+ * 根级文件、绝对路径与含 `..` 的路径返回空数组。
6800
+ * @author ddj 2026年09月11号
6801
+ * @param path 工作区相对路径
6802
+ * @returns 祖先目录相对路径数组
6803
+ */
6804
+ function ancestorDirsOf(path) {
6805
+ if (!isTreeRevealable(path)) return [];
6806
+ const segs = normalizeSlashes(path).split("/").filter(Boolean);
6807
+ const out = [];
6808
+ for (let i = 1; i < segs.length; i += 1) {
6809
+ const prev = segs[i - 1];
6810
+ out.push(out.length ? out[out.length - 1] + "/" + prev : prev);
6811
+ }
6812
+ return out;
6813
+ }
6814
+ /**
6815
+ * 文件名(页签/状态栏展示与 tooltip 用)。
6816
+ * @author ddj 2026年09月11号
6817
+ * @param path 路径
6818
+ * @returns 末段文件名;空路径返回空串
6819
+ */
6820
+ function baseNameOf(path) {
6821
+ return normalizeSlashes(path).split("/").filter(Boolean).pop() ?? "";
6822
+ }
6823
+ //#endregion
6824
+ //#region src/client/tabMenu.ts
6825
+ /**
6826
+ * dsh-vscode-mode client — 文件页签右键菜单模型(纯函数,可单测)。
6827
+ * 菜单是「数据」而非 JSX:条目顺序、分组分隔线、禁用规则与键位提示全部在此收敛,
6828
+ * EditorView 只把结果映射成 ContextMenu 的 entries 并派发动作。
6829
+ *
6830
+ * 与参考图(VS Code / CodeBuddy 页签右键菜单)的对齐口径:
6831
+ * 保留其分组顺序(对话 / 关闭族 / 路径 / 定位 / 固定),**省略**本架构无法实现的
6832
+ * 「向右拆分 / Split & Move / 移动到新窗口 / 复制到新窗口」(浏览器内单编辑器实例)。
6833
+ * 只显示**真实已绑定**的键位;参考图里的两步弦(Ctrl+K W 等)引擎不支持,故不伪造。
6834
+ *
6835
+ * 「固定 = 保护」:关闭其他 / 关闭右侧 / 关闭已保存 / 全部关闭 一律不关固定页签,
6836
+ * 故这些条目在「固定页签是唯一可关对象」时判定为禁用(与 tabActions 语义同源)。
6837
+ * 作者 ddj 2026年09月11号
6838
+ */
6839
+ /**
6840
+ * 构建页签右键菜单条目。
6841
+ * @author ddj 2026年09月11号
6842
+ * @param state 菜单状态快照
6843
+ * @returns 菜单条目(已按参考图分组排序)
6844
+ */
6845
+ function buildTabMenu(state) {
6846
+ const { path, tabs, active, dirty, cwd, hasSession, canAddToConversation, closeChord } = state;
6847
+ const pinned = tabs.find((tab) => tab.path === path)?.pinned === true;
6848
+ return [
6849
+ {
6850
+ id: "add-to-conversation",
6851
+ label: "添加到对话",
6852
+ disabled: !(hasSession && canAddToConversation)
6853
+ },
6854
+ {
6855
+ id: "close",
6856
+ label: "关闭",
6857
+ separator: true,
6858
+ ...closeChord ? { hint: closeChord } : {}
6859
+ },
6860
+ {
6861
+ id: "close-others",
6862
+ label: "关闭其他",
6863
+ disabled: nothingToClose(closeOthers(tabs, path, active), tabs)
6864
+ },
6865
+ {
6866
+ id: "close-right",
6867
+ label: "关闭右侧标签页",
6868
+ disabled: nothingToClose(closeRight(tabs, path, active), tabs)
6869
+ },
6870
+ {
6871
+ id: "close-saved",
6872
+ label: "关闭已保存",
6873
+ disabled: nothingToClose(closeSaved(tabs, dirty, active), tabs)
6874
+ },
6875
+ {
6876
+ id: "close-all",
6877
+ label: "全部关闭",
6878
+ disabled: nothingToClose(closeAll(tabs, active), tabs)
6879
+ },
6880
+ {
6881
+ id: "copy-path",
6882
+ label: "复制路径",
6883
+ separator: true
6884
+ },
6885
+ {
6886
+ id: "copy-relative-path",
6887
+ label: "复制相对路径",
6888
+ disabled: !hasCwd(cwd)
6889
+ },
6890
+ {
6891
+ id: "reveal-in-os",
6892
+ label: "在文件资源管理器中显示",
6893
+ separator: true,
6894
+ disabled: !hasSession
6895
+ },
6896
+ {
6897
+ id: "reveal-in-view",
6898
+ label: "在资源管理器视图中显示",
6899
+ disabled: !isTreeRevealable(path)
6900
+ },
6901
+ {
6902
+ id: "toggle-pinned",
6903
+ label: pinned ? "取消固定" : "固定",
6904
+ separator: true
6905
+ }
6906
+ ];
6907
+ }
6908
+ /**
6909
+ * 关闭族是否无可关对象(结果页签数与关闭前一致 = 一个也没关掉)。
6910
+ * @author ddj 2026年09月11号
6911
+ * @param result 关闭结果
6912
+ * @param tabs 关闭前的页签
6913
+ * @returns 是否无可关对象
6914
+ */
6915
+ function nothingToClose(result, tabs) {
6916
+ return result.tabs.length >= tabs.length;
6917
+ }
6918
+ /**
6919
+ * 是否有可用工作区目录(复制相对路径需要)。
6920
+ * @author ddj 2026年09月11号
6921
+ * @param cwd 会话工作区目录
6922
+ * @returns 是否可用
6923
+ */
6924
+ function hasCwd(cwd) {
6925
+ return typeof cwd === "string" && cwd.trim() !== "";
6926
+ }
6927
+ //#endregion
6928
+ //#region src/client/saveDebounce.ts
6929
+ /**
6930
+ * 创建防抖保存槽。
6931
+ * @author ddj 2026年09月11号
6932
+ * @returns 保存槽句柄
6933
+ */
6934
+ function createSaveTimer() {
6935
+ let pending = null;
6936
+ return {
6937
+ arm(schedule, delay, run) {
6938
+ if (pending) {
6939
+ pending.cancel?.();
6940
+ pending = null;
6941
+ }
6942
+ const cancel = schedule(() => {
6943
+ pending = null;
6944
+ run();
6945
+ }, delay);
6946
+ pending = {
6947
+ cancel: typeof cancel === "function" ? cancel : void 0,
6948
+ run
6949
+ };
6950
+ },
6951
+ flush() {
6952
+ const slot = pending;
6953
+ if (!slot) return;
6954
+ pending = null;
6955
+ slot.cancel?.();
6956
+ slot.run();
6957
+ },
6958
+ cancel() {
6959
+ const slot = pending;
6960
+ if (!slot) return;
6961
+ pending = null;
6962
+ slot.cancel?.();
6963
+ },
6964
+ isPending() {
6965
+ return pending !== null;
6966
+ }
6967
+ };
6968
+ }
6969
+ //#endregion
6970
+ //#region src/client/ui/menuPosition.ts
6971
+ /**
6972
+ * 将菜单坐标限制在 viewport 内。
6973
+ * @author ddj 2026年08月28号
6974
+ * @param x 期望的 viewport x 坐标
6975
+ * @param y 期望的 viewport y 坐标
6976
+ * @param viewportWidth viewport 宽度
6977
+ * @param viewportHeight viewport 高度
6978
+ * @param menuWidth 菜单估算宽度
6979
+ * @param menuHeight 菜单估算高度
6980
+ * @returns 限制后的菜单位置
6981
+ */
6982
+ function clampMenuPosition(x, y, viewportWidth, viewportHeight, menuWidth, menuHeight) {
6983
+ const maxLeft = Math.max(4, viewportWidth - menuWidth);
6984
+ const maxTop = Math.max(4, viewportHeight - menuHeight);
6985
+ return {
6986
+ left: Math.max(4, Math.min(x, maxLeft)),
6987
+ top: Math.max(4, Math.min(y, maxTop))
6988
+ };
6989
+ }
6990
+ //#endregion
6991
+ //#region src/client/ui/ContextMenu.ts
6992
+ /**
6993
+ * dsh-vscode-mode client — 通用浮动右键菜单(createElement 风格、类型化)。
6994
+ * 非阻塞式关闭:window capture 监听(点击菜单外/右键菜单外/Esc 关闭),
6995
+ * 不拦截菜单外事件,允许同一事件内由发起方切换/重开菜单。
6996
+ * 条目支持 danger/disabled/separator,复用既有 edrv-ctxmenu* 样式类。
6997
+ * 作者 ddj 2026-08-27
6998
+ */
6999
+ /** 估算宽高(实测前的回退值;条目数变化大时以实测为准,见 useLayoutEffect)。 */
7000
+ const MENU_W = 224;
7001
+ const MENU_H = 176;
7002
+ /**
7003
+ * 把条目表展开为渲染行:`separator` 是**前置分隔线**,条目本身仍然渲染。
7004
+ *
7005
+ * ⚠️ 旧实现把 `separator` 当成「本条是分隔线」而 `continue` 跳过条目本身 ——
7006
+ * 因历史调用方都没用过该字段,缺陷一直潜伏;页签菜单首次使用后表现为
7007
+ * 「关闭 / 复制路径 / 在文件资源管理器中显示 / 固定」四条主条目整条消失。
7008
+ * 首条带 separator 时不渲染分隔线(菜单顶部不应有横线,与参考图一致)。
7009
+ * @author ddj 2026年09月11号
7010
+ * @param entries 菜单项列表
7011
+ * @returns 渲染行列表
7012
+ */
7013
+ function menuRows(entries) {
7014
+ const rows = [];
7015
+ for (const entry of entries) {
7016
+ if (entry.separator && rows.length) rows.push({
7017
+ kind: "sep",
7018
+ entry
7019
+ });
7020
+ rows.push({
7021
+ kind: "item",
7022
+ entry
7023
+ });
7024
+ }
7025
+ return rows;
7026
+ }
7027
+ /**
7028
+ * 浮动右键菜单。
7029
+ * @param props.x 视口 x 坐标
7030
+ * @param props.y 视口 y 坐标
7031
+ * @param props.entries 菜单项列表
7032
+ * @param props.onClose 关闭回调(backdrop 点击/右键/Esc/点击项后触发)
7033
+ */
7034
+ function ContextMenu(props) {
7035
+ const { x, y, entries, onClose } = props;
7036
+ const menuRef = react.default.useRef(null);
7037
+ const [size, setSize] = react.default.useState(null);
7038
+ react.default.useEffect(() => {
7039
+ const onKey = (e) => {
7040
+ if (e.key === "Escape") onClose();
7041
+ };
7042
+ const onPointerDown = (e) => {
7043
+ if (e.button !== 0) return;
7044
+ if (menuRef.current && menuRef.current.contains(e.target)) return;
7045
+ onClose();
7046
+ };
7047
+ const onContextMenu = (e) => {
7048
+ if (menuRef.current && menuRef.current.contains(e.target)) e.preventDefault();
7049
+ onClose();
7050
+ };
7051
+ window.addEventListener("keydown", onKey, true);
7052
+ window.addEventListener("pointerdown", onPointerDown, true);
7053
+ window.addEventListener("contextmenu", onContextMenu, true);
7054
+ return () => {
7055
+ window.removeEventListener("keydown", onKey, true);
7056
+ window.removeEventListener("pointerdown", onPointerDown, true);
7057
+ window.removeEventListener("contextmenu", onContextMenu, true);
7058
+ };
7059
+ }, [onClose]);
7060
+ react.default.useLayoutEffect(() => {
7061
+ const el = menuRef.current;
7062
+ if (!el) return;
7063
+ const w = el.offsetWidth;
7064
+ const h = el.offsetHeight;
7065
+ if (!w || !h) return;
7066
+ setSize((prev) => prev && prev.w === w && prev.h === h ? prev : {
7067
+ w,
7068
+ h
7069
+ });
7070
+ }, [entries]);
7071
+ 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);
7072
+ const children = [];
7073
+ for (const row of menuRows(entries)) {
7074
+ if (row.kind === "sep") {
7075
+ children.push(react.default.createElement("div", {
7076
+ key: "sep-" + row.entry.id,
7077
+ className: "edrv-ctxmenu-sep"
7078
+ }));
7079
+ continue;
7080
+ }
7081
+ const entry = row.entry;
7082
+ const cls = "edrv-ctxmenu-item" + (entry.danger ? " edrv-ctxmenu-danger" : "") + (entry.disabled ? " edrv-ctxmenu-disabled" : "");
7083
+ children.push(react.default.createElement("button", {
7084
+ key: entry.id,
7085
+ className: cls,
7086
+ disabled: entry.disabled,
7087
+ title: entry.hint ? entry.label + " (" + entry.hint + ")" : entry.label,
7088
+ onClick: () => {
7089
+ if (!entry.disabled) {
7090
+ entry.onClick?.();
7091
+ onClose();
7092
+ }
7093
+ }
7094
+ }, react.default.createElement("span", { className: "edrv-ctxmenu-label" }, entry.label), entry.hint ? react.default.createElement("span", { className: "edrv-ctxmenu-hint" }, entry.hint) : null));
7095
+ }
7096
+ const overlay = react.default.createElement("div", { "data-edrv-view": "1" }, react.default.createElement("div", {
7097
+ className: "edrv-ctxmenu",
7098
+ ref: menuRef,
7099
+ style: position
7100
+ }, ...children));
7101
+ return typeof document === "undefined" ? overlay : (0, react_dom.createPortal)(overlay, document.body);
7102
+ }
7103
+ //#endregion
6288
7104
  //#region src/client/ui/EditorView.ts
6289
7105
  /**
6290
7106
  * dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
@@ -6388,7 +7204,8 @@ window.__ModuleLoader__.load({
6388
7204
  const modelsRef = react.default.useRef(null);
6389
7205
  if (!modelsRef.current) modelsRef.current = modelsForScope(scope);
6390
7206
  const viewStatesRef = react.default.useRef({});
6391
- const saveTimerRef = react.default.useRef(null);
7207
+ const saveTimerRef = react.default.useRef(createSaveTimer());
7208
+ const savingRef = react.default.useRef(/* @__PURE__ */ new Set());
6392
7209
  const loadSeqRef = react.default.useRef(0);
6393
7210
  const programmaticRef = react.default.useRef(false);
6394
7211
  const restoredScopeRef = react.default.useRef(null);
@@ -6404,6 +7221,10 @@ window.__ModuleLoader__.load({
6404
7221
  const rowNavMoveRef = react.default.useRef(false);
6405
7222
  const activeRef = react.default.useRef(null);
6406
7223
  activeRef.current = active;
7224
+ const tabsRef = react.default.useRef([]);
7225
+ tabsRef.current = tabs;
7226
+ const dirtyRef = react.default.useRef({});
7227
+ dirtyRef.current = dirtyMap;
6407
7228
  const tabsHostRef = react.default.useRef(null);
6408
7229
  const [navTick, setNavTick] = react.default.useState(0);
6409
7230
  const hoverRegionsRef = react.default.useRef([]);
@@ -6452,10 +7273,7 @@ window.__ModuleLoader__.load({
6452
7273
  }, [pendingRegions]);
6453
7274
  const sum = react.default.useMemo(() => summarize(Object.values(records)), [records]);
6454
7275
  const addTab = (path, select) => {
6455
- setTabs((prev) => {
6456
- if (prev.some((t) => t.path === path)) return prev;
6457
- return prev.concat([{ path }]);
6458
- });
7276
+ setTabs((prev) => insertTab(prev, path));
6459
7277
  if (select) setActive(path);
6460
7278
  };
6461
7279
  /**
@@ -6581,9 +7399,14 @@ window.__ModuleLoader__.load({
6581
7399
  };
6582
7400
  navBackRef.current = navBack;
6583
7401
  navForwardRef.current = navForward;
6584
- const closeTab = (path) => {
6585
- flushSave();
6586
- if (path === active) {
7402
+ /**
7403
+ * 关闭页签的收尾(不动作状态):活动页签先存视图状态与导航历史;
7404
+ * PDF 页签销毁面板控制器并清 base64 缓存(未保存注释随之丢弃,脏点已提示)。
7405
+ * @author ddj 2026年09月11号
7406
+ * @param path 待关闭页签路径
7407
+ */
7408
+ const releaseTab = (path) => {
7409
+ if (path === activeRef.current) {
6587
7410
  saveViewState(path);
6588
7411
  recordNav();
6589
7412
  }
@@ -6593,13 +7416,44 @@ window.__ModuleLoader__.load({
6593
7416
  pdfCtlRef.current.delete(path);
6594
7417
  }
6595
7418
  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
- });
7419
+ };
7420
+ /**
7421
+ * 提交关闭结果:一次性更新页签与活动页签(补位规则见 tabActions.applyClose)。
7422
+ * @author ddj 2026年09月11号
7423
+ * @param result tabActions 产出的关闭结果
7424
+ */
7425
+ const commitClose = (result) => {
7426
+ setTabs((prev) => prev === result.tabs ? prev : result.tabs);
7427
+ if (result.active !== activeRef.current) setActive(result.active);
7428
+ };
7429
+ /**
7430
+ * 立即关闭页签(页签 × 按钮与「关闭」菜单项共用;批量关闭走 closeTabs)。
7431
+ * @author ddj 2026年08月25号 / 2026年09月11号
7432
+ * @param path 待关闭页签路径
7433
+ */
7434
+ const closeTab = (path) => {
7435
+ flushSave();
7436
+ persistDirty([path]);
7437
+ releaseTab(path);
7438
+ commitClose(applyClose(tabsRef.current, /* @__PURE__ */ new Set([path]), activeRef.current));
7439
+ };
7440
+ /**
7441
+ * 批量关闭:先落盘全部待关闭的脏页签,再逐个收尾并一次性提交。
7442
+ * @author ddj 2026年09月11号
7443
+ * @param result 关闭结果(来自 tabActions 的 closeOthers/closeRight/closeSaved/closeAll)
7444
+ * @param label 状态栏文案前缀(如「已关闭其他」)
7445
+ */
7446
+ const closeTabs = (result, label) => {
7447
+ flushSave();
7448
+ const closing = tabsRef.current.filter((t) => !result.tabs.some((r) => r.path === t.path));
7449
+ if (!closing.length) {
7450
+ setStatus("无可关闭的页签");
7451
+ return;
7452
+ }
7453
+ persistDirty(closing.map((t) => t.path));
7454
+ for (const tab of closing) releaseTab(tab.path);
7455
+ commitClose(result);
7456
+ setStatus(label + "(" + closing.length + " 个)");
6603
7457
  };
6604
7458
  /**
6605
7459
  * 在已打开页签间循环切换(文件分页归编辑器自带页签栏)。
@@ -6994,12 +7848,13 @@ window.__ModuleLoader__.load({
6994
7848
  modelsRef.current = modelsForScope(scope);
6995
7849
  viewStatesRef.current = viewStatesLoad(scope);
6996
7850
  try {
6997
- const raw = localStorage.getItem(CACHE_KEY.editor + String(scope));
7851
+ const raw = localStorage.getItem(CACHE_KEY.editor + String(scope)) ?? localStorage.getItem(CACHE_KEY.editorLegacy + String(scope));
6998
7852
  if (raw) {
6999
7853
  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);
7854
+ const restored = normalizeTabs(saved?.tabs);
7855
+ if (restored.length) {
7856
+ setTabs(restored);
7857
+ setActive(pickActive(restored, saved?.active));
7003
7858
  }
7004
7859
  }
7005
7860
  } catch (e) {}
@@ -7016,10 +7871,14 @@ window.__ModuleLoader__.load({
7016
7871
  react.default.useEffect(() => {
7017
7872
  if (!sessionId) return;
7018
7873
  try {
7019
- localStorage.setItem(CACHE_KEY.editor + String(scope), JSON.stringify({
7020
- tabs: tabs.map((t) => t.path),
7874
+ const payload = {
7875
+ tabs: tabs.map((t) => t.pinned ? {
7876
+ path: t.path,
7877
+ pinned: true
7878
+ } : { path: t.path }),
7021
7879
  active
7022
- }));
7880
+ };
7881
+ localStorage.setItem(CACHE_KEY.editor + String(scope), JSON.stringify(payload));
7023
7882
  } catch (e) {}
7024
7883
  }, [
7025
7884
  tabs,
@@ -7172,6 +8031,7 @@ window.__ModuleLoader__.load({
7172
8031
  ["edrv.command.navigateForward", () => navForwardRef.current?.()],
7173
8032
  ["edrv.command.nextTab", () => cycleTabRef.current?.(1)],
7174
8033
  ["edrv.command.prevTab", () => cycleTabRef.current?.(-1)],
8034
+ ["edrv.command.closeTab", () => closeActiveTab()],
7175
8035
  ["edrv.command.nextEditorRow", () => moveRow(1)],
7176
8036
  ["edrv.command.prevEditorRow", () => moveRow(-1)],
7177
8037
  ["edrv.command.goToDefinition", () => {
@@ -7287,14 +8147,6 @@ window.__ModuleLoader__.load({
7287
8147
  setStatus("加载中…");
7288
8148
  loadContent(active, sessionId);
7289
8149
  }, [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
8150
  react.default.useEffect(() => {
7299
8151
  if (monaco || monacoErr) return;
7300
8152
  let alive = true;
@@ -7354,11 +8206,14 @@ window.__ModuleLoader__.load({
7354
8206
  }
7355
8207
  return model;
7356
8208
  };
8209
+ /**
8210
+ * 提交待执行的防抖保存(**真正执行保存**,不是取消)。
8211
+ * 语义与缺陷背景见 saveDebounce.ts:`schedule` 的返回值是只 clearTimeout 的 disposer,
8212
+ * 旧实现把它当「立即保存」调用 → 防抖窗口内切页签/关闭文件会静默丢改动。
8213
+ * @author ddj 2026年09月11号
8214
+ */
7357
8215
  const flushSave = () => {
7358
- if (saveTimerRef.current) {
7359
- saveTimerRef.current();
7360
- saveTimerRef.current = null;
7361
- }
8216
+ saveTimerRef.current?.flush();
7362
8217
  };
7363
8218
  const doSave = (silent) => {
7364
8219
  if (!active) return;
@@ -7383,6 +8238,7 @@ window.__ModuleLoader__.load({
7383
8238
  }
7384
8239
  const text = ed.getValue();
7385
8240
  if (!silent) setStatus("保存中…");
8241
+ savingRef.current.add(active);
7386
8242
  rpc("edrv.save", {
7387
8243
  sessionId,
7388
8244
  path: active,
@@ -7403,15 +8259,53 @@ window.__ModuleLoader__.load({
7403
8259
  }).catch((e) => {
7404
8260
  setStatus("保存失败");
7405
8261
  setError("保存异常:" + String(e));
8262
+ }).finally(() => {
8263
+ savingRef.current.delete(active);
7406
8264
  });
7407
8265
  };
7408
8266
  doSaveRef.current = doSave;
8267
+ /**
8268
+ * 关闭前落盘:把「待关闭且仍标脏」的页签静默保存。
8269
+ *
8270
+ * 调用方须先 `flushSave()`:它已把活动页签的待执行保存真正提交(见 saveDebounce 的缺陷说明)。
8271
+ * 这里再处理**仍标脏**的页签 —— 包括活动页签(其保存可能在途或失败),
8272
+ * 用 model 里的当前文本补一次,保证关闭前内容一定写到磁盘。
8273
+ * 保存失败只保留脏标记(不丢用户编辑),并在状态栏给出提示。
8274
+ * @author ddj 2026年09月11号
8275
+ * @param paths 即将关闭的页签路径
8276
+ */
8277
+ const persistDirty = (paths) => {
8278
+ for (const path of paths) {
8279
+ if (!dirtyRef.current[path]) continue;
8280
+ if (savingRef.current.has(path)) continue;
8281
+ const pdfCtl = pdfCtlRef.current.get(path);
8282
+ if (pdfCtl) {
8283
+ if (pdfCtl.isDirty()) pdfCtl.savePdf();
8284
+ continue;
8285
+ }
8286
+ const model = modelsRef.current.get(path);
8287
+ if (!model) {
8288
+ setStatus("未保存的修改无法落盘:" + baseNameOf(path));
8289
+ continue;
8290
+ }
8291
+ const content = model.getValue();
8292
+ savingRef.current.add(path);
8293
+ rpc("edrv.save", {
8294
+ sessionId,
8295
+ path,
8296
+ content
8297
+ }).then((res) => {
8298
+ if (res && res.ok) setDirtyMap((d) => Object.assign({}, d, { [path]: false }));
8299
+ }).catch((e) => dbg(sessionId, "关闭前落盘失败:" + path + " · " + String(e))).finally(() => {
8300
+ savingRef.current.delete(path);
8301
+ });
8302
+ }
8303
+ };
7409
8304
  const onEdit = () => {
7410
8305
  if (!editorRef.current || !active) return;
7411
8306
  setDirtyMap((d) => Object.assign({}, d, { [active]: true }));
7412
8307
  setStatus("编辑中…");
7413
- if (saveTimerRef.current) saveTimerRef.current();
7414
- saveTimerRef.current = schedule(() => doSave(true), 700);
8308
+ saveTimerRef.current?.arm(schedule, 700, () => doSave(true));
7415
8309
  };
7416
8310
  onEditRef.current = onEdit;
7417
8311
  react.default.useEffect(() => {
@@ -8033,6 +8927,97 @@ window.__ModuleLoader__.load({
8033
8927
  openInExplorer
8034
8928
  };
8035
8929
  /**
8930
+ * 复制文本到剪贴板(状态栏反馈;浏览器拒绝时提示,不抛异常)。
8931
+ * @author ddj 2026年09月11号
8932
+ * @param text 待复制文本
8933
+ * @param okText 成功文案
8934
+ */
8935
+ const copyText = (text, okText) => {
8936
+ const value = String(text ?? "");
8937
+ if (!value) {
8938
+ setStatus("无可复制内容");
8939
+ return;
8940
+ }
8941
+ if (!navigator.clipboard?.writeText) {
8942
+ setStatus("剪贴板不可用");
8943
+ return;
8944
+ }
8945
+ navigator.clipboard.writeText(value).then(() => setStatus(okText + ":" + value)).catch(() => setStatus("复制失败(浏览器拒绝剪贴板写入)"));
8946
+ };
8947
+ /**
8948
+ * 在资源管理器视图中显示:展开侧栏文件树到目标文件并高亮。
8949
+ * 工作区外文件(片段/全局规则)无树节点,判定为不可定位并给出提示。
8950
+ * @author ddj 2026年09月11号
8951
+ * @param path 工作区相对路径
8952
+ */
8953
+ const revealTabInView = (path) => {
8954
+ if (!isTreeRevealable(path)) {
8955
+ setStatus("该文件不在工作区内,无法在资源管理器中定位");
8956
+ return;
8957
+ }
8958
+ setSidebarOn(true);
8959
+ setActivePanel("explorer");
8960
+ window.dispatchEvent(new CustomEvent("edrv:reveal-path", { detail: { path } }));
8961
+ setStatus("已在资源管理器中定位:" + baseNameOf(path));
8962
+ };
8963
+ /**
8964
+ * 切换页签固定态(固定页签整体前移;固定后隐藏 ×,仅可经菜单取消固定)。
8965
+ * @author ddj 2026年09月11号
8966
+ * @param path 目标页签路径
8967
+ */
8968
+ const toggleTabPin = (path) => {
8969
+ const target = tabsRef.current.find((t) => t.path === path);
8970
+ if (!target) return;
8971
+ setTabs((prev) => togglePin(prev, path));
8972
+ setStatus(target.pinned ? "已取消固定:" + baseNameOf(path) : "已固定:" + baseNameOf(path));
8973
+ };
8974
+ /** 关闭当前活动页签(Ctrl+F4 与菜单动作共用;无页签时提示)。 */
8975
+ const closeActiveTab = () => {
8976
+ if (!activeRef.current) {
8977
+ setStatus("无打开的文件");
8978
+ return;
8979
+ }
8980
+ closeTab(activeRef.current);
8981
+ };
8982
+ /**
8983
+ * 页签右键菜单动作表(按菜单条目 id 分派;全部读 ref 取最新状态,防陈旧闭包)。
8984
+ * @author ddj 2026年09月11号
8985
+ * @param path 右键目标页签路径
8986
+ * @returns id → 动作
8987
+ */
8988
+ const tabMenuActions = (path) => ({
8989
+ "add-to-conversation": () => addRefToChat(path),
8990
+ close: () => closeTab(path),
8991
+ "close-others": () => closeTabs(closeOthers(tabsRef.current, path, activeRef.current), "已关闭其他页签"),
8992
+ "close-right": () => closeTabs(closeRight(tabsRef.current, path, activeRef.current), "已关闭右侧页签"),
8993
+ "close-saved": () => closeTabs(closeSaved(tabsRef.current, dirtyRef.current, activeRef.current), "已关闭已保存页签"),
8994
+ "close-all": () => closeTabs(closeAll(tabsRef.current, activeRef.current), "已关闭全部页签"),
8995
+ "copy-path": () => copyText(absoluteOf(path, cwd), "已复制路径"),
8996
+ "copy-relative-path": () => copyText(relativeOf(path, cwd), "已复制相对路径"),
8997
+ "reveal-in-os": () => openInExplorer(path),
8998
+ "reveal-in-view": () => revealTabInView(path),
8999
+ "toggle-pinned": () => toggleTabPin(path)
9000
+ });
9001
+ /**
9002
+ * 构建页签右键菜单条目(数据来自 tabMenu 纯函数;此处只注入键位与可用性快照)。
9003
+ * @author ddj 2026年09月11号
9004
+ * @returns ContextMenu 的 entries
9005
+ */
9006
+ const tabMenuEntries = () => {
9007
+ if (!tabMenu) return [];
9008
+ const actions = tabMenuActions(tabMenu.path);
9009
+ return buildTabMenu({
9010
+ path: tabMenu.path,
9011
+ tabs: tabsRef.current,
9012
+ active: activeRef.current,
9013
+ dirty: dirtyRef.current,
9014
+ cwd,
9015
+ hasSession: Boolean(sessionId),
9016
+ canAddToConversation: Boolean(addToConversation),
9017
+ closeChord: chordOf("edrv.closeTab")
9018
+ }).map((entry) => Object.assign({}, entry, { onClick: actions[entry.id] }));
9019
+ };
9020
+ /**
8036
9021
  * 在光标处按片段语法展开插入(Monaco 原生 snippet 控制器解析 ${1:占位} 与 $TM_* 变量)。
8037
9022
  * @author ddj 2026年09月10号
8038
9023
  * @param entry 片段条目
@@ -8125,8 +9110,8 @@ window.__ModuleLoader__.load({
8125
9110
  }
8126
9111
  }, tabs.map((t) => react.default.createElement("div", {
8127
9112
  key: t.path,
8128
- className: "edrv-tab" + (t.path === active ? " edrv-tab-active" : ""),
8129
- title: t.path,
9113
+ className: "edrv-tab" + (t.path === active ? " edrv-tab-active" : "") + (t.pinned ? " edrv-tab-pinned" : ""),
9114
+ title: t.pinned ? t.path + "(已固定)" : t.path,
8130
9115
  onClick: () => {
8131
9116
  if (t.path !== active) {
8132
9117
  flushSave();
@@ -8144,15 +9129,20 @@ window.__ModuleLoader__.load({
8144
9129
  path: t.path
8145
9130
  });
8146
9131
  }
8147
- }, react.default.createElement("span", { style: {
9132
+ }, t.pinned ? react.default.createElement("span", {
9133
+ className: "edrv-tab-pin",
9134
+ title: "已固定",
9135
+ "aria-label": "已固定"
9136
+ }, "📌") : null, react.default.createElement("span", { style: {
8148
9137
  overflow: "hidden",
8149
9138
  textOverflow: "ellipsis",
8150
9139
  maxWidth: 180
8151
9140
  } }, t.path.split(/[\\/]/).pop() || t.path), dirtyMap[t.path] ? react.default.createElement("span", {
8152
9141
  className: "edrv-tab-star",
8153
9142
  title: "未保存修改"
8154
- }, "*") : null, react.default.createElement("span", {
9143
+ }, "*") : null, t.pinned ? null : react.default.createElement("span", {
8155
9144
  className: "edrv-tab-x",
9145
+ title: "关闭",
8156
9146
  onClick: (e) => {
8157
9147
  e.stopPropagation();
8158
9148
  closeTab(t.path);
@@ -8414,42 +9404,13 @@ window.__ModuleLoader__.load({
8414
9404
  setLauncherOpen(false);
8415
9405
  }
8416
9406
  })) : 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
- }
9407
+ const tabMenuEl = tabMenu ? react.default.createElement(ContextMenu, {
9408
+ key: "edrv-tab-menu",
9409
+ x: tabMenu.x,
9410
+ y: tabMenu.y,
9411
+ entries: tabMenuEntries(),
9412
+ onClose: dismissMenus
8436
9413
  }) : 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
9414
  const snippetPickerEl = snippetPicker ? react.default.createElement(SnippetsPicker, {
8454
9415
  mode: snippetPicker,
8455
9416
  sessionId,
@@ -8543,14 +9504,14 @@ window.__ModuleLoader__.load({
8543
9504
  "data-edrv-layout": "side",
8544
9505
  className: "edrv-view-side",
8545
9506
  style: Object.assign({}, baseStyle, { height: "100%" })
8546
- }, editorRow, menuBackdrop, tabMenuEl, paletteEl, snippetPickerEl) : react.default.createElement("div", {
9507
+ }, editorRow, tabMenuEl, paletteEl, snippetPickerEl) : react.default.createElement("div", {
8547
9508
  ref: viewRootRef,
8548
9509
  "data-edrv-view": "1",
8549
9510
  style: Object.assign({}, baseStyle, {
8550
9511
  height: "var(--edrv-editor-height, 100%)",
8551
9512
  maxHeight: "var(--edrv-editor-height, 100%)"
8552
9513
  })
8553
- }, editorRow, menuBackdrop, tabMenuEl, paletteEl, snippetPickerEl);
9514
+ }, editorRow, tabMenuEl, paletteEl, snippetPickerEl);
8554
9515
  }
8555
9516
  //#endregion
8556
9517
  //#region src/client/ui/DiffBadge.ts
@@ -11895,7 +12856,8 @@ window.__ModuleLoader__.load({
11895
12856
  },
11896
12857
  openSession: (id) => service("sessions").open(id),
11897
12858
  reference: async (sessionId, path, appearance) => {
11898
- return await add.appendReference(sessionId, path, void 0, appearance) !== "unavailable";
12859
+ const outcome = await add.appendReference(sessionId, path, void 0, appearance);
12860
+ return outcome === "ok" || outcome === "busy";
11899
12861
  },
11900
12862
  choose: (title, folder) => chooseWorkspace(title, folder),
11901
12863
  openEditor: (path, line, column) => {
@@ -12281,101 +13243,6 @@ window.__ModuleLoader__.load({
12281
13243
  };
12282
13244
  }
12283
13245
  //#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
13246
  //#region src/client/sidebar/contextMenu.ts
12380
13247
  /** 校验菜单项并写入注册表(缺 id/run/label 抛 TypeError)。 */
12381
13248
  function itemRegister(entries, notify, item) {
@@ -12706,6 +13573,10 @@ window.__ModuleLoader__.load({
12706
13573
  ".pnpm",
12707
13574
  ".pnpm-store"
12708
13575
  ]);
13576
+ /** 「在资源管理器视图中显示」高亮时长与定位重试上限(目录懒加载需等待行渲染)。 */
13577
+ const REVEAL_HIGHLIGHT_MS = 2e3;
13578
+ const REVEAL_RETRY_MAX = 6;
13579
+ const REVEAL_RETRY_MS = 120;
12709
13580
  /**
12710
13581
  * 目录图标元素(官方 IconFolderOpen16/Close16)。
12711
13582
  * @author ddj 2026年09月10号
@@ -12759,6 +13630,15 @@ window.__ModuleLoader__.load({
12759
13630
  return react.default.createElement(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 14 });
12760
13631
  }
12761
13632
  /**
13633
+ * 转义 CSS 属性选择器的值(路径含引号/反斜杠时避免选择器语法错误)。
13634
+ * @author ddj 2026年09月11号
13635
+ * @param value 原始值
13636
+ * @returns 可安全嵌入 `[attr="…"]` 的字符串
13637
+ */
13638
+ function cssEscape(value) {
13639
+ return String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
13640
+ }
13641
+ /**
12762
13642
  * 目录树面板主体(SWR:有缓存先渲染,无缓存才显示加载态;加载总在后台)。
12763
13643
  * @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath/fileMenuItems/notify)
12764
13644
  */
@@ -12775,6 +13655,7 @@ window.__ModuleLoader__.load({
12775
13655
  const [loading, setLoading] = react.default.useState({});
12776
13656
  const [errors, setErrors] = react.default.useState({});
12777
13657
  const [menu, setMenu] = react.default.useState(null);
13658
+ const [revealPath, setRevealPath] = react.default.useState(null);
12778
13659
  const tokensRef = react.default.useRef({});
12779
13660
  const expandedRef = react.default.useRef({});
12780
13661
  expandedRef.current = expanded;
@@ -12785,6 +13666,10 @@ window.__ModuleLoader__.load({
12785
13666
  const saveTimerRef = react.default.useRef(null);
12786
13667
  const loadDirRef = react.default.useRef(null);
12787
13668
  const refreshRef = react.default.useRef(null);
13669
+ const revealTimerRef = react.default.useRef(null);
13670
+ const revealTryRef = react.default.useRef(0);
13671
+ const revealInTreeRef = react.default.useRef(null);
13672
+ const treeRef = react.default.useRef(null);
12788
13673
  /** 渲染取数:内存态 → 本地条目缓存 → null(显示加载态)。 */
12789
13674
  const entriesOf = (rel) => dirsRef.current[rel] ?? entriesCacheGet(scope, rel) ?? null;
12790
13675
  /** 预取子目录:≤4 个、排除重型目录、缓存新鲜跳过、已加载跳过、不级联。 */
@@ -12850,6 +13735,45 @@ window.__ModuleLoader__.load({
12850
13735
  setExpanded((prev) => Object.assign({}, prev, { [rel]: true }));
12851
13736
  loadDir(rel, { prefetch: true });
12852
13737
  };
13738
+ /**
13739
+ * 尝试把目标行滚入可见并高亮;行尚未渲染(祖先目录仍在加载)时按上限重试。
13740
+ * @author ddj 2026年09月11号
13741
+ * @param path 目标相对路径
13742
+ */
13743
+ const tryReveal = (path) => {
13744
+ const row = treeRef.current?.querySelector?.("[data-edrv-path=\"" + cssEscape(path) + "\"]");
13745
+ if (!row) {
13746
+ revealTryRef.current += 1;
13747
+ if (revealTryRef.current < REVEAL_RETRY_MAX) window.setTimeout(() => tryReveal(path), REVEAL_RETRY_MS);
13748
+ return;
13749
+ }
13750
+ row.scrollIntoView?.({ block: "center" });
13751
+ setRevealPath(path);
13752
+ if (revealTimerRef.current) clearTimeout(revealTimerRef.current);
13753
+ revealTimerRef.current = window.setTimeout(() => {
13754
+ revealTimerRef.current = null;
13755
+ setRevealPath(null);
13756
+ }, REVEAL_HIGHLIGHT_MS);
13757
+ };
13758
+ /**
13759
+ * 在资源管理器视图中显示:展开全部祖先目录并定位高亮目标文件。
13760
+ * 目录条目懒加载,故先展开祖先并触发加载,再由 tryReveal 轮询等行渲染。
13761
+ * @author ddj 2026年09月11号
13762
+ * @param path 目标相对路径
13763
+ */
13764
+ const revealInTree = (path) => {
13765
+ if (!path) return;
13766
+ const ancestors = ancestorDirsOf(path);
13767
+ revealTryRef.current = 0;
13768
+ setExpanded((prev) => {
13769
+ const next = Object.assign({}, prev);
13770
+ for (const dir of ancestors) next[dir] = true;
13771
+ return next;
13772
+ });
13773
+ for (const dir of ancestors) loadDir(dir, { prefetch: false });
13774
+ tryReveal(path);
13775
+ };
13776
+ revealInTreeRef.current = revealInTree;
12853
13777
  const refresh = () => {
12854
13778
  const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
12855
13779
  tokensRef.current = {};
@@ -12879,6 +13803,7 @@ window.__ModuleLoader__.load({
12879
13803
  setErrors({});
12880
13804
  setMenu(null);
12881
13805
  setRoot(null);
13806
+ setRevealPath(null);
12882
13807
  const cached = scope ? explorerLoad(scope) : null;
12883
13808
  const restored = cached?.expanded ?? [];
12884
13809
  if (cached?.root) setRoot(cached.root);
@@ -12920,6 +13845,20 @@ window.__ModuleLoader__.load({
12920
13845
  window.addEventListener("edrv:refresh", onRefresh);
12921
13846
  return () => window.removeEventListener("edrv:refresh", onRefresh);
12922
13847
  }, []);
13848
+ react.default.useEffect(() => {
13849
+ const onReveal = (event) => {
13850
+ const path = event?.detail?.path;
13851
+ if (typeof path === "string" && path) revealInTreeRef.current?.(path);
13852
+ };
13853
+ window.addEventListener("edrv:reveal-path", onReveal);
13854
+ return () => {
13855
+ window.removeEventListener("edrv:reveal-path", onReveal);
13856
+ if (revealTimerRef.current) {
13857
+ clearTimeout(revealTimerRef.current);
13858
+ revealTimerRef.current = null;
13859
+ }
13860
+ };
13861
+ }, []);
12923
13862
  react.default.useEffect(() => {
12924
13863
  if (!scope) return;
12925
13864
  const timer = setInterval(() => {
@@ -12937,8 +13876,9 @@ window.__ModuleLoader__.load({
12937
13876
  const dim = e.type === "other";
12938
13877
  return react.default.createElement("div", {
12939
13878
  key: e.path,
12940
- className: "edrv-tree-row" + (active ? " edrv-tree-active" : "") + (contextTarget ? " edrv-tree-context" : ""),
13879
+ className: "edrv-tree-row" + (active ? " edrv-tree-active" : "") + (contextTarget ? " edrv-tree-context" : "") + (revealPath === e.path ? " edrv-tree-reveal" : ""),
12941
13880
  title: e.path,
13881
+ "data-edrv-path": e.path,
12942
13882
  style: { paddingLeft: 6 + depth * 14 },
12943
13883
  onClick: () => {
12944
13884
  if (isDir) toggle(e.path);
@@ -13014,7 +13954,10 @@ window.__ModuleLoader__.load({
13014
13954
  className: "edrv-side-btn",
13015
13955
  title: "刷新目录树",
13016
13956
  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", {
13957
+ }, refreshIconEl())), react.default.createElement("div", {
13958
+ className: "edrv-tree",
13959
+ ref: treeRef
13960
+ }, errorText ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(errorText)), react.default.createElement("button", {
13018
13961
  className: "edrv-side-btn",
13019
13962
  onClick: () => refresh()
13020
13963
  }, "重试")) : null, rowsOf("", 0)), menu && menuEntries.length ? react.default.createElement(ContextMenu, {