dsh-vscode-mode 0.3.0 → 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 +16 -5
- package/lib/client.js +1069 -200
- 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 +37 -7
- 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
|
* 返回默认键位的独立副本(调用方修改不污染目录)。
|
|
@@ -2550,14 +2551,34 @@ window.__ModuleLoader__.load({
|
|
|
2550
2551
|
}
|
|
2551
2552
|
//#endregion
|
|
2552
2553
|
//#region src/client/editorModelState.ts
|
|
2554
|
+
/** Monaco 编辑器实例选择器(限定在插件编辑器行内,排除官方预览等其它 Monaco)。 */
|
|
2555
|
+
const EDITOR_MODEL_SELECTOR = ".edrv-editor-row .monaco-editor";
|
|
2553
2556
|
/**
|
|
2554
|
-
*
|
|
2557
|
+
* 已打开文件页签选择器(页签栏在编辑器行内;空态时无此节点)。
|
|
2558
|
+
* 判据同样不依赖 Monaco:图片/PDF 页签没有 Monaco 实例,但仍是「有页签可关」。
|
|
2559
|
+
*/
|
|
2560
|
+
const TAB_SELECTOR = ".edrv-editor-row .edrv-tabs .edrv-tab";
|
|
2561
|
+
/**
|
|
2562
|
+
* 当前文档是否已有 Monaco 活动编辑器(编辑器打开着文件而非空态)。
|
|
2563
|
+
* 判据为插件编辑器行内存在 Monaco 实例:与输入实现(textarea / EditContext)无关,
|
|
2564
|
+
* 空态(无文件页签)时 Monaco 未创建 → 返回 false,与旧语义一致。
|
|
2555
2565
|
* @author ddj 2026年09月10号
|
|
2556
|
-
* @returns
|
|
2566
|
+
* @returns 是否存在活动编辑器
|
|
2557
2567
|
*/
|
|
2558
2568
|
function hasEditorModel() {
|
|
2559
2569
|
if (typeof document === "undefined") return false;
|
|
2560
|
-
return document.querySelector(
|
|
2570
|
+
return document.querySelector(EDITOR_MODEL_SELECTOR) !== null;
|
|
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;
|
|
2561
2582
|
}
|
|
2562
2583
|
//#endregion
|
|
2563
2584
|
//#region src/client/ui/commandCatalog.ts
|
|
@@ -2637,6 +2658,24 @@ window.__ModuleLoader__.load({
|
|
|
2637
2658
|
};
|
|
2638
2659
|
}
|
|
2639
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
|
+
/**
|
|
2640
2679
|
* 编辑器内置指令目录(顺序 = 快捷键设置页展示顺序)。
|
|
2641
2680
|
* 前置 8 条的键位由 EditorView / QuickOpen 自行 capture 监听(历史实现),
|
|
2642
2681
|
* 故不进 BRIDGE_COMMANDS,避免同一按键双执行。
|
|
@@ -2772,7 +2811,8 @@ window.__ModuleLoader__.load({
|
|
|
2772
2811
|
const BRIDGE_COMMANDS = [
|
|
2773
2812
|
nextEditorRowDef(),
|
|
2774
2813
|
prevEditorRowDef(),
|
|
2775
|
-
addSelectionRefDef()
|
|
2814
|
+
addSelectionRefDef(),
|
|
2815
|
+
closeTabDef()
|
|
2776
2816
|
];
|
|
2777
2817
|
/**
|
|
2778
2818
|
* 命令栏自身的命令(避免循环依赖,由 commandBridge 注入 run 后注册)。
|
|
@@ -4242,8 +4282,10 @@ window.__ModuleLoader__.load({
|
|
|
4242
4282
|
entries: "edrv.cache.entries.v2.",
|
|
4243
4283
|
/** 编辑区视图状态(viewStateCache v1)。 */
|
|
4244
4284
|
viewstate: "edrv.cache.viewstate.v1.",
|
|
4245
|
-
/** 编辑器页签/活动文件(EditorView
|
|
4246
|
-
editor: "edrv.editor.
|
|
4285
|
+
/** 编辑器页签/活动文件(EditorView v3:页签带 pinned 固定标记)。 */
|
|
4286
|
+
editor: "edrv.editor.v3.",
|
|
4287
|
+
/** 旧编辑器页签键(v2:页签为纯路径数组)——仅用于一次性回读迁移,不再写入。 */
|
|
4288
|
+
editorLegacy: "edrv.editor.v2.",
|
|
4247
4289
|
/** 侧边栏状态(EditorView;按布局 side/central 追加段)。 */
|
|
4248
4290
|
sidebar: "edrv.sidebar.",
|
|
4249
4291
|
/** 搜索面板条件(SearchPanel v1)。 */
|
|
@@ -4398,7 +4440,7 @@ window.__ModuleLoader__.load({
|
|
|
4398
4440
|
}
|
|
4399
4441
|
/** 参与迁移的 localStorage 键前缀(编辑区全部按会话持久化的旧键)。 */
|
|
4400
4442
|
const MIGRATE_PREFIXES = [
|
|
4401
|
-
CACHE_KEY.
|
|
4443
|
+
CACHE_KEY.editorLegacy,
|
|
4402
4444
|
CACHE_KEY.viewstate,
|
|
4403
4445
|
CACHE_KEY.sidebar + "side.",
|
|
4404
4446
|
CACHE_KEY.sidebar,
|
|
@@ -4621,19 +4663,128 @@ window.__ModuleLoader__.load({
|
|
|
4621
4663
|
//#endregion
|
|
4622
4664
|
//#region src/client/addToConversation.ts
|
|
4623
4665
|
/** 把追加引用结果映射为可读文案(ok 用 okText;busy 提示已降级纯文本;unavailable 提示不可用)。
|
|
4624
|
-
* @author ddj 2026年09月03号
|
|
4666
|
+
* @author ddj 2026年09月03号 / 2026年09月11号
|
|
4625
4667
|
* @param outcome 追加结果状态
|
|
4626
4668
|
* @param okText 成功文案(如「已添加文件引用」/「已添加文件夹引用」)
|
|
4627
4669
|
* @returns 状态栏/通知用文案
|
|
4628
4670
|
*/
|
|
4629
4671
|
function statusOfAdd(outcome, okText) {
|
|
4630
4672
|
if (outcome === "ok") return okText;
|
|
4631
|
-
if (outcome === "busy") return okText + "
|
|
4673
|
+
if (outcome === "busy") return okText + "(已降级纯文本)";
|
|
4674
|
+
if (outcome === "failed") return "添加引用失败(输入框忙或未就绪,请重试)";
|
|
4632
4675
|
return "无法添加到对话(无会话或输入框不可用)";
|
|
4633
4676
|
}
|
|
4634
4677
|
/** DSH reference source 名(dsh-client-ui-reference 注册的 @file/@session 统一源)。 */
|
|
4635
4678
|
const REF_SOURCE = "reference";
|
|
4636
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
|
+
/**
|
|
4637
4788
|
* 生成 DSH @file 语法引用串:cwd 相对化、\ → /、含空白时按 @"path" 语法加引号。
|
|
4638
4789
|
* cwd 外/无法相对化的路径回退原路径。
|
|
4639
4790
|
* @author ddj 2026年08月25号
|
|
@@ -4702,20 +4853,21 @@ window.__ModuleLoader__.load({
|
|
|
4702
4853
|
const conversation = ctx.get("conversation");
|
|
4703
4854
|
const actx = sessions?.scope?.(sessionId);
|
|
4704
4855
|
const shell = actx && conversation?.input?.for?.(actx);
|
|
4705
|
-
return shell && typeof shell.
|
|
4856
|
+
return shell && typeof shell.insertReference === "function" ? shell : void 0;
|
|
4706
4857
|
} catch {
|
|
4707
4858
|
return;
|
|
4708
4859
|
}
|
|
4709
4860
|
}
|
|
4710
|
-
/**
|
|
4711
|
-
* @author ddj 2026年08月25号 */
|
|
4861
|
+
/** 取当前草稿快照(长度/版本/chip 列表);无输入门面时返回 null。
|
|
4862
|
+
* @author ddj 2026年08月25号 / 2026年09月11号 */
|
|
4712
4863
|
function draftCursor(input) {
|
|
4713
4864
|
try {
|
|
4714
4865
|
const s = input?.state?.getSnapshot?.();
|
|
4715
4866
|
if (!s) return null;
|
|
4716
4867
|
return {
|
|
4717
4868
|
draft: String(s.draft ?? ""),
|
|
4718
|
-
draftRev: Number(s.draftRev ?? 0)
|
|
4869
|
+
draftRev: Number(s.draftRev ?? 0),
|
|
4870
|
+
occurrences: Array.isArray(s.occurrences) ? s.occurrences : void 0
|
|
4719
4871
|
};
|
|
4720
4872
|
} catch {
|
|
4721
4873
|
return null;
|
|
@@ -4740,9 +4892,38 @@ window.__ModuleLoader__.load({
|
|
|
4740
4892
|
return false;
|
|
4741
4893
|
}
|
|
4742
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
|
+
}
|
|
4743
4919
|
/**
|
|
4744
4920
|
* 创建「添加到对话」动作集(apply 阶段构建一次,随 props 传给 EditorView)。
|
|
4745
|
-
*
|
|
4921
|
+
*
|
|
4922
|
+
* 插入位置 = 输入框**当前光标**(`caretSpan()`,detect 坐标;无光标提示时回落文档末尾),
|
|
4923
|
+
* 不再固定追加到最末尾。坐标一律用 **detect 投影**(见 detectEndOf 的缺陷说明);
|
|
4924
|
+
* 两条写入通道都失败时**不写任何内容**并返回 'failed' —— 旧实现在此处退回 `setDraft`
|
|
4925
|
+
* 整篇重建,会把既有 chip 全部销毁(连续添加多个引用即触发)。
|
|
4926
|
+
* @author ddj 2026年08月25号 / 2026年09月11号
|
|
4746
4927
|
* @param ctx 客户端服务上下文(sessions + conversation)
|
|
4747
4928
|
* @returns 动作集
|
|
4748
4929
|
*/
|
|
@@ -4753,16 +4934,16 @@ window.__ModuleLoader__.load({
|
|
|
4753
4934
|
const cur = draftCursor(input);
|
|
4754
4935
|
if (!cur) return "unavailable";
|
|
4755
4936
|
const { reference, mention } = buildFileRef(path, cwdOf(ctx, sessionId), range, appearance);
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4937
|
+
const at = insertOffsetOf(caretOf(input), detectEndOf(cur));
|
|
4938
|
+
const span = {
|
|
4939
|
+
start: at,
|
|
4940
|
+
end: at,
|
|
4759
4941
|
draftRev: cur.draftRev
|
|
4760
|
-
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
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";
|
|
4766
4947
|
};
|
|
4767
4948
|
return { appendReference };
|
|
4768
4949
|
}
|
|
@@ -6281,6 +6462,567 @@ window.__ModuleLoader__.load({
|
|
|
6281
6462
|
return (0, react_dom.createPortal)(react.default.createElement("div", { "data-edrv-view": "1" }, dialog, createDialog), document.body);
|
|
6282
6463
|
}
|
|
6283
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
|
|
6284
7026
|
//#region src/client/ui/EditorView.ts
|
|
6285
7027
|
/**
|
|
6286
7028
|
* dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
|
|
@@ -6384,7 +7126,8 @@ window.__ModuleLoader__.load({
|
|
|
6384
7126
|
const modelsRef = react.default.useRef(null);
|
|
6385
7127
|
if (!modelsRef.current) modelsRef.current = modelsForScope(scope);
|
|
6386
7128
|
const viewStatesRef = react.default.useRef({});
|
|
6387
|
-
const saveTimerRef = react.default.useRef(
|
|
7129
|
+
const saveTimerRef = react.default.useRef(createSaveTimer());
|
|
7130
|
+
const savingRef = react.default.useRef(/* @__PURE__ */ new Set());
|
|
6388
7131
|
const loadSeqRef = react.default.useRef(0);
|
|
6389
7132
|
const programmaticRef = react.default.useRef(false);
|
|
6390
7133
|
const restoredScopeRef = react.default.useRef(null);
|
|
@@ -6400,6 +7143,10 @@ window.__ModuleLoader__.load({
|
|
|
6400
7143
|
const rowNavMoveRef = react.default.useRef(false);
|
|
6401
7144
|
const activeRef = react.default.useRef(null);
|
|
6402
7145
|
activeRef.current = active;
|
|
7146
|
+
const tabsRef = react.default.useRef([]);
|
|
7147
|
+
tabsRef.current = tabs;
|
|
7148
|
+
const dirtyRef = react.default.useRef({});
|
|
7149
|
+
dirtyRef.current = dirtyMap;
|
|
6403
7150
|
const tabsHostRef = react.default.useRef(null);
|
|
6404
7151
|
const [navTick, setNavTick] = react.default.useState(0);
|
|
6405
7152
|
const hoverRegionsRef = react.default.useRef([]);
|
|
@@ -6448,10 +7195,7 @@ window.__ModuleLoader__.load({
|
|
|
6448
7195
|
}, [pendingRegions]);
|
|
6449
7196
|
const sum = react.default.useMemo(() => summarize(Object.values(records)), [records]);
|
|
6450
7197
|
const addTab = (path, select) => {
|
|
6451
|
-
setTabs((prev) =>
|
|
6452
|
-
if (prev.some((t) => t.path === path)) return prev;
|
|
6453
|
-
return prev.concat([{ path }]);
|
|
6454
|
-
});
|
|
7198
|
+
setTabs((prev) => insertTab(prev, path));
|
|
6455
7199
|
if (select) setActive(path);
|
|
6456
7200
|
};
|
|
6457
7201
|
/**
|
|
@@ -6577,9 +7321,14 @@ window.__ModuleLoader__.load({
|
|
|
6577
7321
|
};
|
|
6578
7322
|
navBackRef.current = navBack;
|
|
6579
7323
|
navForwardRef.current = navForward;
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
7324
|
+
/**
|
|
7325
|
+
* 关闭页签的收尾(不动作状态):活动页签先存视图状态与导航历史;
|
|
7326
|
+
* PDF 页签销毁面板控制器并清 base64 缓存(未保存注释随之丢弃,脏点已提示)。
|
|
7327
|
+
* @author ddj 2026年09月11号
|
|
7328
|
+
* @param path 待关闭页签路径
|
|
7329
|
+
*/
|
|
7330
|
+
const releaseTab = (path) => {
|
|
7331
|
+
if (path === activeRef.current) {
|
|
6583
7332
|
saveViewState(path);
|
|
6584
7333
|
recordNav();
|
|
6585
7334
|
}
|
|
@@ -6589,13 +7338,44 @@ window.__ModuleLoader__.load({
|
|
|
6589
7338
|
pdfCtlRef.current.delete(path);
|
|
6590
7339
|
}
|
|
6591
7340
|
pdfB64CacheRef.current.delete(path);
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
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 + " 个)");
|
|
6599
7379
|
};
|
|
6600
7380
|
/**
|
|
6601
7381
|
* 在已打开页签间循环切换(文件分页归编辑器自带页签栏)。
|
|
@@ -6990,12 +7770,13 @@ window.__ModuleLoader__.load({
|
|
|
6990
7770
|
modelsRef.current = modelsForScope(scope);
|
|
6991
7771
|
viewStatesRef.current = viewStatesLoad(scope);
|
|
6992
7772
|
try {
|
|
6993
|
-
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));
|
|
6994
7774
|
if (raw) {
|
|
6995
7775
|
const saved = JSON.parse(raw);
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
7776
|
+
const restored = normalizeTabs(saved?.tabs);
|
|
7777
|
+
if (restored.length) {
|
|
7778
|
+
setTabs(restored);
|
|
7779
|
+
setActive(pickActive(restored, saved?.active));
|
|
6999
7780
|
}
|
|
7000
7781
|
}
|
|
7001
7782
|
} catch (e) {}
|
|
@@ -7012,10 +7793,14 @@ window.__ModuleLoader__.load({
|
|
|
7012
7793
|
react.default.useEffect(() => {
|
|
7013
7794
|
if (!sessionId) return;
|
|
7014
7795
|
try {
|
|
7015
|
-
|
|
7016
|
-
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 }),
|
|
7017
7801
|
active
|
|
7018
|
-
}
|
|
7802
|
+
};
|
|
7803
|
+
localStorage.setItem(CACHE_KEY.editor + String(scope), JSON.stringify(payload));
|
|
7019
7804
|
} catch (e) {}
|
|
7020
7805
|
}, [
|
|
7021
7806
|
tabs,
|
|
@@ -7168,6 +7953,7 @@ window.__ModuleLoader__.load({
|
|
|
7168
7953
|
["edrv.command.navigateForward", () => navForwardRef.current?.()],
|
|
7169
7954
|
["edrv.command.nextTab", () => cycleTabRef.current?.(1)],
|
|
7170
7955
|
["edrv.command.prevTab", () => cycleTabRef.current?.(-1)],
|
|
7956
|
+
["edrv.command.closeTab", () => closeActiveTab()],
|
|
7171
7957
|
["edrv.command.nextEditorRow", () => moveRow(1)],
|
|
7172
7958
|
["edrv.command.prevEditorRow", () => moveRow(-1)],
|
|
7173
7959
|
["edrv.command.goToDefinition", () => {
|
|
@@ -7283,14 +8069,6 @@ window.__ModuleLoader__.load({
|
|
|
7283
8069
|
setStatus("加载中…");
|
|
7284
8070
|
loadContent(active, sessionId);
|
|
7285
8071
|
}, [active, sessionId]);
|
|
7286
|
-
react.default.useEffect(() => {
|
|
7287
|
-
if (!tabMenu) return;
|
|
7288
|
-
const onKey = (e) => {
|
|
7289
|
-
if (e.key === "Escape") dismissMenus();
|
|
7290
|
-
};
|
|
7291
|
-
window.addEventListener("keydown", onKey, true);
|
|
7292
|
-
return () => window.removeEventListener("keydown", onKey, true);
|
|
7293
|
-
}, [tabMenu]);
|
|
7294
8072
|
react.default.useEffect(() => {
|
|
7295
8073
|
if (monaco || monacoErr) return;
|
|
7296
8074
|
let alive = true;
|
|
@@ -7350,11 +8128,14 @@ window.__ModuleLoader__.load({
|
|
|
7350
8128
|
}
|
|
7351
8129
|
return model;
|
|
7352
8130
|
};
|
|
8131
|
+
/**
|
|
8132
|
+
* 提交待执行的防抖保存(**真正执行保存**,不是取消)。
|
|
8133
|
+
* 语义与缺陷背景见 saveDebounce.ts:`schedule` 的返回值是只 clearTimeout 的 disposer,
|
|
8134
|
+
* 旧实现把它当「立即保存」调用 → 防抖窗口内切页签/关闭文件会静默丢改动。
|
|
8135
|
+
* @author ddj 2026年09月11号
|
|
8136
|
+
*/
|
|
7353
8137
|
const flushSave = () => {
|
|
7354
|
-
|
|
7355
|
-
saveTimerRef.current();
|
|
7356
|
-
saveTimerRef.current = null;
|
|
7357
|
-
}
|
|
8138
|
+
saveTimerRef.current?.flush();
|
|
7358
8139
|
};
|
|
7359
8140
|
const doSave = (silent) => {
|
|
7360
8141
|
if (!active) return;
|
|
@@ -7379,6 +8160,7 @@ window.__ModuleLoader__.load({
|
|
|
7379
8160
|
}
|
|
7380
8161
|
const text = ed.getValue();
|
|
7381
8162
|
if (!silent) setStatus("保存中…");
|
|
8163
|
+
savingRef.current.add(active);
|
|
7382
8164
|
rpc("edrv.save", {
|
|
7383
8165
|
sessionId,
|
|
7384
8166
|
path: active,
|
|
@@ -7399,15 +8181,53 @@ window.__ModuleLoader__.load({
|
|
|
7399
8181
|
}).catch((e) => {
|
|
7400
8182
|
setStatus("保存失败");
|
|
7401
8183
|
setError("保存异常:" + String(e));
|
|
8184
|
+
}).finally(() => {
|
|
8185
|
+
savingRef.current.delete(active);
|
|
7402
8186
|
});
|
|
7403
8187
|
};
|
|
7404
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
|
+
};
|
|
7405
8226
|
const onEdit = () => {
|
|
7406
8227
|
if (!editorRef.current || !active) return;
|
|
7407
8228
|
setDirtyMap((d) => Object.assign({}, d, { [active]: true }));
|
|
7408
8229
|
setStatus("编辑中…");
|
|
7409
|
-
|
|
7410
|
-
saveTimerRef.current = schedule(() => doSave(true), 700);
|
|
8230
|
+
saveTimerRef.current?.arm(schedule, 700, () => doSave(true));
|
|
7411
8231
|
};
|
|
7412
8232
|
onEditRef.current = onEdit;
|
|
7413
8233
|
react.default.useEffect(() => {
|
|
@@ -8029,6 +8849,97 @@ window.__ModuleLoader__.load({
|
|
|
8029
8849
|
openInExplorer
|
|
8030
8850
|
};
|
|
8031
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
|
+
/**
|
|
8032
8943
|
* 在光标处按片段语法展开插入(Monaco 原生 snippet 控制器解析 ${1:占位} 与 $TM_* 变量)。
|
|
8033
8944
|
* @author ddj 2026年09月10号
|
|
8034
8945
|
* @param entry 片段条目
|
|
@@ -8121,8 +9032,8 @@ window.__ModuleLoader__.load({
|
|
|
8121
9032
|
}
|
|
8122
9033
|
}, tabs.map((t) => react.default.createElement("div", {
|
|
8123
9034
|
key: t.path,
|
|
8124
|
-
className: "edrv-tab" + (t.path === active ? " edrv-tab-active" : ""),
|
|
8125
|
-
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,
|
|
8126
9037
|
onClick: () => {
|
|
8127
9038
|
if (t.path !== active) {
|
|
8128
9039
|
flushSave();
|
|
@@ -8140,15 +9051,20 @@ window.__ModuleLoader__.load({
|
|
|
8140
9051
|
path: t.path
|
|
8141
9052
|
});
|
|
8142
9053
|
}
|
|
8143
|
-
}, 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: {
|
|
8144
9059
|
overflow: "hidden",
|
|
8145
9060
|
textOverflow: "ellipsis",
|
|
8146
9061
|
maxWidth: 180
|
|
8147
9062
|
} }, t.path.split(/[\\/]/).pop() || t.path), dirtyMap[t.path] ? react.default.createElement("span", {
|
|
8148
9063
|
className: "edrv-tab-star",
|
|
8149
9064
|
title: "未保存修改"
|
|
8150
|
-
}, "*") : null, react.default.createElement("span", {
|
|
9065
|
+
}, "*") : null, t.pinned ? null : react.default.createElement("span", {
|
|
8151
9066
|
className: "edrv-tab-x",
|
|
9067
|
+
title: "关闭",
|
|
8152
9068
|
onClick: (e) => {
|
|
8153
9069
|
e.stopPropagation();
|
|
8154
9070
|
closeTab(t.path);
|
|
@@ -8410,42 +9326,13 @@ window.__ModuleLoader__.load({
|
|
|
8410
9326
|
setLauncherOpen(false);
|
|
8411
9327
|
}
|
|
8412
9328
|
})) : null;
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
top: Math.max(4, Math.min(y, (window.innerHeight || 600) - 176))
|
|
8420
|
-
});
|
|
8421
|
-
const menuBackdrop = tabMenu ? react.default.createElement("div", {
|
|
8422
|
-
style: {
|
|
8423
|
-
position: "fixed",
|
|
8424
|
-
inset: 0,
|
|
8425
|
-
zIndex: 70
|
|
8426
|
-
},
|
|
8427
|
-
onClick: dismissMenus,
|
|
8428
|
-
onContextMenu: (e) => {
|
|
8429
|
-
e.preventDefault();
|
|
8430
|
-
dismissMenus();
|
|
8431
|
-
}
|
|
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
|
|
8432
9335
|
}) : null;
|
|
8433
|
-
const tabMenuEl = tabMenu ? react.default.createElement("div", {
|
|
8434
|
-
className: "edrv-ctxmenu",
|
|
8435
|
-
style: menuPos(tabMenu.x, tabMenu.y)
|
|
8436
|
-
}, react.default.createElement("button", {
|
|
8437
|
-
className: "edrv-ctxmenu-item",
|
|
8438
|
-
onClick: () => {
|
|
8439
|
-
addRefToChat(tabMenu.path);
|
|
8440
|
-
dismissMenus();
|
|
8441
|
-
}
|
|
8442
|
-
}, "添加文件到对话"), react.default.createElement("div", { className: "edrv-ctxmenu-sep" }), react.default.createElement("button", {
|
|
8443
|
-
className: "edrv-ctxmenu-item edrv-ctxmenu-danger",
|
|
8444
|
-
onClick: () => {
|
|
8445
|
-
closeTab(tabMenu.path);
|
|
8446
|
-
dismissMenus();
|
|
8447
|
-
}
|
|
8448
|
-
}, "关闭标签页")) : null;
|
|
8449
9336
|
const snippetPickerEl = snippetPicker ? react.default.createElement(SnippetsPicker, {
|
|
8450
9337
|
mode: snippetPicker,
|
|
8451
9338
|
sessionId,
|
|
@@ -8539,14 +9426,14 @@ window.__ModuleLoader__.load({
|
|
|
8539
9426
|
"data-edrv-layout": "side",
|
|
8540
9427
|
className: "edrv-view-side",
|
|
8541
9428
|
style: Object.assign({}, baseStyle, { height: "100%" })
|
|
8542
|
-
}, editorRow,
|
|
9429
|
+
}, editorRow, tabMenuEl, paletteEl, snippetPickerEl) : react.default.createElement("div", {
|
|
8543
9430
|
ref: viewRootRef,
|
|
8544
9431
|
"data-edrv-view": "1",
|
|
8545
9432
|
style: Object.assign({}, baseStyle, {
|
|
8546
9433
|
height: "var(--edrv-editor-height, 100%)",
|
|
8547
9434
|
maxHeight: "var(--edrv-editor-height, 100%)"
|
|
8548
9435
|
})
|
|
8549
|
-
}, editorRow,
|
|
9436
|
+
}, editorRow, tabMenuEl, paletteEl, snippetPickerEl);
|
|
8550
9437
|
}
|
|
8551
9438
|
//#endregion
|
|
8552
9439
|
//#region src/client/ui/DiffBadge.ts
|
|
@@ -11891,7 +12778,8 @@ window.__ModuleLoader__.load({
|
|
|
11891
12778
|
},
|
|
11892
12779
|
openSession: (id) => service("sessions").open(id),
|
|
11893
12780
|
reference: async (sessionId, path, appearance) => {
|
|
11894
|
-
|
|
12781
|
+
const outcome = await add.appendReference(sessionId, path, void 0, appearance);
|
|
12782
|
+
return outcome === "ok" || outcome === "busy";
|
|
11895
12783
|
},
|
|
11896
12784
|
choose: (title, folder) => chooseWorkspace(title, folder),
|
|
11897
12785
|
openEditor: (path, line, column) => {
|
|
@@ -12277,101 +13165,6 @@ window.__ModuleLoader__.load({
|
|
|
12277
13165
|
};
|
|
12278
13166
|
}
|
|
12279
13167
|
//#endregion
|
|
12280
|
-
//#region src/client/ui/menuPosition.ts
|
|
12281
|
-
/**
|
|
12282
|
-
* 将菜单坐标限制在 viewport 内。
|
|
12283
|
-
* @author ddj 2026年08月28号
|
|
12284
|
-
* @param x 期望的 viewport x 坐标
|
|
12285
|
-
* @param y 期望的 viewport y 坐标
|
|
12286
|
-
* @param viewportWidth viewport 宽度
|
|
12287
|
-
* @param viewportHeight viewport 高度
|
|
12288
|
-
* @param menuWidth 菜单估算宽度
|
|
12289
|
-
* @param menuHeight 菜单估算高度
|
|
12290
|
-
* @returns 限制后的菜单位置
|
|
12291
|
-
*/
|
|
12292
|
-
function clampMenuPosition(x, y, viewportWidth, viewportHeight, menuWidth, menuHeight) {
|
|
12293
|
-
const maxLeft = Math.max(4, viewportWidth - menuWidth);
|
|
12294
|
-
const maxTop = Math.max(4, viewportHeight - menuHeight);
|
|
12295
|
-
return {
|
|
12296
|
-
left: Math.max(4, Math.min(x, maxLeft)),
|
|
12297
|
-
top: Math.max(4, Math.min(y, maxTop))
|
|
12298
|
-
};
|
|
12299
|
-
}
|
|
12300
|
-
//#endregion
|
|
12301
|
-
//#region src/client/ui/ContextMenu.ts
|
|
12302
|
-
/**
|
|
12303
|
-
* dsh-vscode-mode client — 通用浮动右键菜单(createElement 风格、类型化)。
|
|
12304
|
-
* 非阻塞式关闭:window capture 监听(点击菜单外/右键菜单外/Esc 关闭),
|
|
12305
|
-
* 不拦截菜单外事件,允许同一事件内由发起方切换/重开菜单。
|
|
12306
|
-
* 条目支持 danger/disabled/separator,复用既有 edrv-ctxmenu* 样式类。
|
|
12307
|
-
* 作者 ddj 2026-08-27
|
|
12308
|
-
*/
|
|
12309
|
-
/** 菜单估算宽高(clamp 防越出视口,与 EditorView menuPos 常量对齐)。 */
|
|
12310
|
-
const MENU_W = 224;
|
|
12311
|
-
const MENU_H = 176;
|
|
12312
|
-
/**
|
|
12313
|
-
* 浮动右键菜单。
|
|
12314
|
-
* @param props.x 视口 x 坐标
|
|
12315
|
-
* @param props.y 视口 y 坐标
|
|
12316
|
-
* @param props.entries 菜单项列表
|
|
12317
|
-
* @param props.onClose 关闭回调(backdrop 点击/右键/Esc/点击项后触发)
|
|
12318
|
-
*/
|
|
12319
|
-
function ContextMenu(props) {
|
|
12320
|
-
const { x, y, entries, onClose } = props;
|
|
12321
|
-
const menuRef = react.default.useRef(null);
|
|
12322
|
-
react.default.useEffect(() => {
|
|
12323
|
-
const onKey = (e) => {
|
|
12324
|
-
if (e.key === "Escape") onClose();
|
|
12325
|
-
};
|
|
12326
|
-
const onPointerDown = (e) => {
|
|
12327
|
-
if (e.button !== 0) return;
|
|
12328
|
-
if (menuRef.current && menuRef.current.contains(e.target)) return;
|
|
12329
|
-
onClose();
|
|
12330
|
-
};
|
|
12331
|
-
const onContextMenu = (e) => {
|
|
12332
|
-
if (menuRef.current && menuRef.current.contains(e.target)) e.preventDefault();
|
|
12333
|
-
onClose();
|
|
12334
|
-
};
|
|
12335
|
-
window.addEventListener("keydown", onKey, true);
|
|
12336
|
-
window.addEventListener("pointerdown", onPointerDown, true);
|
|
12337
|
-
window.addEventListener("contextmenu", onContextMenu, true);
|
|
12338
|
-
return () => {
|
|
12339
|
-
window.removeEventListener("keydown", onKey, true);
|
|
12340
|
-
window.removeEventListener("pointerdown", onPointerDown, true);
|
|
12341
|
-
window.removeEventListener("contextmenu", onContextMenu, true);
|
|
12342
|
-
};
|
|
12343
|
-
}, [onClose]);
|
|
12344
|
-
const position = clampMenuPosition(Number.isFinite(x) ? x : 4, Number.isFinite(y) ? y : 4, window.innerWidth || 800, window.innerHeight || 600, MENU_W, MENU_H);
|
|
12345
|
-
const children = [];
|
|
12346
|
-
for (const entry of entries) {
|
|
12347
|
-
if (entry.separator) {
|
|
12348
|
-
children.push(react.default.createElement("div", {
|
|
12349
|
-
key: "sep-" + entry.id,
|
|
12350
|
-
className: "edrv-ctxmenu-sep"
|
|
12351
|
-
}));
|
|
12352
|
-
continue;
|
|
12353
|
-
}
|
|
12354
|
-
const cls = "edrv-ctxmenu-item" + (entry.danger ? " edrv-ctxmenu-danger" : "") + (entry.disabled ? " edrv-ctxmenu-disabled" : "");
|
|
12355
|
-
children.push(react.default.createElement("button", {
|
|
12356
|
-
key: entry.id,
|
|
12357
|
-
className: cls,
|
|
12358
|
-
disabled: entry.disabled,
|
|
12359
|
-
onClick: () => {
|
|
12360
|
-
if (!entry.disabled) {
|
|
12361
|
-
entry.onClick?.();
|
|
12362
|
-
onClose();
|
|
12363
|
-
}
|
|
12364
|
-
}
|
|
12365
|
-
}, entry.label));
|
|
12366
|
-
}
|
|
12367
|
-
const overlay = react.default.createElement("div", { "data-edrv-view": "1" }, react.default.createElement("div", {
|
|
12368
|
-
className: "edrv-ctxmenu",
|
|
12369
|
-
ref: menuRef,
|
|
12370
|
-
style: position
|
|
12371
|
-
}, ...children));
|
|
12372
|
-
return typeof document === "undefined" ? overlay : (0, react_dom.createPortal)(overlay, document.body);
|
|
12373
|
-
}
|
|
12374
|
-
//#endregion
|
|
12375
13168
|
//#region src/client/sidebar/contextMenu.ts
|
|
12376
13169
|
/** 校验菜单项并写入注册表(缺 id/run/label 抛 TypeError)。 */
|
|
12377
13170
|
function itemRegister(entries, notify, item) {
|
|
@@ -12702,6 +13495,10 @@ window.__ModuleLoader__.load({
|
|
|
12702
13495
|
".pnpm",
|
|
12703
13496
|
".pnpm-store"
|
|
12704
13497
|
]);
|
|
13498
|
+
/** 「在资源管理器视图中显示」高亮时长与定位重试上限(目录懒加载需等待行渲染)。 */
|
|
13499
|
+
const REVEAL_HIGHLIGHT_MS = 2e3;
|
|
13500
|
+
const REVEAL_RETRY_MAX = 6;
|
|
13501
|
+
const REVEAL_RETRY_MS = 120;
|
|
12705
13502
|
/**
|
|
12706
13503
|
* 目录图标元素(官方 IconFolderOpen16/Close16)。
|
|
12707
13504
|
* @author ddj 2026年09月10号
|
|
@@ -12755,6 +13552,15 @@ window.__ModuleLoader__.load({
|
|
|
12755
13552
|
return react.default.createElement(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 14 });
|
|
12756
13553
|
}
|
|
12757
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
|
+
/**
|
|
12758
13564
|
* 目录树面板主体(SWR:有缓存先渲染,无缓存才显示加载态;加载总在后台)。
|
|
12759
13565
|
* @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath/fileMenuItems/notify)
|
|
12760
13566
|
*/
|
|
@@ -12771,6 +13577,7 @@ window.__ModuleLoader__.load({
|
|
|
12771
13577
|
const [loading, setLoading] = react.default.useState({});
|
|
12772
13578
|
const [errors, setErrors] = react.default.useState({});
|
|
12773
13579
|
const [menu, setMenu] = react.default.useState(null);
|
|
13580
|
+
const [revealPath, setRevealPath] = react.default.useState(null);
|
|
12774
13581
|
const tokensRef = react.default.useRef({});
|
|
12775
13582
|
const expandedRef = react.default.useRef({});
|
|
12776
13583
|
expandedRef.current = expanded;
|
|
@@ -12781,6 +13588,10 @@ window.__ModuleLoader__.load({
|
|
|
12781
13588
|
const saveTimerRef = react.default.useRef(null);
|
|
12782
13589
|
const loadDirRef = react.default.useRef(null);
|
|
12783
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);
|
|
12784
13595
|
/** 渲染取数:内存态 → 本地条目缓存 → null(显示加载态)。 */
|
|
12785
13596
|
const entriesOf = (rel) => dirsRef.current[rel] ?? entriesCacheGet(scope, rel) ?? null;
|
|
12786
13597
|
/** 预取子目录:≤4 个、排除重型目录、缓存新鲜跳过、已加载跳过、不级联。 */
|
|
@@ -12846,6 +13657,45 @@ window.__ModuleLoader__.load({
|
|
|
12846
13657
|
setExpanded((prev) => Object.assign({}, prev, { [rel]: true }));
|
|
12847
13658
|
loadDir(rel, { prefetch: true });
|
|
12848
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;
|
|
12849
13699
|
const refresh = () => {
|
|
12850
13700
|
const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
|
|
12851
13701
|
tokensRef.current = {};
|
|
@@ -12875,6 +13725,7 @@ window.__ModuleLoader__.load({
|
|
|
12875
13725
|
setErrors({});
|
|
12876
13726
|
setMenu(null);
|
|
12877
13727
|
setRoot(null);
|
|
13728
|
+
setRevealPath(null);
|
|
12878
13729
|
const cached = scope ? explorerLoad(scope) : null;
|
|
12879
13730
|
const restored = cached?.expanded ?? [];
|
|
12880
13731
|
if (cached?.root) setRoot(cached.root);
|
|
@@ -12916,6 +13767,20 @@ window.__ModuleLoader__.load({
|
|
|
12916
13767
|
window.addEventListener("edrv:refresh", onRefresh);
|
|
12917
13768
|
return () => window.removeEventListener("edrv:refresh", onRefresh);
|
|
12918
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
|
+
}, []);
|
|
12919
13784
|
react.default.useEffect(() => {
|
|
12920
13785
|
if (!scope) return;
|
|
12921
13786
|
const timer = setInterval(() => {
|
|
@@ -12933,8 +13798,9 @@ window.__ModuleLoader__.load({
|
|
|
12933
13798
|
const dim = e.type === "other";
|
|
12934
13799
|
return react.default.createElement("div", {
|
|
12935
13800
|
key: e.path,
|
|
12936
|
-
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" : ""),
|
|
12937
13802
|
title: e.path,
|
|
13803
|
+
"data-edrv-path": e.path,
|
|
12938
13804
|
style: { paddingLeft: 6 + depth * 14 },
|
|
12939
13805
|
onClick: () => {
|
|
12940
13806
|
if (isDir) toggle(e.path);
|
|
@@ -13010,7 +13876,10 @@ window.__ModuleLoader__.load({
|
|
|
13010
13876
|
className: "edrv-side-btn",
|
|
13011
13877
|
title: "刷新目录树",
|
|
13012
13878
|
onClick: refresh
|
|
13013
|
-
}, 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", {
|
|
13014
13883
|
className: "edrv-side-btn",
|
|
13015
13884
|
onClick: () => refresh()
|
|
13016
13885
|
}, "重试")) : null, rowsOf("", 0)), menu && menuEntries.length ? react.default.createElement(ContextMenu, {
|