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