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