dsh-vscode-mode 0.7.0 → 0.9.0
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 +37 -6
- package/assets/icon.svg +7 -0
- package/cordis.patch.yml +16 -0
- package/lib/client.js +2804 -300
- package/lib/client.js.map +1 -1
- package/lib/index.js +1975 -191
- package/lib/index.js.map +1 -1
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +4 -2
- package/src/ai/inline.ts +19 -2
- package/src/ai/svnTriage.ts +150 -0
- package/src/capture.ts +225 -51
- package/src/client/addToConversation.ts +203 -1
- package/src/client/compat.ts +134 -4
- package/src/client/imagePreview.ts +76 -2
- package/src/client/index.ts +65 -36
- package/src/client/monaco/lsp/lspClient.ts +46 -0
- package/src/client/monaco/lsp/providers.ts +245 -1
- package/src/client/nativeOpenStore.ts +91 -0
- package/src/client/officialSidebar.ts +11 -45
- package/src/client/outline/index.ts +1 -1
- package/src/client/pdf/pdfPanel.ts +18 -0
- package/src/client/pendingNav.ts +141 -0
- package/src/client/settingsContext.ts +6 -4
- package/src/client/sidebar/panels/FileExplorer.ts +2 -15
- package/src/client/sidebar/panels/SvnPanel.ts +682 -72
- package/src/client/sidebar/panels/index.ts +1 -1
- package/src/client/sidebar/panels/svnListBudget.ts +58 -0
- package/src/client/state/records.ts +7 -1
- package/src/client/styles/editor.css +47 -5
- package/src/client/svnStatus.ts +206 -1
- package/src/client/svnStore.ts +6 -1
- package/src/client/ui/AiSettings.ts +47 -14
- package/src/client/ui/DiffBox.ts +1 -1
- package/src/client/ui/DiffLauncher.ts +6 -4
- package/src/client/ui/EditorView.ts +149 -22
- package/src/client/ui/McpSettings.ts +30 -0
- package/src/client/ui/PerfSettings.ts +4 -1
- package/src/client/ui/SvnAiPlanDialog.ts +293 -0
- package/src/client/ui/icons.ts +91 -0
- package/src/compat.ts +26 -5
- package/src/dshVersion.ts +2 -1
- package/src/fileOpenSettings.ts +274 -49
- package/src/index.ts +18 -1
- package/src/lsp/client.ts +24 -0
- package/src/lsp/rpc.ts +57 -0
- package/src/lsp/server.ts +272 -2
- package/src/model.ts +2 -1
- package/src/perf.ts +90 -4
- package/src/remoteWorkspace.ts +177 -0
- package/src/routes.ts +13 -1
- package/src/rpc.ts +115 -39
- package/src/shared/ai.ts +12 -0
- package/src/shared/lsp.ts +79 -0
- package/src/shared/nativeOpen.ts +91 -0
- package/src/shared/rpc.ts +162 -2
- package/src/shared/svn.ts +296 -10
- package/src/shared/types.ts +5 -1
- package/src/svn.ts +235 -13
package/lib/client.js
CHANGED
|
@@ -29,10 +29,11 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
let react = require("react");
|
|
30
30
|
react = __toESM(react, 1);
|
|
31
31
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
32
|
+
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
32
33
|
let react_dom = require("react-dom");
|
|
33
34
|
let react_dom_client = require("react-dom/client");
|
|
34
35
|
//#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; overflow-y: hidden; scrollbar-width: thin; flex-shrink: 0; overscroll-behavior-x: contain; } /* overscroll-behavior-x: 横滚到边界不把滚动链传给页面(滚轮横滚接管更可控)@author ddj 2026年09月18号 */\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/* 跳转目标区域高亮(LSP 定义/引用、搜索命中落点):落点一眼可见,约 1.2s 后由 EditorView 自动清除。\n 用官方令牌并带回落值,不在插件作用域重定义 --dsw-* (否则永久脱离官方主题)。 */\n[data-edrv-view] .edrv-nav-target { background: var(--dsw-alias-interactive-bg-hover, rgba(79,193,255,.28)); border-radius: 2px; box-shadow: 0 0 0 1px var(--dsw-alias-interactive-primary, rgba(79,193,255,.55)); }\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/* Markdown 预览(需求 4):工具条 + 居中阅读栏;正文排版由官方 MarkdownText 自带的 --dsw-* 样式负责 */\n[data-edrv-view] .edrv-mdview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-mdview-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-mdview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n[data-edrv-view] .edrv-mdview-stage { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px 20px 28px; }\n[data-edrv-view] .edrv-mdview-doc { max-width: 880px; margin: 0 auto; width: 100%; }\n[data-edrv-view] .edrv-mdview-fallback { font-size: 12px; line-height: 1.7; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-mdview-pre { margin: 8px 0 0; padding: 10px 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }\n[data-edrv-view] .edrv-pb-mdbtn { height: 20px; padding: 0 8px; font-size: 11px; flex-shrink: 0; }\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/* ===== SVN 变更面板(工作副本状态列表;颜色分级对齐 TortoiseSVN 口径) @author ddj 2026-09-16 ===== */\n[data-edrv-view] .edrv-svn-toolbar { display: flex; align-items: center; gap: 12px; padding: 4px 10px 6px 12px; flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svn-check { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-check input { margin: 0; cursor: pointer; }\n[data-edrv-view] .edrv-svn-bulk { display: flex; align-items: center; gap: 6px; padding: 0 10px 6px 12px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-count { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svn-list { flex: 1; min-height: 0; overflow: auto; padding: 2px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-svn-group { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; margin: 2px 0; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; font-weight: 600; }\n[data-edrv-view] .edrv-svn-group-n { margin-left: auto; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-weight: 400; }\n[data-edrv-view] .edrv-svn-row { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; border-radius: 5px; cursor: default; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svn-path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svn-acts { display: none; align-items: center; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-row:hover .edrv-svn-acts { display: inline-flex; }\n[data-edrv-view] .edrv-svn-act { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; padding: 1px 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 14px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svn-act:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-svn-act-danger { color: var(--dsw-alias-state-error-primary, #d9534f); border-color: var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-svn-ch { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-weight: 700; }\n[data-edrv-view] .edrv-svn-ch-modified { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svn-ch-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); }\n[data-edrv-view] .edrv-svn-ch-deleted { color: var(--dsw-alias-label-secondary, #b91c1c); }\n[data-edrv-view] .edrv-svn-ch-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-svn-ch-plain { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 文件树行尾 SVN 状态徽标(与差异角标并列;方形字母,区别圆形差异计数) */\n[data-edrv-view] .edrv-tree-svn { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 4px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-modified { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-deleted { color: var(--dsw-alias-label-secondary, #b91c1c); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* ===== SVN 基线差异视图(BASE 左 / 工作区右,只读并排) @author ddj 2026-09-16 ===== */\n[data-edrv-view] .edrv-svndiff { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-svndiff-bar { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svndiff-title { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-svndiff-tag { padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svndiff-host { flex: 1 1 auto; min-height: 0; }\n[data-edrv-view] .edrv-svndiff-empty { display: flex; flex: 1 1 auto; align-items: center; justify-content: center; padding: 24px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 12px; text-align: center; }\n/* 差异整行色块与行首 +/- 指示符:vendored Monaco(diffEditorWidget2)静态 CSS 无\n .line-insert/.line-delete 背景规则,整行色块靠主题键 + 此处固定色兜底保证可见。\n 注意不要用 --dsw-alias-interactive-bg-hover 类 token:实测为 92% 透明中性色,肉眼不可见。\n 固定色取自研差异 UI 同色相(绿 #0f9d58 / 红 #d9534f),alpha 提到肉眼可辨。 */\n[data-edrv-view] .monaco-editor .line-insert { background: rgba(15, 157, 88, 0.18); }\n[data-edrv-view] .monaco-editor .line-delete { background: rgba(217, 83, 79, 0.16); }\n[data-edrv-view] .monaco-editor .gutter-insert { background: rgba(15, 157, 88, 0.35); }\n[data-edrv-view] .monaco-editor .gutter-delete { background: rgba(217, 83, 79, 0.35); }\n[data-edrv-view] .monaco-editor .insert-sign { color: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .monaco-editor .delete-sign { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* 差异条快速跳转簇(对齐自研审查条 ↑ i/N ↓ 的形态) */\n[data-edrv-view] .edrv-svndiff-nav { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svndiff-nav-count { min-width: 36px; text-align: center; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n/* W1-3 差异统计徽标(+新增/修改 −删除/修改;色相与行块绿红一致) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svndiff-stat { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svndiff-stat-add { color: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-svndiff-stat-del { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* W1-1 差异视图选项条(右端开关组;复用 .edrv-svn-check 的 label 形态) */\n[data-edrv-view] .edrv-svndiff-opts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n/* W1-2 变更面板名称过滤输入框(工具条行内,紧凑) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-name-filter { width: 168px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; line-height: 14px; }\n[data-edrv-view] .edrv-svn-name-filter:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-svn-name-filter::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* W2-3 远端更新提示条(工具条上方;warn = 失败降级) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-hintbar { display: flex; align-items: center; gap: 6px; margin: 0 10px 4px 12px; padding: 3px 8px; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-hintbar-warn { background: rgba(217, 83, 79, 0.12); color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* W2-5 疑似改名标记(行内 ⇄;hover 见 tooltip) */\n[data-edrv-view] .edrv-svn-pair-mark { flex-shrink: 0; color: var(--dsw-alias-state-warn-primary, #7c3aed); font-size: 11px; cursor: help; }\n/* W2-1 补丁对话框 / W2-2 目录对比对话框 @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-patch-dialog { max-width: calc(100vw - 48px); }\n[data-edrv-view] .edrv-svn-patch-head { display: flex; align-items: center; gap: 8px; padding-bottom: 8px; }\n[data-edrv-view] .edrv-svn-patch-notes { display: flex; flex-direction: column; gap: 4px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svn-patch-warn { color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-svn-patch-pre { max-height: 46vh; overflow: auto; margin: 0 0 8px; padding: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.5; white-space: pre; user-select: text; }\n[data-edrv-view] .edrv-svn-patch-foot { display: flex; align-items: center; gap: 8px; }\n[data-edrv-view] .edrv-svn-sum-form { display: flex; align-items: center; gap: 10px; padding-bottom: 8px; }\n[data-edrv-view] .edrv-svn-sum-list { max-height: 44vh; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 2px; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnlog-path-kind { flex-shrink: 0; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; }\n\n/* ===== 通用弹窗外壳(ModalShell)+ SVN 日志弹窗 @author ddj 2026-09-16 ===== */\n/* z-index 190:落在既有阶梯内(命令栏 200 > 片段二级 197 > 片段一级 195 > 190),\n 不遮挡命令栏;既有浮层数值不受影响(见 tests/overlayZOrder.test.ts)。 */\n[data-edrv-view] .edrv-modal-mask { position: fixed; inset: 0; z-index: 190; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, #000000aa); }\n[data-edrv-view] .edrv-svnlog-dialog { position: relative; display: flex; flex-direction: column; gap: 8px; }\n[data-edrv-view] .edrv-svnlog-head { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-svnlog-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-target { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-filter { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-svnlog-field { flex: 0 0 auto; padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-search { min-width: 160px; flex: 1 1 220px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-date { padding: 2px 4px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-check { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-svnlog-error { padding: 4px 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/* P0-14 上中下三段全宽:body 纵向铺满弹窗(外框高度由拖拽受控) */\n[data-edrv-view] .edrv-svnlog-body { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; min-height: 0; }\n/* 上段修订列表:列头行 + 单行五列(版本/动作/作者/日期/信息),列宽由 cell 类与 nth-child 对齐(P0-12/D30) */\n[data-edrv-view] .edrv-svnlog-list { flex: 0 0 auto; display: flex; flex-direction: column; overflow: auto; border: 2px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 4px; }\n[data-edrv-view] .edrv-svnlog-cols { display: flex; align-items: center; gap: 6px; padding: 2px 6px; border-bottom: 2px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(1) { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(2) { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(3) { width: 88px; flex: none; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(4) { width: 82px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(5) { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnlog-col-sortable { cursor: pointer; }\n[data-edrv-view] .edrv-svnlog-col-sortable:hover { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-col-on { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-sort-reset { flex: none; }\n[data-edrv-view] .edrv-svnlog-item { display: flex; align-items: center; gap: 6px; padding: 2px 6px; border-radius: 6px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svnlog-item.edrv-svnlog-item-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-svnlog-item-copied .edrv-svnlog-cell-msg,\n[data-edrv-view] .edrv-svnlog-item-copied .edrv-svnlog-rev { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-cell { overflow: hidden; text-overflow: ellipsis; font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-cell-rev { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cell-act { width: 52px; flex: none; display: inline-flex; gap: 3px; }\n[data-edrv-view] .edrv-svnlog-cell-author { width: 88px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cell-date { width: 82px; flex: none; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-cell-msg { flex: 1 1 auto; }\n[data-edrv-view] .edrv-svnlog-rev { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-weight: 700; color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-badge { display: inline-flex; align-items: center; justify-content: center; width: 12px; height: 12px; border-radius: 3px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; }\n[data-edrv-view] .edrv-svnlog-badge-modified { color: var(--dsw-alias-brand-primary, #2563eb); background: rgba(37,99,235,.12); }\n[data-edrv-view] .edrv-svnlog-badge-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); background: rgba(124,58,237,.12); }\n[data-edrv-view] .edrv-svnlog-badge-deleted { color: #b91c1c; background: rgba(185,28,28,.12); }\n[data-edrv-view] .edrv-svnlog-badge-plain { color: var(--dsw-alias-label-tertiary, #9aa5b1); background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-more { padding: 6px 4px 2px; text-align: center; }\n/* P0-14 三段 pane 公共外观(上段列表自带 .edrv-svnlog-list 边框) */\n[data-edrv-view] .edrv-svnlog-pane { border: 2px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 8px 10px; overflow: hidden; }\n[data-edrv-view] .edrv-svnlog-msgpane { flex: 0 0 auto; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-svnlog-pathspane { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }\n/* P0-13/P0-14:分段横向拖拽分隔条 + 变更路径独立滚动容器 */\n[data-edrv-view] .edrv-svnlog-hsplit { flex: 0 0 6px; cursor: row-resize; margin: 2px 0; border-radius: 3px; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-hsplit:hover, [data-edrv-view] .edrv-svnlog-hsplit:active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.28)); }\n[data-edrv-view] .edrv-svnlog-paths-wrap { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-svnlog-meta { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-date { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-msgpane pre.edrv-svnlog-body { margin: 6px 0 0; padding: 6px 8px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; overflow: auto; flex: 1 1 auto; min-height: 0; }\n/* P0-14 外框尺寸手柄(右缘调宽 / 底缘调高 / 右下角双向) */\n[data-edrv-view] .edrv-svnlog-grip { position: absolute; z-index: 2; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-grip-r { top: 12px; bottom: 12px; right: 0; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-b { left: 12px; right: 12px; bottom: 0; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-c { right: 0; bottom: 0; width: 14px; height: 14px; cursor: nwse-resize; border-radius: 3px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-grip:hover, [data-edrv-view] .edrv-svnlog-grip:active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.35)); }\n[data-edrv-view] .edrv-svnlog-paths-head { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); margin-bottom: 4px; }\n[data-edrv-view] .edrv-svnlog-hint { font-weight: 400; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-paths { display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-svnlog-path { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; border-radius: 5px; cursor: default; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-path:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svnlog-path-dim .edrv-svnlog-pathtext,\n[data-edrv-view] .edrv-svnlog-path-dim .edrv-svnlog-copyfrom { opacity: .45; }\n[data-edrv-view] .edrv-svnlog-pathtext { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-copyfrom { flex: 0 1 160px; overflow: hidden; text-overflow: ellipsis; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-svnlog-kind { font-size: 10px; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-acts { display: none; align-items: center; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-path:hover .edrv-svnlog-acts { display: inline-flex; }\n[data-edrv-view] .edrv-svnlog-footwrap { display: flex; flex-direction: column; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-btns { display: flex; align-items: center; gap: 6px; }\n/* ===== SVN 日志统计窗(P1-10)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-svnlog-stats { display: flex; flex-direction: column; max-height: 82vh; }\n[data-edrv-view] .edrv-svnlog-stats-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px 0; }\n[data-edrv-view] .edrv-svnlog-stats-body { display: flex; flex-direction: column; gap: 14px; padding: 10px 16px 16px; overflow: auto; }\n[data-edrv-view] .edrv-svnlog-stat-line { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-stat-block { display: flex; flex-direction: column; gap: 8px; }\n[data-edrv-view] .edrv-svnlog-stat-label { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-stat-duo { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-stat-hist { flex: 1 1 260px; display: flex; flex-direction: column; gap: 4px; min-width: 220px; }\n[data-edrv-view] .edrv-svnlog-stat-row { display: flex; align-items: center; gap: 8px; font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-stat-name { width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnlog-stat-barwrap { flex: 1; height: 12px; border-radius: 6px; background: rgba(127,127,127,.15); overflow: hidden; }\n[data-edrv-view] .edrv-svnlog-stat-bar { display: block; height: 100%; border-radius: 6px; }\n[data-edrv-view] .edrv-svnlog-stat-count { width: 36px; text-align: right; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnlog-stat-pie { width: 120px; height: 120px; flex-shrink: 0; border-radius: 50%; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-stat-days { display: flex; align-items: flex-end; gap: 2px; height: 120px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow-x: auto; }\n[data-edrv-view] .edrv-svnlog-stat-day { flex: 1 0 6px; min-width: 6px; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; }\n[data-edrv-view] .edrv-svnlog-stat-seg { display: block; width: 100%; }\n/* ===== SVN 日志区间弹窗(P1-6)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-svnlog-range-body { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px 16px; }\n[data-edrv-view] .edrv-svnlog-range-row { display: flex; gap: 14px; flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-range-input { width: 110px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-range-btns { display: flex; gap: 8px; justify-content: flex-end; }\n/* SVN 日志 WC 版号行加粗(P1-9) */\n[data-edrv-view] .edrv-svnlog-item-wc { font-weight: 700; }\n/* SVN 日志 merged 行(P1-5):灰字 + 缩进(缩进量按层级内联),前置「↳」标记 */\n[data-edrv-view] .edrv-svnlog-item-merged { color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-svnlog-item-merged .edrv-svnlog-merge-mark { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-svnlog-item-merged .edrv-svnlog-cell-msg { font-style: italic; }\n/* ===== 诊断日志弹窗(LogDialog)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-dlog-dialog { display: flex; flex-direction: column; gap: 8px; max-height: 88vh; }\n[data-edrv-view] .edrv-dlog-head { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-dlog-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-file { max-width: 200px; padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-level { padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-search { min-width: 120px; flex: 0 1 200px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-auto { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-dlog-error { padding: 4px 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-dlog-scroll { flex: 1 1 auto; min-height: 240px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 4px 0; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-dlog-row { display: flex; align-items: baseline; gap: 8px; padding: 1px 8px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.55; }\n[data-edrv-view] .edrv-dlog-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-dlog-at { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dlog-lvl { flex-shrink: 0; width: 26px; text-align: center; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dlog-text { flex: 1; min-width: 0; white-space: pre-wrap; word-break: break-word; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-dlog-row-error .edrv-dlog-text { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-dlog-row-warn .edrv-dlog-text { color: var(--dsw-alias-state-warning-primary, #b45309); }\n[data-edrv-view] .edrv-dlog-row-debug .edrv-dlog-text { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-dlog-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-stat { white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-switch { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; flex-shrink: 0; }\n/* SVN 更新结果条(P3:update 条目级明细,可关闭) */\n[data-edrv-view] .edrv-svnres { display: flex; align-items: flex-start; gap: 8px; flex-shrink: 0; padding: 5px 10px; font-size: 11px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-svnres-head { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svnres-list { display: flex; flex-wrap: wrap; gap: 4px 10px; min-width: 0; }\n[data-edrv-view] .edrv-svnres-item { display: inline-flex; align-items: center; gap: 3px; font-family: var(--ds-font-family-code, ui-monospace, monospace); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnres-item.edrv-svnres-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); font-weight: 700; }\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\n/* 调试(DAP):断点圆点 / 停帧行 / 浮动调试工具条。\n 圆点挂在 Monaco glyphMargin(glyphMarginClassName),行背景为 isWholeLine className;\n 工具条绝对定位于编辑区右上(editorArea relative 由 .edrv-editor-area 自身定位)。 */\n/* 断点圆点:装饰类占满整行,圆点经 ::before 在本行盒子内居中(行归属明确、不跨行界) */\n[data-edrv-view] .edrv-dap-bp { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }\n[data-edrv-view] .edrv-dap-bp-off { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-off::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }\n/* 未验证断点(适配器真实回执 verified:false):空心红圈,对齐 VS Code 空心圆语义 */\n[data-edrv-view] .edrv-dap-bp-unverified { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-unverified::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }\n[data-edrv-view] .edrv-dap-paused-line { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .22)); }\n[data-edrv-view] .edrv-dap-paused-glyph { cursor: pointer; }\n[data-edrv-view] .edrv-dap-paused-glyph::before { content: ''; display: block; width: 5px; height: 12px; margin: 4px auto 0; border-radius: 2px; background: var(--dsw-alias-state-warn-primary, #e5b93f); }\n[data-edrv-view] .edrv-editor-area { position: relative; }\n/* launch.json 右下角「添加配置」按钮(VSCode 同款形态):仅 launch.json 打开时渲染 */\n[data-edrv-view] .edrv-addcfg { position: absolute; right: 14px; bottom: 10px; z-index: 13; padding: 4px 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 18px; cursor: pointer; box-shadow: 0 1px 4px rgba(0, 0, 0, .1); }\n[data-edrv-view] .edrv-addcfg:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-dapbar { position: absolute; top: 6px; right: 14px; z-index: 12; display: none; align-items: center; gap: 3px; padding: 3px 5px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #f7f9fa); box-shadow: 0 2px 8px rgba(0, 0, 0, .12); }\n[data-edrv-view] .edrv-dapbar.on { display: flex; border-color: var(--dsw-alias-border-l2, #cdd6da); }\n[data-edrv-view] .edrv-dapbar-grip { flex: 0 0 auto; width: 16px; height: 22px; display: inline-flex; align-items: center; justify-content: center; cursor: grab; user-select: none; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 12px; }\n[data-edrv-view] .edrv-dapbar-grip:active, [data-edrv-view] .edrv-dapbar.dragging .edrv-dapbar-grip { cursor: grabbing; }\n[data-edrv-view] .edrv-dapbar.dragging { user-select: none; }\n[data-edrv-view] .edrv-dapbar-cfg { max-width: 220px; height: 22px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dapbar-btn { min-width: 24px; height: 22px; padding: 0 6px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 12px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-dapbar-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }\n[data-edrv-view] .edrv-dapbar-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-dapbar-phase { padding: 0 4px; font-size: 11px; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapbar-phase.paused { color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }\n[data-edrv-view] .edrv-dapbar-err { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--dsw-alias-state-error-primary, #e05252); }\n\n/* 调试面板(活动栏「调试」页签):四段折叠区 + 断点列表 + REPL。 */\n[data-edrv-view] .edrv-dappanel { display: flex; flex-direction: column; gap: 10px; height: 100%; min-height: 0; overflow: auto; padding: 2px 2px 8px; }\n[data-edrv-view] .edrv-dappanel-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; row-gap: 4px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dappanel-phase { padding: 2px 8px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-dappanel-phase.paused { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .18)); color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }\n[data-edrv-view] .edrv-dappanel-cfg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dappanel-pid { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-dappanel-err { color: var(--dsw-alias-state-error-primary, #e05252); }\n[data-edrv-view] .edrv-dapseg { display: flex; flex-direction: column; gap: 2px; min-height: 0; }\n[data-edrv-view] .edrv-dapseg-body { min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }\n/* 段间分隔条(splitview sash):拖动调整相邻两段高度占比;各段高度由 DebugPanel 内联接管。 */\n[data-edrv-view] .edrv-dapseg-sash { flex: 0 0 auto; height: 4px; margin: 2px 0; border-radius: 2px; background: transparent; cursor: row-resize; }\n[data-edrv-view] .edrv-dapseg-sash:hover, [data-edrv-view] .edrv-dapseg-sash.dragging { background: var(--dsw-alias-border-l2, #cdd6da); }\n/* 折叠段头:chevron + 标题 + 悬浮动作区(动作 hover 显隐;REPL 头不参与折叠)。 */\n[data-edrv-view] .edrv-dapseg-head { display: flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-tertiary, #7b8794); padding: 2px 2px; cursor: pointer; border-radius: 4px; }\n[data-edrv-view] .edrv-dapseg-head:hover { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-daprepl .edrv-dapseg-head { cursor: default; padding: 2px 0; }\n[data-edrv-view] .edrv-dapseg-chevron { flex: 0 0 auto; width: 14px; text-align: center; font-size: 10px; }\n[data-edrv-view] .edrv-dapseg-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dapseg-act { flex: 0 0 auto; min-width: 20px; height: 18px; padding: 0 4px; border: none; border-radius: 4px; background: transparent; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 11px; line-height: 1; cursor: pointer; opacity: 0; }\n[data-edrv-view] .edrv-dapseg-head:hover .edrv-dapseg-act { opacity: 1; }\n[data-edrv-view] .edrv-dapseg-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dapempty { font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 4px 2px; }\n[data-edrv-view] .edrv-dapframe { display: flex; align-items: baseline; gap: 8px; padding: 3px 4px; border-radius: 6px; cursor: pointer; font-size: 12px; }\n[data-edrv-view] .edrv-dapframe:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-dapframe-name { color: var(--dsw-alias-label-primary, inherit); font-weight: 600; }\n[data-edrv-view] .edrv-dapframe-loc { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dapscope { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); padding: 4px 2px 1px; }\n[data-edrv-view] .edrv-dapvar { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; font-size: 12px; padding: 1px 2px; min-width: 0; }\n[data-edrv-view] .edrv-dapvar-tw { flex: 0 0 auto; width: 14px; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 10px; padding: 0; }\n[data-edrv-view] span.edrv-dapvar-tw { cursor: default; }\n[data-edrv-view] .edrv-dapvar-name { flex: 0 1 34%; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, inherit); font-family: var(--ds-font-family-code, ui-monospace, monospace); white-space: nowrap; }\n[data-edrv-view] .edrv-dapvar-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapvar-type { flex: 0 0 auto; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dapframe.selected { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .16)); outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }\n[data-edrv-view] .edrv-dapwatch { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; min-width: 0; font-size: 12px; padding: 1px 2px; }\n[data-edrv-view] .edrv-dapwatch-x { border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; width: 14px; }\n[data-edrv-view] .edrv-dapwatch-x:hover { color: var(--dsw-alias-state-error-primary, #e05252); }\n[data-edrv-view] .edrv-dapwatch-add { padding: 2px 0; }\n[data-edrv-view] .edrv-dapbp { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 2px 2px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-dapbp:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-dapbp:active { background: var(--dsw-alias-interactive-bg-pressed, rgba(127, 127, 127, .2)); }\n[data-edrv-view] .edrv-dapbp-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }\n[data-edrv-view] .edrv-dapbp-dot.off { background: transparent; border: 1.5px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }\n[data-edrv-view] .edrv-dapbp-file { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: none; background: transparent; color: var(--dsw-alias-label-primary, inherit); cursor: pointer; padding: 0; font-size: 12px; }\n[data-edrv-view] .edrv-dapbp-dir { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; text-align: right; }\n[data-edrv-view] .edrv-dapbp-line { flex: 0 0 auto; font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapbp-x { flex: 0 0 auto; border: none; background: transparent; color: transparent; cursor: pointer; width: 14px; }\n[data-edrv-view] .edrv-dapbp:hover .edrv-dapbp-x { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dapbp-x:hover { color: var(--dsw-alias-state-error-primary, #e05252) !important; }\n[data-edrv-view] .edrv-daprepl { display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-daprepl-log { display: flex; flex-direction: column; gap: 1px; max-height: 180px; overflow: auto; }\n[data-edrv-view] .edrv-daprepl-line { font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); color: var(--dsw-alias-label-secondary, #52606d); white-space: pre-wrap; word-break: break-all; display: flex; gap: 6px; }\n[data-edrv-view] .edrv-daprepl-line.out { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-daprepl-in { width: 100%; box-sizing: border-box; height: 26px; padding: 0 8px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dappanel-run { flex: 0 0 auto; width: 24px; height: 24px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-dappanel-run:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }\n[data-edrv-view] .edrv-dappanel-run:disabled { opacity: .35; cursor: default; background: transparent; }\n/* 面板顶部配置下拉(与编辑器浮动条 .edrv-dapbar-cfg 同视觉)。 */\n[data-edrv-view] .edrv-dappanel-cfg-select { max-width: 190px; height: 24px; padding: 0 4px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n/* 断点行内编辑行(面板内展开编辑,控件复用 .edrv-bpwidget-* 视觉;左缩进对齐断点行文字列)。 */\n[data-edrv-view] .edrv-dapbp-edit { display: flex; align-items: center; gap: 6px; padding: 3px 2px 3px 20px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-mode { height: 20px; }\n[data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-input { height: 20px; }\n/* 断点回执:未验证空心红圈(须排在 .off 之后,禁用态优先展示灰圈语义)。 */\n[data-edrv-view] .edrv-dapbp-dot.unverified { background: transparent; border: 1.5px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }\n/* 断点行内编辑浮层(Monaco content widget,锚定断点行上方;对齐 VS Code breakpointWidget 形态)。 */\n/* View Zone 浮层:宽度/左移由 BpWidget.syncWidth 按 layout 内联设置\n (Monaco .view-zones 仅内容区宽,需补偿 contentLeft 与滚动条),此处只管视觉。 */\n[data-edrv-view] .edrv-bpwidget { position: relative; box-sizing: border-box; display: flex; align-items: center; gap: 6px; padding: 3px 8px; border-top: 1px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-layer-1, #f7f9fa); }\n/* 断点编辑中的目标行高亮(浮层打开期间) */\n[data-edrv-view] .edrv-dap-editing-line { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-bpwidget-mode { flex: 0 0 auto; height: 22px; padding: 0 4px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); cursor: pointer; }\n[data-edrv-view] .edrv-bpwidget-input { flex: 1 1 auto !important; min-width: 0; width: auto !important; box-sizing: border-box; height: 22px; padding: 0 6px; font-size: 12px; font-family: var(--ds-font-family-code, ui-monospace, monospace); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-bpwidget-input:focus { outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }\n\n/* 断点 hover 预览(glyph 区):半透明小红点 + pointer 光标(对齐 VS Code .codicon-debug-hint)。 */\n[data-edrv-view] .edrv-dap-bp-hint { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-hint::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); opacity: .4; }\n/* 行号区也给出可点提示(与 VS Code 一致:glyph 区与行号区都可点/可 hover);\n 不拦截折叠箭头等自有控件的光标。 */\n[data-edrv-view] .edrv-editor-row .monaco-editor .margin-view-overlays .line-numbers:hover { cursor: pointer; }\n\n/* 暂停态 hover 调试块 + 变量树(成员全量直显 + table 成员折叠展开)。\n ⚠️ 选择器一律用 data-*:本仓 vendored Monaco 的 markdown→DOM 管线会剥掉我们 HTML 里的\n class(supportHtml/isTrusted 都不豁免),只有 data-* 存活;用 class 会静默失效。 */\n[data-edrv-view] [data-edrv-dap] { display: block; min-width: 0; }\n[data-edrv-view] [data-edrv-head] { display: flex; align-items: baseline; gap: 2px; }\n[data-edrv-view] [data-edrv-head] [data-edrv-expr] { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-head] [data-edrv-val] { font-family: var(--ds-font-family-code, ui-monospace, monospace); word-break: break-all; }\n[data-edrv-view] [data-edrv-type] { margin-top: 2px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-tree] { margin: 2px 0 0; padding: 0; list-style: none; font-variant-numeric: tabular-nums; }\n[data-edrv-view] [data-edrv-line] { display: flex; align-items: baseline; gap: 4px; padding: 1px 2px; border-radius: 3px; white-space: nowrap; }\n[data-edrv-view] [data-edrv-line]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n/* 折叠按钮(对齐 CodeBuddy 调试浮窗变量树的 twist):热区 14px + hover 反馈,未可展开时留空占位 */\n[data-edrv-view] [data-edrv-twist] { flex: 0 0 auto; box-sizing: border-box; width: 14px; height: 14px; line-height: 13px; text-align: center; border-radius: 3px; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-twist]:not(:empty) { cursor: pointer; user-select: none; }\n[data-edrv-view] [data-edrv-twist]:not(:empty):hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .2)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-name] { flex: 0 0 auto; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-line] [data-edrv-eq] { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-line] [data-edrv-val] { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] [data-edrv-note] { color: var(--dsw-alias-label-tertiary, #8a949b); font-style: italic; }\n/* Alt 提示层:由 hoverTree 注入到 .monaco-hover(滚动区之外)贴底常驻;容器预留同高内边距避免遮挡末行。\n 不再用 sticky / `:has()`:滚动链在 .monaco-hover-content 上,sticky 会被滚走。 */\n[data-edrv-view] .monaco-hover[data-edrv-hint-host=\"1\"] { padding-bottom: 22px; }\n[data-edrv-view] [data-edrv-hint=\"1\"] { position: absolute; left: 0; right: 0; bottom: 0; box-sizing: border-box; height: 22px; padding: 2px 8px; font-size: 11px; line-height: 18px; color: var(--dsw-alias-label-tertiary, #8a949b); background: var(--dsw-alias-bg-overlay, #ffffff); border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }";
|
|
36
|
+
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; overflow-y: hidden; scrollbar-width: thin; flex-shrink: 0; overscroll-behavior-x: contain; } /* overscroll-behavior-x: 横滚到边界不把滚动链传给页面(滚轮横滚接管更可控)@author ddj 2026年09月18号 */\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/* 跳转目标区域高亮(LSP 定义/引用、搜索命中落点):落点一眼可见,约 1.2s 后由 EditorView 自动清除。\n 用官方令牌并带回落值,不在插件作用域重定义 --dsw-* (否则永久脱离官方主题)。 */\n[data-edrv-view] .edrv-nav-target { background: var(--dsw-alias-interactive-bg-hover, rgba(79,193,255,.28)); border-radius: 2px; box-shadow: 0 0 0 1px var(--dsw-alias-interactive-primary, rgba(79,193,255,.55)); }\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/* 图片预览:居中交给子项 margin:auto(auto 外边距优先吃正向空闲空间;溢出时空闲为负 → 外边距归 0、\n flex-start 起点对齐 → 放大后四边均可滚动到达,规避 justify-content:center 双侧溢出不可达问题) */\n[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; 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%; flex-shrink: 0; margin: auto; align-self: flex-start; 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); margin: auto; }\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/* Markdown 预览(需求 4):工具条 + 居中阅读栏;正文排版由官方 MarkdownText 自带的 --dsw-* 样式负责 */\n[data-edrv-view] .edrv-mdview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-mdview-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-mdview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n[data-edrv-view] .edrv-mdview-stage { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px 20px 28px; }\n[data-edrv-view] .edrv-mdview-doc { max-width: 880px; margin: 0 auto; width: 100%; }\n[data-edrv-view] .edrv-mdview-fallback { font-size: 12px; line-height: 1.7; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-mdview-pre { margin: 8px 0 0; padding: 10px 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }\n[data-edrv-view] .edrv-pb-mdbtn { height: 20px; padding: 0 8px; font-size: 11px; flex-shrink: 0; }\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/* ===== SVN 变更面板(工作副本状态列表;颜色分级对齐 TortoiseSVN 口径) @author ddj 2026-09-16 ===== */\n[data-edrv-view] .edrv-svn-toolbar { display: flex; align-items: center; gap: 12px; padding: 4px 10px 6px 12px; flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svn-check { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-check input { margin: 0; cursor: pointer; }\n[data-edrv-view] .edrv-svn-bulk { display: flex; align-items: center; gap: 6px; padding: 0 10px 6px 12px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-count { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* AI 助手 dock(12-panel-ai-bar 设计定稿):manifest 分带之 assistant 带,accent 左竖轨为唯一记忆点。\n AI 线程色三处同轨:竖轨 / dock 按钮描边 / 弹窗进度条(--dsw-alias-state-info-primary)。 @author ddj 2026-09-23 */\n[data-edrv-view] .edrv-svn-ai-bar { position: relative; display: flex; align-items: center; gap: 8px; padding: 6px 10px 6px 14px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-ai-rail { position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-ai-label { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svn-ai-btn { border-color: var(--dsw-alias-state-info-primary, #2f6fed); color: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-ai-btn:disabled { border-color: var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 状态位定宽右对齐(就绪/分析中…/等待助手投递…互换不跳版) */\n[data-edrv-view] .edrv-svn-ai-status { flex-shrink: 0; min-width: 88px; text-align: right; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n/* 批量条语义分隔(导出簇 ┊ 动作簇之间 1px 竖线) */\n[data-edrv-view] .edrv-svn-sep { width: 1px; height: 14px; background: var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n/* 头部图标组(检查远端/刷新成组,gap 收紧) */\n[data-edrv-view] .edrv-svn-head-icons { display: inline-flex; align-items: center; gap: 2px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-list { flex: 1; min-height: 0; overflow: auto; padding: 2px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-svn-group { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; margin: 2px 0; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; font-weight: 600; cursor: pointer; user-select: none; }\n[data-edrv-view] .edrv-svn-group:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svn-fold-ch { width: 10px; flex-shrink: 0; text-align: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svn-group-n { margin-left: auto; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-weight: 400; }\n[data-edrv-view] .edrv-svn-row { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; border-radius: 5px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svn-path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svn-acts { display: none; align-items: center; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-row:hover .edrv-svn-acts { display: inline-flex; }\n[data-edrv-view] .edrv-svn-act { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; padding: 1px 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 14px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svn-act:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-svn-act-danger { color: var(--dsw-alias-state-error-primary, #d9534f); border-color: var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-svn-ch { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-weight: 700; }\n[data-edrv-view] .edrv-svn-ch-modified { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svn-ch-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); }\n[data-edrv-view] .edrv-svn-ch-deleted { color: var(--dsw-alias-label-secondary, #b91c1c); }\n[data-edrv-view] .edrv-svn-ch-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-svn-ch-plain { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 文件树行尾 SVN 状态徽标(与差异角标并列;方形字母,区别圆形差异计数) */\n[data-edrv-view] .edrv-tree-svn { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 4px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-modified { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-deleted { color: var(--dsw-alias-label-secondary, #b91c1c); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* ===== SVN 基线差异视图(BASE 左 / 工作区右,只读并排) @author ddj 2026-09-16 ===== */\n[data-edrv-view] .edrv-svndiff { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-svndiff-bar { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svndiff-title { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-svndiff-tag { padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svndiff-host { flex: 1 1 auto; min-height: 0; }\n[data-edrv-view] .edrv-svndiff-empty { display: flex; flex: 1 1 auto; align-items: center; justify-content: center; padding: 24px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 12px; text-align: center; }\n/* 差异整行色块与行首 +/- 指示符:vendored Monaco(diffEditorWidget2)静态 CSS 无\n .line-insert/.line-delete 背景规则,整行色块靠主题键 + 此处固定色兜底保证可见。\n 注意不要用 --dsw-alias-interactive-bg-hover 类 token:实测为 92% 透明中性色,肉眼不可见。\n 固定色取自研差异 UI 同色相(绿 #0f9d58 / 红 #d9534f),alpha 提到肉眼可辨。 */\n[data-edrv-view] .monaco-editor .line-insert { background: rgba(15, 157, 88, 0.18); }\n[data-edrv-view] .monaco-editor .line-delete { background: rgba(217, 83, 79, 0.16); }\n[data-edrv-view] .monaco-editor .gutter-insert { background: rgba(15, 157, 88, 0.35); }\n[data-edrv-view] .monaco-editor .gutter-delete { background: rgba(217, 83, 79, 0.35); }\n[data-edrv-view] .monaco-editor .insert-sign { color: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .monaco-editor .delete-sign { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* 差异条快速跳转簇(对齐自研审查条 ↑ i/N ↓ 的形态) */\n[data-edrv-view] .edrv-svndiff-nav { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svndiff-nav-count { min-width: 36px; text-align: center; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n/* W1-3 差异统计徽标(+新增/修改 −删除/修改;色相与行块绿红一致) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svndiff-stat { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svndiff-stat-add { color: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-svndiff-stat-del { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* W1-1 差异视图选项条(右端开关组;复用 .edrv-svn-check 的 label 形态) */\n[data-edrv-view] .edrv-svndiff-opts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n/* W1-2 变更面板名称过滤输入框(工具条行内,紧凑) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-name-filter { width: 168px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; line-height: 14px; }\n[data-edrv-view] .edrv-svn-name-filter:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-svn-name-filter::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* W2-3 远端更新提示条(工具条上方;warn = 失败降级) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-hintbar { display: flex; align-items: center; gap: 6px; margin: 0 10px 4px 12px; padding: 3px 8px; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-hintbar-warn { background: rgba(217, 83, 79, 0.12); color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* W2-5 疑似改名标记(行内 ⇄;hover 见 tooltip) */\n[data-edrv-view] .edrv-svn-pair-mark { flex-shrink: 0; color: var(--dsw-alias-state-warn-primary, #7c3aed); font-size: 11px; cursor: help; }\n/* W2-1 补丁对话框 / W2-2 目录对比对话框 @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-patch-dialog { max-width: calc(100vw - 48px); }\n[data-edrv-view] .edrv-svn-patch-head { display: flex; align-items: center; gap: 8px; padding-bottom: 8px; }\n[data-edrv-view] .edrv-svn-patch-notes { display: flex; flex-direction: column; gap: 4px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svn-patch-warn { color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-svn-patch-pre { max-height: 46vh; overflow: auto; margin: 0 0 8px; padding: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.5; white-space: pre; user-select: text; }\n[data-edrv-view] .edrv-svn-patch-foot { display: flex; align-items: center; gap: 8px; }\n/* AI 智能整理弹窗(11-ai-changelist-triage):预览三分组卡 + 执行进度 @author ddj 2026-09-23 */\n[data-edrv-view] .edrv-svnplan-dialog { max-width: calc(100vw - 48px); }\n[data-edrv-view] .edrv-svnplan-head { display: flex; align-items: center; gap: 8px; padding-bottom: 4px; }\n[data-edrv-view] .edrv-svnplan-meta { display: flex; gap: 10px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svnplan-note { color: var(--dsw-alias-state-warn-primary, #b7791f); font-size: 10px; }\n[data-edrv-view] .edrv-svnplan-sec { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 6px 8px; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnplan-sechead { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnplan-sectitle { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-list { max-height: 26vh; overflow: auto; padding-left: 18px; }\n[data-edrv-view] .edrv-svnplan-card { border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); padding: 3px 0; }\n[data-edrv-view] .edrv-svnplan-row { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnplan-item { display: flex; align-items: center; gap: 6px; padding-left: 18px; }\n[data-edrv-view] .edrv-svnplan-groupname { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 46ch; }\n[data-edrv-view] .edrv-svnplan-reason { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30ch; }\n[data-edrv-view] .edrv-svnplan-stage { display: flex; align-items: center; gap: 8px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svnplan-badge { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; padding: 1px 8px; font-size: 10px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-alias-bg-layer-2, #f7f9f9); white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-badge-cur { font-family: var(--ds-font-family-code, ui-monospace, monospace); overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnplan-bar { height: 6px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnplan-bar-in { height: 100%; background: var(--dsw-alias-state-info-primary, #2f6fed); transition: width .2s; }\n[data-edrv-view] .edrv-svnplan-logwrap { max-height: 40vh; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 6px 8px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); }\n[data-edrv-view] .edrv-svnplan-log { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.6; color: var(--dsw-alias-label-secondary, #52606d); user-select: text; }\n[data-edrv-view] .edrv-svnplan-log-bad { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-svnplan-foot { display: flex; align-items: center; gap: 8px; }\n[data-edrv-view] .edrv-svnplan-primary { font-weight: 600; border-color: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-sum-form { display: flex; align-items: center; gap: 10px; padding-bottom: 8px; }\n[data-edrv-view] .edrv-svn-sum-list { max-height: 44vh; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 2px; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnlog-path-kind { flex-shrink: 0; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; }\n\n/* ===== 通用弹窗外壳(ModalShell)+ SVN 日志弹窗 @author ddj 2026-09-16 ===== */\n/* z-index 190:落在既有阶梯内(命令栏 200 > 片段二级 197 > 片段一级 195 > 190),\n 不遮挡命令栏;既有浮层数值不受影响(见 tests/overlayZOrder.test.ts)。 */\n[data-edrv-view] .edrv-modal-mask { position: fixed; inset: 0; z-index: 190; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, #000000aa); }\n[data-edrv-view] .edrv-svnlog-dialog { position: relative; display: flex; flex-direction: column; gap: 8px; }\n[data-edrv-view] .edrv-svnlog-head { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-svnlog-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-target { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-filter { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-svnlog-field { flex: 0 0 auto; padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-search { min-width: 160px; flex: 1 1 220px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-date { padding: 2px 4px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-check { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-svnlog-error { padding: 4px 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/* P0-14 上中下三段全宽:body 纵向铺满弹窗(外框高度由拖拽受控) */\n[data-edrv-view] .edrv-svnlog-body { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; min-height: 0; }\n/* 上段修订列表:列头行 + 单行五列(版本/动作/作者/日期/信息),列宽由 cell 类与 nth-child 对齐(P0-12/D30) */\n[data-edrv-view] .edrv-svnlog-list { flex: 0 0 auto; display: flex; flex-direction: column; overflow: auto; border: 2px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 4px; }\n[data-edrv-view] .edrv-svnlog-cols { display: flex; align-items: center; gap: 6px; padding: 2px 6px; border-bottom: 2px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(1) { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(2) { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(3) { width: 88px; flex: none; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(4) { width: 82px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(5) { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnlog-col-sortable { cursor: pointer; }\n[data-edrv-view] .edrv-svnlog-col-sortable:hover { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-col-on { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-sort-reset { flex: none; }\n[data-edrv-view] .edrv-svnlog-item { display: flex; align-items: center; gap: 6px; padding: 2px 6px; border-radius: 6px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svnlog-item.edrv-svnlog-item-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-svnlog-item-copied .edrv-svnlog-cell-msg,\n[data-edrv-view] .edrv-svnlog-item-copied .edrv-svnlog-rev { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-cell { overflow: hidden; text-overflow: ellipsis; font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-cell-rev { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cell-act { width: 52px; flex: none; display: inline-flex; gap: 3px; }\n[data-edrv-view] .edrv-svnlog-cell-author { width: 88px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cell-date { width: 82px; flex: none; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-cell-msg { flex: 1 1 auto; }\n[data-edrv-view] .edrv-svnlog-rev { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-weight: 700; color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-badge { display: inline-flex; align-items: center; justify-content: center; width: 12px; height: 12px; border-radius: 3px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; }\n[data-edrv-view] .edrv-svnlog-badge-modified { color: var(--dsw-alias-brand-primary, #2563eb); background: rgba(37,99,235,.12); }\n[data-edrv-view] .edrv-svnlog-badge-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); background: rgba(124,58,237,.12); }\n[data-edrv-view] .edrv-svnlog-badge-deleted { color: #b91c1c; background: rgba(185,28,28,.12); }\n[data-edrv-view] .edrv-svnlog-badge-plain { color: var(--dsw-alias-label-tertiary, #9aa5b1); background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-more { padding: 6px 4px 2px; text-align: center; }\n/* P0-14 三段 pane 公共外观(上段列表自带 .edrv-svnlog-list 边框) */\n[data-edrv-view] .edrv-svnlog-pane { border: 2px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 8px 10px; overflow: hidden; }\n[data-edrv-view] .edrv-svnlog-msgpane { flex: 0 0 auto; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-svnlog-pathspane { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }\n/* P0-13/P0-14:分段横向拖拽分隔条 + 变更路径独立滚动容器 */\n[data-edrv-view] .edrv-svnlog-hsplit { flex: 0 0 6px; cursor: row-resize; margin: 2px 0; border-radius: 3px; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-hsplit:hover, [data-edrv-view] .edrv-svnlog-hsplit:active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.28)); }\n[data-edrv-view] .edrv-svnlog-paths-wrap { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-svnlog-meta { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-date { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-msgpane pre.edrv-svnlog-body { margin: 6px 0 0; padding: 6px 8px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; overflow: auto; flex: 1 1 auto; min-height: 0; }\n/* P0-14 外框尺寸手柄(右缘调宽 / 底缘调高 / 右下角双向) */\n[data-edrv-view] .edrv-svnlog-grip { position: absolute; z-index: 2; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-grip-r { top: 12px; bottom: 12px; right: 0; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-b { left: 12px; right: 12px; bottom: 0; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-c { right: 0; bottom: 0; width: 14px; height: 14px; cursor: nwse-resize; border-radius: 3px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-grip:hover, [data-edrv-view] .edrv-svnlog-grip:active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.35)); }\n[data-edrv-view] .edrv-svnlog-paths-head { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); margin-bottom: 4px; }\n[data-edrv-view] .edrv-svnlog-hint { font-weight: 400; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-paths { display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-svnlog-path { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; border-radius: 5px; cursor: default; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-path:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svnlog-path-dim .edrv-svnlog-pathtext,\n[data-edrv-view] .edrv-svnlog-path-dim .edrv-svnlog-copyfrom { opacity: .45; }\n[data-edrv-view] .edrv-svnlog-pathtext { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-copyfrom { flex: 0 1 160px; overflow: hidden; text-overflow: ellipsis; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-svnlog-kind { font-size: 10px; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-acts { display: none; align-items: center; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-path:hover .edrv-svnlog-acts { display: inline-flex; }\n[data-edrv-view] .edrv-svnlog-footwrap { display: flex; flex-direction: column; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-btns { display: flex; align-items: center; gap: 6px; }\n/* ===== SVN 日志统计窗(P1-10)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-svnlog-stats { display: flex; flex-direction: column; max-height: 82vh; }\n[data-edrv-view] .edrv-svnlog-stats-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px 0; }\n[data-edrv-view] .edrv-svnlog-stats-body { display: flex; flex-direction: column; gap: 14px; padding: 10px 16px 16px; overflow: auto; }\n[data-edrv-view] .edrv-svnlog-stat-line { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-stat-block { display: flex; flex-direction: column; gap: 8px; }\n[data-edrv-view] .edrv-svnlog-stat-label { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-stat-duo { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-stat-hist { flex: 1 1 260px; display: flex; flex-direction: column; gap: 4px; min-width: 220px; }\n[data-edrv-view] .edrv-svnlog-stat-row { display: flex; align-items: center; gap: 8px; font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-stat-name { width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnlog-stat-barwrap { flex: 1; height: 12px; border-radius: 6px; background: rgba(127,127,127,.15); overflow: hidden; }\n[data-edrv-view] .edrv-svnlog-stat-bar { display: block; height: 100%; border-radius: 6px; }\n[data-edrv-view] .edrv-svnlog-stat-count { width: 36px; text-align: right; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnlog-stat-pie { width: 120px; height: 120px; flex-shrink: 0; border-radius: 50%; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-stat-days { display: flex; align-items: flex-end; gap: 2px; height: 120px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow-x: auto; }\n[data-edrv-view] .edrv-svnlog-stat-day { flex: 1 0 6px; min-width: 6px; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; }\n[data-edrv-view] .edrv-svnlog-stat-seg { display: block; width: 100%; }\n/* ===== SVN 日志区间弹窗(P1-6)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-svnlog-range-body { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px 16px; }\n[data-edrv-view] .edrv-svnlog-range-row { display: flex; gap: 14px; flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-range-input { width: 110px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-range-btns { display: flex; gap: 8px; justify-content: flex-end; }\n/* SVN 日志 WC 版号行加粗(P1-9) */\n[data-edrv-view] .edrv-svnlog-item-wc { font-weight: 700; }\n/* SVN 日志 merged 行(P1-5):灰字 + 缩进(缩进量按层级内联),前置「↳」标记 */\n[data-edrv-view] .edrv-svnlog-item-merged { color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-svnlog-item-merged .edrv-svnlog-merge-mark { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-svnlog-item-merged .edrv-svnlog-cell-msg { font-style: italic; }\n/* ===== 诊断日志弹窗(LogDialog)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-dlog-dialog { display: flex; flex-direction: column; gap: 8px; max-height: 88vh; }\n[data-edrv-view] .edrv-dlog-head { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-dlog-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-file { max-width: 200px; padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-level { padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-search { min-width: 120px; flex: 0 1 200px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-auto { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-dlog-error { padding: 4px 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-dlog-scroll { flex: 1 1 auto; min-height: 240px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 4px 0; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-dlog-row { display: flex; align-items: baseline; gap: 8px; padding: 1px 8px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.55; }\n[data-edrv-view] .edrv-dlog-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-dlog-at { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dlog-lvl { flex-shrink: 0; width: 26px; text-align: center; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dlog-text { flex: 1; min-width: 0; white-space: pre-wrap; word-break: break-word; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-dlog-row-error .edrv-dlog-text { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-dlog-row-warn .edrv-dlog-text { color: var(--dsw-alias-state-warning-primary, #b45309); }\n[data-edrv-view] .edrv-dlog-row-debug .edrv-dlog-text { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-dlog-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-stat { white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-switch { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; flex-shrink: 0; }\n/* SVN 更新结果条(P3:update 条目级明细,可关闭) */\n[data-edrv-view] .edrv-svnres { display: flex; align-items: flex-start; gap: 8px; flex-shrink: 0; padding: 5px 10px; font-size: 11px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-svnres-head { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svnres-list { display: flex; flex-wrap: wrap; gap: 4px 10px; min-width: 0; }\n[data-edrv-view] .edrv-svnres-item { display: inline-flex; align-items: center; gap: 3px; font-family: var(--ds-font-family-code, ui-monospace, monospace); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnres-item.edrv-svnres-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); font-weight: 700; }\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\n/* 调试(DAP):断点圆点 / 停帧行 / 浮动调试工具条。\n 圆点挂在 Monaco glyphMargin(glyphMarginClassName),行背景为 isWholeLine className;\n 工具条绝对定位于编辑区右上(editorArea relative 由 .edrv-editor-area 自身定位)。 */\n/* 断点圆点:装饰类占满整行,圆点经 ::before 在本行盒子内居中(行归属明确、不跨行界) */\n[data-edrv-view] .edrv-dap-bp { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }\n[data-edrv-view] .edrv-dap-bp-off { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-off::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }\n/* 未验证断点(适配器真实回执 verified:false):空心红圈,对齐 VS Code 空心圆语义 */\n[data-edrv-view] .edrv-dap-bp-unverified { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-unverified::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }\n[data-edrv-view] .edrv-dap-paused-line { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .22)); }\n[data-edrv-view] .edrv-dap-paused-glyph { cursor: pointer; }\n[data-edrv-view] .edrv-dap-paused-glyph::before { content: ''; display: block; width: 5px; height: 12px; margin: 4px auto 0; border-radius: 2px; background: var(--dsw-alias-state-warn-primary, #e5b93f); }\n[data-edrv-view] .edrv-editor-area { position: relative; }\n/* launch.json 右下角「添加配置」按钮(VSCode 同款形态):仅 launch.json 打开时渲染 */\n[data-edrv-view] .edrv-addcfg { position: absolute; right: 14px; bottom: 10px; z-index: 13; padding: 4px 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 18px; cursor: pointer; box-shadow: 0 1px 4px rgba(0, 0, 0, .1); }\n[data-edrv-view] .edrv-addcfg:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-dapbar { position: absolute; top: 6px; right: 14px; z-index: 12; display: none; align-items: center; gap: 3px; padding: 3px 5px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #f7f9fa); box-shadow: 0 2px 8px rgba(0, 0, 0, .12); }\n[data-edrv-view] .edrv-dapbar.on { display: flex; border-color: var(--dsw-alias-border-l2, #cdd6da); }\n[data-edrv-view] .edrv-dapbar-grip { flex: 0 0 auto; width: 16px; height: 22px; display: inline-flex; align-items: center; justify-content: center; cursor: grab; user-select: none; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 12px; }\n[data-edrv-view] .edrv-dapbar-grip:active, [data-edrv-view] .edrv-dapbar.dragging .edrv-dapbar-grip { cursor: grabbing; }\n[data-edrv-view] .edrv-dapbar.dragging { user-select: none; }\n[data-edrv-view] .edrv-dapbar-cfg { max-width: 220px; height: 22px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dapbar-btn { min-width: 24px; height: 22px; padding: 0 6px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 12px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-dapbar-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }\n[data-edrv-view] .edrv-dapbar-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-dapbar-phase { padding: 0 4px; font-size: 11px; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapbar-phase.paused { color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }\n[data-edrv-view] .edrv-dapbar-err { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--dsw-alias-state-error-primary, #e05252); }\n\n/* 调试面板(活动栏「调试」页签):四段折叠区 + 断点列表 + REPL。 */\n[data-edrv-view] .edrv-dappanel { display: flex; flex-direction: column; gap: 10px; height: 100%; min-height: 0; overflow: auto; padding: 2px 2px 8px; }\n[data-edrv-view] .edrv-dappanel-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; row-gap: 4px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dappanel-phase { padding: 2px 8px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-dappanel-phase.paused { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .18)); color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }\n[data-edrv-view] .edrv-dappanel-cfg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dappanel-pid { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-dappanel-err { color: var(--dsw-alias-state-error-primary, #e05252); }\n[data-edrv-view] .edrv-dapseg { display: flex; flex-direction: column; gap: 2px; min-height: 0; }\n[data-edrv-view] .edrv-dapseg-body { min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }\n/* 段间分隔条(splitview sash):拖动调整相邻两段高度占比;各段高度由 DebugPanel 内联接管。 */\n[data-edrv-view] .edrv-dapseg-sash { flex: 0 0 auto; height: 4px; margin: 2px 0; border-radius: 2px; background: transparent; cursor: row-resize; }\n[data-edrv-view] .edrv-dapseg-sash:hover, [data-edrv-view] .edrv-dapseg-sash.dragging { background: var(--dsw-alias-border-l2, #cdd6da); }\n/* 折叠段头:chevron + 标题 + 悬浮动作区(动作 hover 显隐;REPL 头不参与折叠)。 */\n[data-edrv-view] .edrv-dapseg-head { display: flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-tertiary, #7b8794); padding: 2px 2px; cursor: pointer; border-radius: 4px; }\n[data-edrv-view] .edrv-dapseg-head:hover { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-daprepl .edrv-dapseg-head { cursor: default; padding: 2px 0; }\n[data-edrv-view] .edrv-dapseg-chevron { flex: 0 0 auto; width: 14px; text-align: center; font-size: 10px; }\n[data-edrv-view] .edrv-dapseg-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dapseg-act { flex: 0 0 auto; min-width: 20px; height: 18px; padding: 0 4px; border: none; border-radius: 4px; background: transparent; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 11px; line-height: 1; cursor: pointer; opacity: 0; }\n[data-edrv-view] .edrv-dapseg-head:hover .edrv-dapseg-act { opacity: 1; }\n[data-edrv-view] .edrv-dapseg-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dapempty { font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 4px 2px; }\n[data-edrv-view] .edrv-dapframe { display: flex; align-items: baseline; gap: 8px; padding: 3px 4px; border-radius: 6px; cursor: pointer; font-size: 12px; }\n[data-edrv-view] .edrv-dapframe:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-dapframe-name { color: var(--dsw-alias-label-primary, inherit); font-weight: 600; }\n[data-edrv-view] .edrv-dapframe-loc { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dapscope { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); padding: 4px 2px 1px; }\n[data-edrv-view] .edrv-dapvar { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; font-size: 12px; padding: 1px 2px; min-width: 0; }\n[data-edrv-view] .edrv-dapvar-tw { flex: 0 0 auto; width: 14px; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 10px; padding: 0; }\n[data-edrv-view] span.edrv-dapvar-tw { cursor: default; }\n[data-edrv-view] .edrv-dapvar-name { flex: 0 1 34%; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, inherit); font-family: var(--ds-font-family-code, ui-monospace, monospace); white-space: nowrap; }\n[data-edrv-view] .edrv-dapvar-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapvar-type { flex: 0 0 auto; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dapframe.selected { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .16)); outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }\n[data-edrv-view] .edrv-dapwatch { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; min-width: 0; font-size: 12px; padding: 1px 2px; }\n[data-edrv-view] .edrv-dapwatch-x { border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; width: 14px; }\n[data-edrv-view] .edrv-dapwatch-x:hover { color: var(--dsw-alias-state-error-primary, #e05252); }\n[data-edrv-view] .edrv-dapwatch-add { padding: 2px 0; }\n[data-edrv-view] .edrv-dapbp { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 2px 2px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-dapbp:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-dapbp:active { background: var(--dsw-alias-interactive-bg-pressed, rgba(127, 127, 127, .2)); }\n[data-edrv-view] .edrv-dapbp-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }\n[data-edrv-view] .edrv-dapbp-dot.off { background: transparent; border: 1.5px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }\n[data-edrv-view] .edrv-dapbp-file { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: none; background: transparent; color: var(--dsw-alias-label-primary, inherit); cursor: pointer; padding: 0; font-size: 12px; }\n[data-edrv-view] .edrv-dapbp-dir { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; text-align: right; }\n[data-edrv-view] .edrv-dapbp-line { flex: 0 0 auto; font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapbp-x { flex: 0 0 auto; border: none; background: transparent; color: transparent; cursor: pointer; width: 14px; }\n[data-edrv-view] .edrv-dapbp:hover .edrv-dapbp-x { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dapbp-x:hover { color: var(--dsw-alias-state-error-primary, #e05252) !important; }\n[data-edrv-view] .edrv-daprepl { display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-daprepl-log { display: flex; flex-direction: column; gap: 1px; max-height: 180px; overflow: auto; }\n[data-edrv-view] .edrv-daprepl-line { font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); color: var(--dsw-alias-label-secondary, #52606d); white-space: pre-wrap; word-break: break-all; display: flex; gap: 6px; }\n[data-edrv-view] .edrv-daprepl-line.out { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-daprepl-in { width: 100%; box-sizing: border-box; height: 26px; padding: 0 8px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dappanel-run { flex: 0 0 auto; width: 24px; height: 24px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-dappanel-run:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }\n[data-edrv-view] .edrv-dappanel-run:disabled { opacity: .35; cursor: default; background: transparent; }\n/* 面板顶部配置下拉(与编辑器浮动条 .edrv-dapbar-cfg 同视觉)。 */\n[data-edrv-view] .edrv-dappanel-cfg-select { max-width: 190px; height: 24px; padding: 0 4px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n/* 断点行内编辑行(面板内展开编辑,控件复用 .edrv-bpwidget-* 视觉;左缩进对齐断点行文字列)。 */\n[data-edrv-view] .edrv-dapbp-edit { display: flex; align-items: center; gap: 6px; padding: 3px 2px 3px 20px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-mode { height: 20px; }\n[data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-input { height: 20px; }\n/* 断点回执:未验证空心红圈(须排在 .off 之后,禁用态优先展示灰圈语义)。 */\n[data-edrv-view] .edrv-dapbp-dot.unverified { background: transparent; border: 1.5px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }\n/* 断点行内编辑浮层(Monaco content widget,锚定断点行上方;对齐 VS Code breakpointWidget 形态)。 */\n/* View Zone 浮层:宽度/左移由 BpWidget.syncWidth 按 layout 内联设置\n (Monaco .view-zones 仅内容区宽,需补偿 contentLeft 与滚动条),此处只管视觉。 */\n[data-edrv-view] .edrv-bpwidget { position: relative; box-sizing: border-box; display: flex; align-items: center; gap: 6px; padding: 3px 8px; border-top: 1px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-layer-1, #f7f9fa); }\n/* 断点编辑中的目标行高亮(浮层打开期间) */\n[data-edrv-view] .edrv-dap-editing-line { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-bpwidget-mode { flex: 0 0 auto; height: 22px; padding: 0 4px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); cursor: pointer; }\n[data-edrv-view] .edrv-bpwidget-input { flex: 1 1 auto !important; min-width: 0; width: auto !important; box-sizing: border-box; height: 22px; padding: 0 6px; font-size: 12px; font-family: var(--ds-font-family-code, ui-monospace, monospace); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-bpwidget-input:focus { outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }\n\n/* 断点 hover 预览(glyph 区):半透明小红点 + pointer 光标(对齐 VS Code .codicon-debug-hint)。 */\n[data-edrv-view] .edrv-dap-bp-hint { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-hint::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); opacity: .4; }\n/* 行号区也给出可点提示(与 VS Code 一致:glyph 区与行号区都可点/可 hover);\n 不拦截折叠箭头等自有控件的光标。 */\n[data-edrv-view] .edrv-editor-row .monaco-editor .margin-view-overlays .line-numbers:hover { cursor: pointer; }\n\n/* 暂停态 hover 调试块 + 变量树(成员全量直显 + table 成员折叠展开)。\n ⚠️ 选择器一律用 data-*:本仓 vendored Monaco 的 markdown→DOM 管线会剥掉我们 HTML 里的\n class(supportHtml/isTrusted 都不豁免),只有 data-* 存活;用 class 会静默失效。 */\n[data-edrv-view] [data-edrv-dap] { display: block; min-width: 0; }\n[data-edrv-view] [data-edrv-head] { display: flex; align-items: baseline; gap: 2px; }\n[data-edrv-view] [data-edrv-head] [data-edrv-expr] { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-head] [data-edrv-val] { font-family: var(--ds-font-family-code, ui-monospace, monospace); word-break: break-all; }\n[data-edrv-view] [data-edrv-type] { margin-top: 2px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-tree] { margin: 2px 0 0; padding: 0; list-style: none; font-variant-numeric: tabular-nums; }\n[data-edrv-view] [data-edrv-line] { display: flex; align-items: baseline; gap: 4px; padding: 1px 2px; border-radius: 3px; white-space: nowrap; }\n[data-edrv-view] [data-edrv-line]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n/* 折叠按钮(对齐 CodeBuddy 调试浮窗变量树的 twist):热区 14px + hover 反馈,未可展开时留空占位 */\n[data-edrv-view] [data-edrv-twist] { flex: 0 0 auto; box-sizing: border-box; width: 14px; height: 14px; line-height: 13px; text-align: center; border-radius: 3px; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-twist]:not(:empty) { cursor: pointer; user-select: none; }\n[data-edrv-view] [data-edrv-twist]:not(:empty):hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .2)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-name] { flex: 0 0 auto; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-line] [data-edrv-eq] { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-line] [data-edrv-val] { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] [data-edrv-note] { color: var(--dsw-alias-label-tertiary, #8a949b); font-style: italic; }\n/* Alt 提示层:由 hoverTree 注入到 .monaco-hover(滚动区之外)贴底常驻;容器预留同高内边距避免遮挡末行。\n 不再用 sticky / `:has()`:滚动链在 .monaco-hover-content 上,sticky 会被滚走。 */\n[data-edrv-view] .monaco-hover[data-edrv-hint-host=\"1\"] { padding-bottom: 22px; }\n[data-edrv-view] [data-edrv-hint=\"1\"] { position: absolute; left: 0; right: 0; bottom: 0; box-sizing: border-box; height: 22px; padding: 2px 8px; font-size: 11px; line-height: 18px; color: var(--dsw-alias-label-tertiary, #8a949b); background: var(--dsw-alias-bg-overlay, #ffffff); border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }";
|
|
36
37
|
const tagId$1 = "dsh-vscode-mode/editor.css";
|
|
37
38
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
38
39
|
const tag = document.createElement("style");
|
|
@@ -61,6 +62,155 @@ window.__ModuleLoader__.load({
|
|
|
61
62
|
avif: "image/avif",
|
|
62
63
|
svg: "image/svg+xml"
|
|
63
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* 路径 → 图片 MIME:按 basename 扩展名小写查 {@link IMAGE_MIME},未命中回退 application/octet-stream。
|
|
67
|
+
* @author ddj 2026年09月08号
|
|
68
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
69
|
+
* @returns MIME 字符串
|
|
70
|
+
*/
|
|
71
|
+
function imageMimeOf(path) {
|
|
72
|
+
const base = String(path || "").split(/[\\/]/).pop() || "";
|
|
73
|
+
const dot = base.lastIndexOf(".");
|
|
74
|
+
const ext = dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
75
|
+
return IMAGE_MIME[ext] ?? "application/octet-stream";
|
|
76
|
+
}
|
|
77
|
+
/** PDF 统一 MIME(host base64 响应与 client PDF 面板同源,避免两处漂移)。 */
|
|
78
|
+
const PDF_MIME = "application/pdf";
|
|
79
|
+
/**
|
|
80
|
+
* 路径 → 二进制预览 MIME:PDF 特判 {@link PDF_MIME},其余走图片表回退 octet-stream。
|
|
81
|
+
* @author ddj 2026年09月22号
|
|
82
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
83
|
+
* @returns MIME 字符串
|
|
84
|
+
*/
|
|
85
|
+
function binaryMimeOf(path) {
|
|
86
|
+
const base = String(path || "").split(/[\\/]/).pop() || "";
|
|
87
|
+
const dot = base.lastIndexOf(".");
|
|
88
|
+
if (dot > 0 && base.slice(dot + 1).toLowerCase() === "pdf") return PDF_MIME;
|
|
89
|
+
return imageMimeOf(path);
|
|
90
|
+
}
|
|
91
|
+
/** 二进制读成功响应头(octet-stream 携带元数据;同源请求可直接读取自定义头)。 */
|
|
92
|
+
const BINARY_READ_HEADERS = {
|
|
93
|
+
mime: "x-edrv-mime",
|
|
94
|
+
size: "x-edrv-size",
|
|
95
|
+
version: "x-edrv-version"
|
|
96
|
+
};
|
|
97
|
+
/** 通道探测状态:unknown=按次尝试;unwired=host 有 handler 但 routes 仍强制 JSON(本页不再尝试)。 */
|
|
98
|
+
let binaryTransport = "unknown";
|
|
99
|
+
/**
|
|
100
|
+
* 二进制预览读取(edrv.readBinary,失败自动回退 edrv.read base64 现有路径)。
|
|
101
|
+
* 降级链:非 200 / 非 octet-stream / 元数据头缺失或长度不符 / 网络错 / host 未接线
|
|
102
|
+
* 把信封 JSON 化 → 一律立即回退 base64 路径,功能不降级;
|
|
103
|
+
* 其中「信封被 JSON 化」判定为未接线,探测一次后本页生命周期内不再尝试新通道
|
|
104
|
+
* (避免每次读图都付一次无效往返与大响应序列化);其余失败不关停通道(瞬时故障可自愈)。
|
|
105
|
+
* @author ddj 2026年09月22号
|
|
106
|
+
* @param rpcJson 类型化 JSON RPC 调用(调用方传入 client rpc 包装)
|
|
107
|
+
* @param args edrv.readBinary 请求参数
|
|
108
|
+
* @returns 统一读取结果(via 标记实际通道;probed 供调用方做一次性回退日志)
|
|
109
|
+
*/
|
|
110
|
+
async function readBinaryPreview(rpcJson, args) {
|
|
111
|
+
const attempted = binaryTransport !== "unwired";
|
|
112
|
+
if (attempted) {
|
|
113
|
+
const direct = await tryBinaryRead(args);
|
|
114
|
+
if (direct) return direct;
|
|
115
|
+
}
|
|
116
|
+
return fallbackRead(rpcJson, args, attempted);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 新通道单次尝试:响应完全符合约定才返回二进制结果,否则返回 null 触发回退。
|
|
120
|
+
* @author ddj 2026年09月22号
|
|
121
|
+
* @param args edrv.readBinary 请求参数
|
|
122
|
+
* @returns 二进制读取结果或 null(null=回退 base64)
|
|
123
|
+
*/
|
|
124
|
+
async function tryBinaryRead(args) {
|
|
125
|
+
try {
|
|
126
|
+
const res = await fetch(RPC_PATH, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: { "content-type": "application/json" },
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
method: "edrv.readBinary",
|
|
131
|
+
args
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) return null;
|
|
135
|
+
if (!(res.headers.get("content-type") ?? "").includes("application/octet-stream")) return noteJsonBody(res);
|
|
136
|
+
const size = Number(res.headers.get(BINARY_READ_HEADERS.size));
|
|
137
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
138
|
+
if (!Number.isFinite(size) || size !== bytes.byteLength) return null;
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
via: "binary",
|
|
142
|
+
bytes,
|
|
143
|
+
mime: res.headers.get(BINARY_READ_HEADERS.mime) || binaryMimeOf(args.path),
|
|
144
|
+
size: bytes.byteLength,
|
|
145
|
+
version: res.headers.get(BINARY_READ_HEADERS.version) || ""
|
|
146
|
+
};
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 非 octet-stream 响应体判定:host 已有 edrv.readBinary 但 routes 尚未接线时,
|
|
153
|
+
* 二进制信封会被 JSON 序列化成 {bytes:{0:..}} 普通对象——识别到该形态即标记
|
|
154
|
+
* 「未接线」(本页不再尝试);其余 JSON(错误响应/未知方法)仅本次回退不关停。
|
|
155
|
+
* @author ddj 2026年09月22号
|
|
156
|
+
* @param res JSON 形态的响应
|
|
157
|
+
* @returns 恒为 null(统一由调用方回退)
|
|
158
|
+
*/
|
|
159
|
+
async function noteJsonBody(res) {
|
|
160
|
+
try {
|
|
161
|
+
const body = await res.json();
|
|
162
|
+
if (body && body.ok === true && body.binary && typeof body.binary === "object" && body.binary.bytes != null) binaryTransport = "unwired";
|
|
163
|
+
} catch (error) {}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* base64 回退读取(现有 edrv.read JSON 通道,载荷形状与历史实现一致)。
|
|
168
|
+
* @author ddj 2026年09月22号
|
|
169
|
+
* @param rpcJson 类型化 JSON RPC 调用
|
|
170
|
+
* @param args 请求参数
|
|
171
|
+
* @param attempted 本次是否尝试过新通道(回带给调用方做一次性日志)
|
|
172
|
+
* @returns 统一读取结果(调用失败转错误结果,不向上抛)
|
|
173
|
+
*/
|
|
174
|
+
async function fallbackRead(rpcJson, args, attempted) {
|
|
175
|
+
const probed = attempted ? { probed: true } : {};
|
|
176
|
+
try {
|
|
177
|
+
const res = await rpcJson("edrv.read", {
|
|
178
|
+
sessionId: args.sessionId,
|
|
179
|
+
path: args.path,
|
|
180
|
+
encoding: "base64"
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
const legacy = res;
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
error: legacy.error,
|
|
187
|
+
...probed,
|
|
188
|
+
...legacy.resolvedPath !== void 0 ? { resolvedPath: legacy.resolvedPath } : {}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (res.encoding !== "base64") return {
|
|
192
|
+
ok: false,
|
|
193
|
+
error: "读取失败",
|
|
194
|
+
...probed
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
ok: true,
|
|
198
|
+
via: "base64",
|
|
199
|
+
content: res.content,
|
|
200
|
+
size: res.size,
|
|
201
|
+
encoding: res.encoding,
|
|
202
|
+
mime: res.mime,
|
|
203
|
+
version: res.version,
|
|
204
|
+
...probed
|
|
205
|
+
};
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
error: "read异常:" + String(error),
|
|
210
|
+
...probed
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
64
214
|
//#endregion
|
|
65
215
|
//#region src/client/rpc.ts
|
|
66
216
|
/**
|
|
@@ -1646,8 +1796,9 @@ window.__ModuleLoader__.load({
|
|
|
1646
1796
|
//#endregion
|
|
1647
1797
|
//#region src/client/imagePreview.ts
|
|
1648
1798
|
/**
|
|
1649
|
-
* dsh-vscode-mode client —
|
|
1650
|
-
* 纯函数、无 DOM 依赖,便于单测;MIME 表收敛在 shared/rpc.ts(host base64
|
|
1799
|
+
* dsh-vscode-mode client — 图片文件判定、data URL 组装与缩放纯逻辑(编辑区图片预览面板用)。
|
|
1800
|
+
* 纯函数、无 DOM 依赖,便于单测;MIME 表收敛在 shared/rpc.ts(host base64 响应同源);
|
|
1801
|
+
* 缩放:步进/clamp 为纯函数,页签记忆为模块级 Map(会话+路径键,EditorView 卸载清空、不持久化)。
|
|
1651
1802
|
* @author ddj 2026年09月08号
|
|
1652
1803
|
*/
|
|
1653
1804
|
/** 可预览的图片扩展名集合(小写、无点)。 */
|
|
@@ -1685,6 +1836,66 @@ window.__ModuleLoader__.load({
|
|
|
1685
1836
|
function dataUrlOf(base64, mime) {
|
|
1686
1837
|
return "data:" + (mime || "application/octet-stream") + ";base64," + base64;
|
|
1687
1838
|
}
|
|
1839
|
+
/** 图片缩放下限(25%,低于此档不再缩小)。 */
|
|
1840
|
+
const ZOOM_MIN = .25;
|
|
1841
|
+
/** 图片缩放步进(10%/档,−/+ 按钮每点一次的变化量)。 */
|
|
1842
|
+
const ZOOM_STEP = .1;
|
|
1843
|
+
/** 页签缩放记忆表:键 = zoomKeyOf(会话, 路径),值 = 显式缩放或 null(适应宽度)。 */
|
|
1844
|
+
const zoomMem = /* @__PURE__ */ new Map();
|
|
1845
|
+
/**
|
|
1846
|
+
* 图片缩放收敛:clamp 到 25%–400% 并按整百分比取整(消除浮点步进噪声)。
|
|
1847
|
+
* @author ddj 2026年09月22号
|
|
1848
|
+
* @param zoom 目标缩放(非有限数值按 100% 处理)
|
|
1849
|
+
* @returns 收敛后的缩放比例
|
|
1850
|
+
*/
|
|
1851
|
+
function clampZoom(zoom) {
|
|
1852
|
+
return Math.round(Math.min(4, Math.max(ZOOM_MIN, Number.isFinite(zoom) ? zoom : 1)) * 100) / 100;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* 图片缩放步进:按 10%/档变化并 clamp(dir 为负即缩小,其余按放大处理)。
|
|
1856
|
+
* @author ddj 2026年09月22号
|
|
1857
|
+
* @param base 起始缩放(适应宽度态由调用方实测当前渲染缩放传入)
|
|
1858
|
+
* @param dir 方向:< 0 缩小,≥ 0 放大
|
|
1859
|
+
* @returns 步进后的缩放比例
|
|
1860
|
+
*/
|
|
1861
|
+
function zoomStepOf(base, dir) {
|
|
1862
|
+
return clampZoom(base + (dir < 0 ? -.1 : ZOOM_STEP));
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* 页签缩放记忆键(会话 + 路径;NUL 分隔,防止两段拼接撞键)。
|
|
1866
|
+
* @author ddj 2026年09月22号
|
|
1867
|
+
* @param sessionId 会话 id
|
|
1868
|
+
* @param path 图片路径
|
|
1869
|
+
* @returns 记忆键
|
|
1870
|
+
*/
|
|
1871
|
+
function zoomKeyOf(sessionId, path) {
|
|
1872
|
+
return String(sessionId ?? "") + "\0" + String(path ?? "");
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* 写入页签缩放记忆(null = 适应宽度;会话级生命周期,不进 localStorage)。
|
|
1876
|
+
* @author ddj 2026年09月22号
|
|
1877
|
+
* @param key zoomKeyOf 产出的记忆键
|
|
1878
|
+
* @param zoom 记忆缩放(null = 适应宽度)
|
|
1879
|
+
*/
|
|
1880
|
+
function rememberZoom(key, zoom) {
|
|
1881
|
+
zoomMem.set(key, zoom);
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* 读取页签缩放记忆(无记忆返回 null,调用方按初始适应宽度处理)。
|
|
1885
|
+
* @author ddj 2026年09月22号
|
|
1886
|
+
* @param key zoomKeyOf 产出的记忆键
|
|
1887
|
+
* @returns 记忆缩放或 null
|
|
1888
|
+
*/
|
|
1889
|
+
function recallZoom(key) {
|
|
1890
|
+
return zoomMem.get(key) ?? null;
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* 清空全部页签缩放记忆(EditorView 卸载时调用)。
|
|
1894
|
+
* @author ddj 2026年09月22号
|
|
1895
|
+
*/
|
|
1896
|
+
function clearZoomMem() {
|
|
1897
|
+
zoomMem.clear();
|
|
1898
|
+
}
|
|
1688
1899
|
//#endregion
|
|
1689
1900
|
//#region src/client/markdownPreview.ts
|
|
1690
1901
|
/**
|
|
@@ -1839,6 +2050,311 @@ window.__ModuleLoader__.load({
|
|
|
1839
2050
|
for (let i = 0; i < bytes.length; i += chunk) bin += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
1840
2051
|
return btoa(bin);
|
|
1841
2052
|
}
|
|
2053
|
+
//#endregion
|
|
2054
|
+
//#region src/shared/nativeOpen.ts
|
|
2055
|
+
/**
|
|
2056
|
+
* dsh-vscode-mode shared — 原生打开范围判定(host/client 双面契约)。
|
|
2057
|
+
* 背景:DSH 文件预览双形态——本插件认领 `dsh-resource://file/**` 进 Monaco 编辑,
|
|
2058
|
+
* 官方渲染器(Office 预览/不可预览提示/表格预览)看认领让位。本模块承载「原生打开」
|
|
2059
|
+
* 范围的单一事实源:默认集 = 让位清单(插件本来就不认领的后缀),用户可经设置
|
|
2060
|
+
* `nativeOpenExts`(逗号分隔)增删;host 的 Config schema 默认值与 client 的
|
|
2061
|
+
* 文件树点击判定 / claim 认领判定共用,保证三处永不同源漂移。
|
|
2062
|
+
* 决策沿袭(不因本模块迁移而变):CSV/TSV **不在**默认集——官方 0.1.7 有表格预览,
|
|
2063
|
+
* 但编辑器可编辑性优先,仅用户显式加入 nativeOpenExts 才让位(同 avif 例外先例)。
|
|
2064
|
+
* 作者 ddj 2026年09月22号
|
|
2065
|
+
*/
|
|
2066
|
+
/**
|
|
2067
|
+
* Office 文档后缀:官方 `dsh-client-ui-sidebar-documentpreview` 的 Office 渲染器
|
|
2068
|
+
* 声明 `doc/docx/xls/xlsx/ppt/pptx`(0.1.6 起侧栏 Office 预览;0.1.7 扩展表格 CSV/TSV
|
|
2069
|
+
* 只读预览——本清单刻意不含 csv/tsv,见文件头决策)。本插件让位官方(不认领),
|
|
2070
|
+
* 否则会把这些文件路由进 Monaco 而丢掉官方预览。
|
|
2071
|
+
*/
|
|
2072
|
+
const OFFICE_EXT = [
|
|
2073
|
+
"doc",
|
|
2074
|
+
"docx",
|
|
2075
|
+
"xls",
|
|
2076
|
+
"xlsx",
|
|
2077
|
+
"ppt",
|
|
2078
|
+
"pptx",
|
|
2079
|
+
"odt",
|
|
2080
|
+
"ods",
|
|
2081
|
+
"odp",
|
|
2082
|
+
"pages",
|
|
2083
|
+
"numbers"
|
|
2084
|
+
];
|
|
2085
|
+
/**
|
|
2086
|
+
* 官方「不可预览二进制容器」清单(`UNVIEWABLE_BINARY_EXTENSIONS`,逐项取自
|
|
2087
|
+
* `dsh-client-ui-sidebar-documentpreview/lib/client.js`)。这些后缀官方会给出
|
|
2088
|
+
* 「无法预览」提示;本插件让位官方,避免把二进制当文本读成乱码。
|
|
2089
|
+
*
|
|
2090
|
+
* ⚠️ 刻意例外:官方清单含 `avif`,但本插件图片预览支持 avif(浏览器原生解码),
|
|
2091
|
+
* 故从本表**移除** `avif` —— 保留本插件的图片预览优于官方的「不可预览」。
|
|
2092
|
+
* @author ddj 2026年09月18号
|
|
2093
|
+
*/
|
|
2094
|
+
const BLIND_EXT = [
|
|
2095
|
+
"mp4",
|
|
2096
|
+
"mov",
|
|
2097
|
+
"avi",
|
|
2098
|
+
"mkv",
|
|
2099
|
+
"webm",
|
|
2100
|
+
"flv",
|
|
2101
|
+
"wmv",
|
|
2102
|
+
"m4v",
|
|
2103
|
+
"mp3",
|
|
2104
|
+
"wav",
|
|
2105
|
+
"flac",
|
|
2106
|
+
"ogg",
|
|
2107
|
+
"m4a",
|
|
2108
|
+
"aac",
|
|
2109
|
+
"wma",
|
|
2110
|
+
"opus",
|
|
2111
|
+
"zip",
|
|
2112
|
+
"gz",
|
|
2113
|
+
"tgz",
|
|
2114
|
+
"bz2",
|
|
2115
|
+
"xz",
|
|
2116
|
+
"zst",
|
|
2117
|
+
"7z",
|
|
2118
|
+
"rar",
|
|
2119
|
+
"tar",
|
|
2120
|
+
"jar",
|
|
2121
|
+
"exe",
|
|
2122
|
+
"dll",
|
|
2123
|
+
"so",
|
|
2124
|
+
"dylib",
|
|
2125
|
+
"bin",
|
|
2126
|
+
"o",
|
|
2127
|
+
"class",
|
|
2128
|
+
"pyc",
|
|
2129
|
+
"wasm",
|
|
2130
|
+
"ttf",
|
|
2131
|
+
"otf",
|
|
2132
|
+
"woff",
|
|
2133
|
+
"woff2",
|
|
2134
|
+
"eot",
|
|
2135
|
+
"dmg",
|
|
2136
|
+
"iso",
|
|
2137
|
+
"img",
|
|
2138
|
+
"sqlite",
|
|
2139
|
+
"db",
|
|
2140
|
+
"psd",
|
|
2141
|
+
"ai",
|
|
2142
|
+
"sketch",
|
|
2143
|
+
"tiff",
|
|
2144
|
+
"tif",
|
|
2145
|
+
"heic",
|
|
2146
|
+
"heif"
|
|
2147
|
+
];
|
|
2148
|
+
/** 原生打开默认后缀集 = 让位清单并集(两清单元素天然互斥,直接拼接)。 */
|
|
2149
|
+
const DEFAULT_NATIVE_EXT = [...OFFICE_EXT, ...BLIND_EXT];
|
|
2150
|
+
/** 默认范围的设置序列(逗号分隔;Config schema 默认值与设置页回显同源)。 */
|
|
2151
|
+
const DEFAULT_NATIVE_CSV = DEFAULT_NATIVE_EXT.join(",");
|
|
2152
|
+
/**
|
|
2153
|
+
* 取 path 的 basename 后缀(小写、无点);无后缀/隐藏文件返回 ''。
|
|
2154
|
+
* @author ddj 2026年09月22号
|
|
2155
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
2156
|
+
* @returns 小写后缀或 ''
|
|
2157
|
+
*/
|
|
2158
|
+
function suffixOf(path) {
|
|
2159
|
+
const base = String(path ?? "").replace(/\\/g, "/").split("/").pop() ?? "";
|
|
2160
|
+
const dot = base.lastIndexOf(".");
|
|
2161
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* 解析设置串为后缀集合:逗号分隔、去空白、去前导点、小写;空段丢弃。
|
|
2165
|
+
* 解析失败/空串 → 空集合(= 不追加任何原生打开范围,让位仍走 deferToOfficial)。
|
|
2166
|
+
* @author ddj 2026年09月22号
|
|
2167
|
+
* @param csv 逗号分隔后缀串
|
|
2168
|
+
* @returns 后缀集合
|
|
2169
|
+
*/
|
|
2170
|
+
function parseExtCsv(csv) {
|
|
2171
|
+
const out = /* @__PURE__ */ new Set();
|
|
2172
|
+
if (typeof csv !== "string") return out;
|
|
2173
|
+
for (const raw of csv.split(",")) {
|
|
2174
|
+
const item = raw.trim().replace(/^\./, "").toLowerCase();
|
|
2175
|
+
if (item) out.add(item);
|
|
2176
|
+
}
|
|
2177
|
+
return out;
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* 该路径是否命中「原生打开」范围(仅看用户配置集合;默认集语义由调用方
|
|
2181
|
+
* 与 deferToOfficial 取并,保持两判定各司其职)。
|
|
2182
|
+
* @author ddj 2026年09月22号
|
|
2183
|
+
* @param path 文件路径
|
|
2184
|
+
* @param exts 已解析的后缀集合(parseExtCsv 产物)
|
|
2185
|
+
* @returns 是否命中
|
|
2186
|
+
*/
|
|
2187
|
+
function inNativeOpen(path, exts) {
|
|
2188
|
+
const ext = suffixOf(path);
|
|
2189
|
+
return ext !== "" && exts.has(ext);
|
|
2190
|
+
}
|
|
2191
|
+
//#endregion
|
|
2192
|
+
//#region src/client/nativeOpenStore.ts
|
|
2193
|
+
/**
|
|
2194
|
+
* dsh-vscode-mode client — 原生打开范围的运行时状态(设置值缓存 + 官方打开 handler)。
|
|
2195
|
+
* 职责:
|
|
2196
|
+
* 1. 缓存设置字段 `nativeOpenExts` 的解析结果(mountSettingsSyncs 的 keybindings 同步
|
|
2197
|
+
* 块每拍推送;旧版设置无该字段 → 保持默认集,与 deferToOfficial 同源 shared 事实源);
|
|
2198
|
+
* 2. 持有「原生打开」执行 handler(index.ts 装配时注册 sidebarRight.openResource 包装),
|
|
2199
|
+
* EditorView 的 openFile 链路命中判定时调用,失败回退现状打开。
|
|
2200
|
+
* 单例模块级状态:值变更经 CustomEvent 广播(跨组件按需订阅;消费方多为每调用读缓存)。
|
|
2201
|
+
* @author ddj 2026年09月22号
|
|
2202
|
+
*/
|
|
2203
|
+
/** 设置值变化广播事件名(mountSettingsSyncs 推送时派发;幂等值不派发)。 */
|
|
2204
|
+
const NATIVE_OPEN_EVENT = "edrv:native-open-exts-change";
|
|
2205
|
+
/** 当前设置串(设置页回显 / 诊断用)。 */
|
|
2206
|
+
let csv = DEFAULT_NATIVE_CSV;
|
|
2207
|
+
/** 解析后的后缀集合(热路径读取,避免每次点击 split)。 */
|
|
2208
|
+
let exts = new Set(DEFAULT_NATIVE_EXT);
|
|
2209
|
+
/** 官方原生打开 handler(index.ts 装配注册;返回 false/缺失 = 回退现状打开)。 */
|
|
2210
|
+
let handler = null;
|
|
2211
|
+
/**
|
|
2212
|
+
* 该路径是否命中用户配置的原生打开范围(不含 deferToOfficial 默认让位语义——
|
|
2213
|
+
* 那部分由调用方自行取并,保持两判定各司其职)。
|
|
2214
|
+
* @author ddj 2026年09月22号
|
|
2215
|
+
* @param path 文件路径
|
|
2216
|
+
* @returns 是否命中
|
|
2217
|
+
*/
|
|
2218
|
+
function inNativePath(path) {
|
|
2219
|
+
return inNativeOpen(path, exts);
|
|
2220
|
+
}
|
|
2221
|
+
/**
|
|
2222
|
+
* 推送设置值(mountSettingsSyncs 每拍调用):解析为集合,值变化才更新并广播。
|
|
2223
|
+
* 非法/空输入 → 回落默认集(与 host schema default 同源)。
|
|
2224
|
+
* @author ddj 2026年09月22号
|
|
2225
|
+
* @param value 设置原值(unknown,兼容旧版 undefined)
|
|
2226
|
+
*/
|
|
2227
|
+
function setNativeCsv(value) {
|
|
2228
|
+
const next = typeof value === "string" && value.trim() ? value : DEFAULT_NATIVE_CSV;
|
|
2229
|
+
if (next === csv) return;
|
|
2230
|
+
csv = next;
|
|
2231
|
+
exts = parseExtCsv(next);
|
|
2232
|
+
if (exts.size === 0) exts = new Set(DEFAULT_NATIVE_EXT);
|
|
2233
|
+
try {
|
|
2234
|
+
window.dispatchEvent(new CustomEvent(NATIVE_OPEN_EVENT, { detail: { value: csv } }));
|
|
2235
|
+
} catch {}
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* 注册原生打开执行 handler(index.ts 在官方 sidebarRight.openResource 可用时注册)。
|
|
2239
|
+
* @author ddj 2026年09月22号
|
|
2240
|
+
* @param fn handler(返回是否已原生打开成功);null 解除
|
|
2241
|
+
*/
|
|
2242
|
+
function setNativeOpenHandler(fn) {
|
|
2243
|
+
handler = fn;
|
|
2244
|
+
}
|
|
2245
|
+
/**
|
|
2246
|
+
* 尝试原生打开(EditorView openFile 链路命中判定时调用)。
|
|
2247
|
+
* @author ddj 2026年09月22号
|
|
2248
|
+
* @param path 文件路径
|
|
2249
|
+
* @returns true=已走官方原生打开;false=handler 缺失或失败(调用方回退现状)
|
|
2250
|
+
*/
|
|
2251
|
+
function tryNativeOpen(path) {
|
|
2252
|
+
if (!handler) return false;
|
|
2253
|
+
try {
|
|
2254
|
+
return handler(path) === true;
|
|
2255
|
+
} catch {
|
|
2256
|
+
return false;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
/** 复位(卸载/测试隔离用:回默认集、解绑 handler)。 */
|
|
2260
|
+
function resetNativeOpen() {
|
|
2261
|
+
csv = DEFAULT_NATIVE_CSV;
|
|
2262
|
+
exts = new Set(DEFAULT_NATIVE_EXT);
|
|
2263
|
+
handler = null;
|
|
2264
|
+
}
|
|
2265
|
+
//#endregion
|
|
2266
|
+
//#region src/client/pendingNav.ts
|
|
2267
|
+
/** window 槽键名(跨热重载存活的全局挂点)。 */
|
|
2268
|
+
const PENDING_NAV_KEY = "__edrvPendingNav__";
|
|
2269
|
+
/** 交接有效期:超过即失效清槽(点击意图的有效窗口,防陈旧跳转)。 */
|
|
2270
|
+
const PENDING_NAV_TTL = 3e4;
|
|
2271
|
+
/**
|
|
2272
|
+
* 形状守卫:非法入参返回 null,合法则归一化为 PendingNav。
|
|
2273
|
+
* 行列非有限数字一律归 null(缺省语义 = 不定位行),path 必须为非空字符串。
|
|
2274
|
+
* @author ddj 2026年09月23号
|
|
2275
|
+
* @param raw 任意来源的原始值
|
|
2276
|
+
* @returns 归一化后的 PendingNav 或 null
|
|
2277
|
+
*/
|
|
2278
|
+
function sanitizeNav(raw) {
|
|
2279
|
+
if (!raw || typeof raw !== "object") return null;
|
|
2280
|
+
const src = raw;
|
|
2281
|
+
if (typeof src.path !== "string" || !src.path) return null;
|
|
2282
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
2283
|
+
const at = num(src.at);
|
|
2284
|
+
if (at === null) return null;
|
|
2285
|
+
return {
|
|
2286
|
+
path: src.path,
|
|
2287
|
+
line: num(src.line),
|
|
2288
|
+
column: num(src.column),
|
|
2289
|
+
endLine: num(src.endLine),
|
|
2290
|
+
endColumn: num(src.endColumn),
|
|
2291
|
+
at
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* 新鲜度判定:形状合法且未超过 TTL 才算有效;过期返回 null。
|
|
2296
|
+
* @author ddj 2026年09月23号
|
|
2297
|
+
* @param nav 已归一化的待跳转(可空)
|
|
2298
|
+
* @param now 当前时刻(毫秒)
|
|
2299
|
+
* @param ttl 有效期(毫秒)
|
|
2300
|
+
* @returns 有效导航或 null
|
|
2301
|
+
*/
|
|
2302
|
+
function freshNav(nav, now, ttl) {
|
|
2303
|
+
if (!nav) return null;
|
|
2304
|
+
return now - nav.at <= ttl ? nav : null;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* 取槽宿主:仅浏览器环境返回 window,否则 undefined(测试/SSR 降级)。
|
|
2308
|
+
* @author ddj 2026年09月23号
|
|
2309
|
+
*/
|
|
2310
|
+
function slotHost() {
|
|
2311
|
+
if (typeof window === "undefined") return void 0;
|
|
2312
|
+
return window;
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* 写入待跳转(盖写整槽;at 由调用方给定或取当前时间)。
|
|
2316
|
+
* 非法 path 直接丢弃,不写槽。
|
|
2317
|
+
* @author ddj 2026年09月23号
|
|
2318
|
+
* @param nav 待跳转内容(不含 at)
|
|
2319
|
+
* @param now 写入时刻(测试注入用)
|
|
2320
|
+
* @returns 是否写入成功
|
|
2321
|
+
*/
|
|
2322
|
+
function putPendingNav(nav, now = Date.now()) {
|
|
2323
|
+
const host = slotHost();
|
|
2324
|
+
if (!host) return false;
|
|
2325
|
+
if (typeof nav?.path !== "string" || !nav.path) return false;
|
|
2326
|
+
host[PENDING_NAV_KEY] = {
|
|
2327
|
+
...nav,
|
|
2328
|
+
at: now
|
|
2329
|
+
};
|
|
2330
|
+
return true;
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* 读取新鲜的待跳转(**不消费**;过期或形状非法才清槽)。
|
|
2334
|
+
* @author ddj 2026年09月23号
|
|
2335
|
+
* @param now 当前时刻(测试注入用)
|
|
2336
|
+
* @param ttl 有效期(毫秒)
|
|
2337
|
+
* @returns 有效导航或 null
|
|
2338
|
+
*/
|
|
2339
|
+
function readPendingNav(now = Date.now(), ttl = PENDING_NAV_TTL) {
|
|
2340
|
+
const host = slotHost();
|
|
2341
|
+
if (!host) return null;
|
|
2342
|
+
const nav = freshNav(sanitizeNav(host[PENDING_NAV_KEY]), now, ttl);
|
|
2343
|
+
if (!nav) {
|
|
2344
|
+
if (host["__edrvPendingNav__"] !== void 0) delete host[PENDING_NAV_KEY];
|
|
2345
|
+
return null;
|
|
2346
|
+
}
|
|
2347
|
+
return nav;
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* 清除待跳转槽(落点成功或显式放弃时调用)。
|
|
2351
|
+
* @author ddj 2026年09月23号
|
|
2352
|
+
*/
|
|
2353
|
+
function clearPendingNav() {
|
|
2354
|
+
const host = slotHost();
|
|
2355
|
+
if (!host) return;
|
|
2356
|
+
delete host[PENDING_NAV_KEY];
|
|
2357
|
+
}
|
|
1842
2358
|
/** 作用域键 → 路径 → 磁盘版本令牌。 */
|
|
1843
2359
|
const baselines = /* @__PURE__ */ new Map();
|
|
1844
2360
|
/** 作用域键 → 路径 → 已读取过内容的磁盘版本(同版本不重复读盘;含当时的比对结果)。 */
|
|
@@ -2282,6 +2798,7 @@ window.__ModuleLoader__.load({
|
|
|
2282
2798
|
};
|
|
2283
2799
|
let stage = null;
|
|
2284
2800
|
let pageLabel = null;
|
|
2801
|
+
let zoomLabel = null;
|
|
2285
2802
|
let eventBus = null;
|
|
2286
2803
|
let busAbort = null;
|
|
2287
2804
|
let editorTypes = null;
|
|
@@ -2305,6 +2822,14 @@ window.__ModuleLoader__.load({
|
|
|
2305
2822
|
if (!pageLabel || !pdfViewer) return;
|
|
2306
2823
|
pageLabel.textContent = pdfViewer.currentPageNumber + " / " + pdfViewer.pagesCount;
|
|
2307
2824
|
};
|
|
2825
|
+
/**
|
|
2826
|
+
* 缩放百分比指示更新(scalechanging/pagesinit 事件;与图片预览的 x% 文本同款对齐)。
|
|
2827
|
+
* @author ddj 2026年09月22号
|
|
2828
|
+
*/
|
|
2829
|
+
const updateZoomLabel = () => {
|
|
2830
|
+
if (!zoomLabel || !(pdfViewer && pdfViewer.currentScale > 0)) return;
|
|
2831
|
+
zoomLabel.textContent = Math.round(pdfViewer.currentScale * 100) + "%";
|
|
2832
|
+
};
|
|
2308
2833
|
/** 编辑模式按钮高亮同步(annotationeditormodechanged 事件;evt.mode 为枚举数值)。 */
|
|
2309
2834
|
const syncModeButtons = (mode) => {
|
|
2310
2835
|
if (!modeButtons || !editorTypes) return;
|
|
@@ -2426,6 +2951,10 @@ window.__ModuleLoader__.load({
|
|
|
2426
2951
|
pdfViewer.increaseScale();
|
|
2427
2952
|
} catch {}
|
|
2428
2953
|
}, "edrv-pill edrv-pill-ghost edrv-pdf-zoom");
|
|
2954
|
+
zoomLabel = document.createElement("span");
|
|
2955
|
+
zoomLabel.className = "edrv-pdf-page";
|
|
2956
|
+
zoomLabel.textContent = "…";
|
|
2957
|
+
bar.appendChild(zoomLabel);
|
|
2429
2958
|
bar.appendChild(document.createElement("span")).className = "edrv-pdf-sep";
|
|
2430
2959
|
const modes = {};
|
|
2431
2960
|
modes.freetext = mkBtn("✎ 文本框", "插入文本框注释", () => applyMode("freetext"));
|
|
@@ -2485,11 +3014,13 @@ window.__ModuleLoader__.load({
|
|
|
2485
3014
|
});
|
|
2486
3015
|
linkService.setViewer(pdfViewer);
|
|
2487
3016
|
onBus("pagechanging", updatePageLabel);
|
|
3017
|
+
onBus("scalechanging", updateZoomLabel);
|
|
2488
3018
|
onBus("pagesinit", () => {
|
|
2489
3019
|
try {
|
|
2490
3020
|
pdfViewer.currentScaleValue = "page-width";
|
|
2491
3021
|
} catch {}
|
|
2492
3022
|
updatePageLabel();
|
|
3023
|
+
updateZoomLabel();
|
|
2493
3024
|
});
|
|
2494
3025
|
onBus("annotationeditormodechanged", (evt) => syncModeButtons(evt?.mode));
|
|
2495
3026
|
loadingTask = pdfjs.lib.getDocument({
|
|
@@ -2545,6 +3076,7 @@ window.__ModuleLoader__.load({
|
|
|
2545
3076
|
pdfDocument = null;
|
|
2546
3077
|
loadingTask = null;
|
|
2547
3078
|
modeButtons = null;
|
|
3079
|
+
zoomLabel = null;
|
|
2548
3080
|
};
|
|
2549
3081
|
return {
|
|
2550
3082
|
mount,
|
|
@@ -3320,11 +3852,15 @@ window.__ModuleLoader__.load({
|
|
|
3320
3852
|
byPath.get(r.path).push(r);
|
|
3321
3853
|
}
|
|
3322
3854
|
const files = [];
|
|
3323
|
-
for (const [path, recs] of byPath)
|
|
3324
|
-
path
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3855
|
+
for (const [path, recs] of byPath) {
|
|
3856
|
+
const label = recs.find((r) => !!r.displayPath)?.displayPath ?? path;
|
|
3857
|
+
files.push({
|
|
3858
|
+
path,
|
|
3859
|
+
label,
|
|
3860
|
+
recs,
|
|
3861
|
+
pending: pendingCount(recs)
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3328
3864
|
files.sort((a, b) => a.path < b.path ? -1 : 1);
|
|
3329
3865
|
const pendingFiles = files.filter((f) => f.pending > 0);
|
|
3330
3866
|
return {
|
|
@@ -3594,20 +4130,20 @@ window.__ModuleLoader__.load({
|
|
|
3594
4130
|
return true;
|
|
3595
4131
|
}
|
|
3596
4132
|
/**
|
|
3597
|
-
*
|
|
3598
|
-
*
|
|
3599
|
-
*
|
|
3600
|
-
* @
|
|
3601
|
-
* @param pattern 过滤串(空/空白 =
|
|
3602
|
-
* @returns
|
|
4133
|
+
* 名称过滤匹配器(性能版):模式解析与正则编译只做一次,循环内零构造。
|
|
4134
|
+
* `*` 任意串、`?` 单字符(忽略大小写);无通配符回落子串包含;
|
|
4135
|
+
* 非法正则元字符已转义,永不抛错。与 changeNameMatch 语义一致。
|
|
4136
|
+
* @author ddj 2026年09月23号
|
|
4137
|
+
* @param pattern 过滤串(空/空白 = 全命中)
|
|
4138
|
+
* @returns 单条路径命中判定
|
|
3603
4139
|
*/
|
|
3604
|
-
function
|
|
4140
|
+
function nameMatcherOf(pattern) {
|
|
3605
4141
|
const query = String(pattern ?? "").trim().toLowerCase();
|
|
3606
|
-
if (!query) return true;
|
|
3607
|
-
|
|
3608
|
-
if (!query.includes("*") && !query.includes("?")) return target.includes(query);
|
|
4142
|
+
if (!query) return () => true;
|
|
4143
|
+
if (!query.includes("*") && !query.includes("?")) return (path) => String(path ?? "").toLowerCase().includes(query);
|
|
3609
4144
|
const source = query.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("*", ".*").replaceAll("?", ".");
|
|
3610
|
-
|
|
4145
|
+
const tester = new RegExp("^" + source + "$");
|
|
4146
|
+
return (path) => tester.test(String(path ?? "").toLowerCase());
|
|
3611
4147
|
}
|
|
3612
4148
|
/**
|
|
3613
4149
|
* 按开关过滤变更清单(过滤规则唯一出口,面板/徽标共用,避免两处漂移)。
|
|
@@ -3619,8 +4155,9 @@ window.__ModuleLoader__.load({
|
|
|
3619
4155
|
function svnVisibleChanges(entries, filter = {}) {
|
|
3620
4156
|
const showUnversioned = filter.unversioned !== false;
|
|
3621
4157
|
const showIgnored = filter.ignored === true;
|
|
4158
|
+
const nameHit = nameMatcherOf(filter.name);
|
|
3622
4159
|
return entries.filter((entry) => {
|
|
3623
|
-
if (!
|
|
4160
|
+
if (!nameHit(entry.path)) return false;
|
|
3624
4161
|
if (entry.status === "ignored") return showIgnored;
|
|
3625
4162
|
if (entry.status === "unversioned") return showUnversioned;
|
|
3626
4163
|
return isSvnChange(entry);
|
|
@@ -3704,6 +4241,36 @@ window.__ModuleLoader__.load({
|
|
|
3704
4241
|
blame: "TortoiseSVN 追溯",
|
|
3705
4242
|
revert: "TortoiseSVN 还原"
|
|
3706
4243
|
};
|
|
4244
|
+
/**
|
|
4245
|
+
* 分区名(changelist 名)合法性校验:返回错误文案,合法返回 null。
|
|
4246
|
+
* 规则:去首尾空白后非空、不以 `-` 开头(防被 svn 当成选项解析)、长度 ≤ 上限。
|
|
4247
|
+
* client 预检 + host 权威校验共用,避免两处漂移。
|
|
4248
|
+
* @author ddj 2026年09月23号
|
|
4249
|
+
* @param name 用户输入的分区名
|
|
4250
|
+
* @returns 错误文案;合法为 null
|
|
4251
|
+
*/
|
|
4252
|
+
function svnChangelistNameErrorOf(name) {
|
|
4253
|
+
const trimmed = String(name ?? "").trim();
|
|
4254
|
+
if (!trimmed) return "分区名不能为空";
|
|
4255
|
+
if (trimmed.startsWith("-")) return "分区名不能以 - 开头";
|
|
4256
|
+
if (trimmed.length > 64) return "分区名最长 64 字符";
|
|
4257
|
+
return null;
|
|
4258
|
+
}
|
|
4259
|
+
/**
|
|
4260
|
+
* 路径清单按单次上限分块(host 批量路径上限护栏的客户端对齐)。
|
|
4261
|
+
* @author ddj 2026年09月23号
|
|
4262
|
+
* @param paths 路径列表
|
|
4263
|
+
* @param cap 单块上限(≤0 或非有限值视为不分块,整包一块)
|
|
4264
|
+
* @returns 分块数组(保持原顺序;空入参返回 [])
|
|
4265
|
+
*/
|
|
4266
|
+
function svnChunksOf(paths, cap) {
|
|
4267
|
+
const size = Number.isFinite(cap) && cap > 0 ? Math.floor(cap) : 0;
|
|
4268
|
+
if (!paths.length) return [];
|
|
4269
|
+
if (!size || paths.length <= size) return [[...paths]];
|
|
4270
|
+
const chunks = [];
|
|
4271
|
+
for (let start = 0; start < paths.length; start += size) chunks.push(paths.slice(start, start + size));
|
|
4272
|
+
return chunks;
|
|
4273
|
+
}
|
|
3707
4274
|
/** 取路径 basename(`/`/`\` 通用;纯函数)。 */
|
|
3708
4275
|
function baseNameOf$1(path) {
|
|
3709
4276
|
return String(path ?? "").split(/[\\/]/).pop() ?? "";
|
|
@@ -3747,6 +4314,74 @@ window.__ModuleLoader__.load({
|
|
|
3747
4314
|
"props-deleted": " ",
|
|
3748
4315
|
"props-modified": " "
|
|
3749
4316
|
};
|
|
4317
|
+
/**
|
|
4318
|
+
* 忽略路径清单 → 目录聚合写入项(纯函数):路径拆 dir + basename,同目录合并、同名去重。
|
|
4319
|
+
* 供 `svn.ignore` 的执行载荷构造(一个目录一次 propset,进度粒度按目录)。
|
|
4320
|
+
* @author ddj 2026年09月23号
|
|
4321
|
+
* @param paths 未版本化路径清单(工作区相对)
|
|
4322
|
+
* @returns 目录聚合写入项(保持目录首次出现顺序;空入参返回 [])
|
|
4323
|
+
*/
|
|
4324
|
+
function ignoreItemsOf(paths) {
|
|
4325
|
+
const byDir = /* @__PURE__ */ new Map();
|
|
4326
|
+
for (const raw of paths) {
|
|
4327
|
+
const norm = String(raw ?? "").trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
4328
|
+
if (!norm) continue;
|
|
4329
|
+
const cut = norm.lastIndexOf("/");
|
|
4330
|
+
const dir = cut <= 0 ? "" : norm.slice(0, cut);
|
|
4331
|
+
const name = cut <= 0 ? norm : norm.slice(cut + 1);
|
|
4332
|
+
if (!name) continue;
|
|
4333
|
+
const bucket = byDir.get(dir);
|
|
4334
|
+
if (bucket) {
|
|
4335
|
+
if (!bucket.includes(name)) bucket.push(name);
|
|
4336
|
+
} else byDir.set(dir, [name]);
|
|
4337
|
+
}
|
|
4338
|
+
return [...byDir.entries()].map(([dir, names]) => ({
|
|
4339
|
+
dir,
|
|
4340
|
+
names
|
|
4341
|
+
}));
|
|
4342
|
+
}
|
|
4343
|
+
/**
|
|
4344
|
+
* 会话 agent 深度分析任务文本(混合通道 02-deep-session-prompt 修正版)。
|
|
4345
|
+
* 在**独立新会话**草稿箱填入后由用户发送;agent 自跑只读 `svn status` 取清单
|
|
4346
|
+
* (prompt 定长瘦身,不随变更数膨胀),借 codegraph/读文件/sub agent 深度分析,
|
|
4347
|
+
* 产出方案经免鉴权 RPC `svn.aiPlanSubmit` 投递回面板收件箱——确认与执行仍全部
|
|
4348
|
+
* 走面板(agent 禁写、只读放行)。
|
|
4349
|
+
* @author ddj 2026年09月23号
|
|
4350
|
+
* @param wcRoot 工作副本根(取数指引内嵌)
|
|
4351
|
+
* @param sessionId 会话 id(投递命令内嵌;host 按它解析工作副本)
|
|
4352
|
+
* @returns 任务文本(定长 ~1KB:取数指引/分析要求/投递命令/硬约束)
|
|
4353
|
+
*/
|
|
4354
|
+
function buildAgentPrompt(wcRoot, sessionId) {
|
|
4355
|
+
return [
|
|
4356
|
+
"请对 SVN 工作副本「" + wcRoot + "」的本地变更做**深度分析**,产出整理方案(分组/还原/忽略三段)。",
|
|
4357
|
+
"",
|
|
4358
|
+
"取数(只读命令允许):",
|
|
4359
|
+
"- 先执行 `svn status`(只读)获取完整变更清单;",
|
|
4360
|
+
"- 判断改动性质时用 `svn diff`(只读)查看具体改动。",
|
|
4361
|
+
"",
|
|
4362
|
+
"分析要求(按需使用你的工具):",
|
|
4363
|
+
"- 借助 codegraph 查询调用链/模块归属,判断哪些文件属于同一功能主题(分组依据);",
|
|
4364
|
+
"- 按需读取文件内容,判断改动性质(误改/行尾空白噪音/试验性改动 → 建议还原);",
|
|
4365
|
+
"- 未版本化条目判断是否为生成物/临时文件/日志/缓存(建议忽略);",
|
|
4366
|
+
"- 需要并行探索时可用 sub agent。",
|
|
4367
|
+
"",
|
|
4368
|
+
"方案 JSON 形状(reason 为简短中文一句话):",
|
|
4369
|
+
"{ \"groups\": [{ \"name\": \"英文短横线名\", \"paths\": [\"路径1\"], \"reason\": \"理由\" }],",
|
|
4370
|
+
" \"reverts\": [{ \"path\": \"路径\", \"reason\": \"理由\" }],",
|
|
4371
|
+
" \"ignores\": [{ \"path\": \"路径\", \"reason\": \"理由\" }] }",
|
|
4372
|
+
"",
|
|
4373
|
+
"硬约束:",
|
|
4374
|
+
"1. 禁止任何**写**操作(revert/commit/changelist/propset/update/merge 等一律禁止);只读命令(status/diff/log/cat/info)允许;",
|
|
4375
|
+
"2. 每个路径至多归一类(groups/reverts/ignores 互斥),拿不准的不填;",
|
|
4376
|
+
"3. 组名用简短英文、不以 - 开头;",
|
|
4377
|
+
"4. 只使用 `svn status` 实际返回的路径,绝不虚构或改写路径。",
|
|
4378
|
+
"",
|
|
4379
|
+
"分析完成后,用 shell 执行下面的命令把方案投递回面板(把 <PLAN_JSON> 替换为方案 JSON):",
|
|
4380
|
+
"Invoke-RestMethod -Uri http://127.0.0.1:3080/edrv/rpc -Method Post -ContentType 'application/json' -Body (@{ method = 'svn.aiPlanSubmit'; args = @{ sessionId = '" + sessionId + "'; plan = <PLAN_JSON> } } | ConvertTo-Json -Depth 8)",
|
|
4381
|
+
"",
|
|
4382
|
+
"投递成功会返回 { ok, accepted, dropped };随后我会在面板弹窗中确认执行,你无需再做任何操作。"
|
|
4383
|
+
].join("\n");
|
|
4384
|
+
}
|
|
3750
4385
|
//#endregion
|
|
3751
4386
|
//#region src/shared/svnActions.ts
|
|
3752
4387
|
/**
|
|
@@ -4044,7 +4679,10 @@ window.__ModuleLoader__.load({
|
|
|
4044
4679
|
};
|
|
4045
4680
|
const ensure = (sessionId, key, force = false, extra) => {
|
|
4046
4681
|
if (!key || inFlight.has(key)) return;
|
|
4047
|
-
if (!force && cache.has(key))
|
|
4682
|
+
if (!force && cache.has(key)) {
|
|
4683
|
+
latestValue = cache.get(key);
|
|
4684
|
+
return;
|
|
4685
|
+
}
|
|
4048
4686
|
const fixed = config.args ? config.args(key) : {};
|
|
4049
4687
|
const task = rpc(config.method, {
|
|
4050
4688
|
...fixed,
|
|
@@ -4170,10 +4808,19 @@ window.__ModuleLoader__.load({
|
|
|
4170
4808
|
* 读取指定 scope 的变更清单(未加载返回 null;已加载但无变更返回空数组)。
|
|
4171
4809
|
* @author ddj 2026年09月16号
|
|
4172
4810
|
* @param scope 状态作用域
|
|
4173
|
-
* @returns 变更清单或 null
|
|
4811
|
+
* @returns 变更清单或 null
|
|
4812
|
+
*/
|
|
4813
|
+
function getSvnChanges(scope) {
|
|
4814
|
+
return changesStore.get(scope);
|
|
4815
|
+
}
|
|
4816
|
+
/**
|
|
4817
|
+
* 变更清单是否被条目上限截断(面板提示用)。
|
|
4818
|
+
* @author ddj 2026年09月16号
|
|
4819
|
+
* @param scope 状态作用域
|
|
4820
|
+
* @returns 是否截断
|
|
4174
4821
|
*/
|
|
4175
|
-
function
|
|
4176
|
-
return changesStore.
|
|
4822
|
+
function svnChangesCapped(scope) {
|
|
4823
|
+
return changesStore.meta(scope).truncated === true;
|
|
4177
4824
|
}
|
|
4178
4825
|
/**
|
|
4179
4826
|
* 路径 → 变更条目映射(文件树徽标 O(1) 查表;未加载返回空表)。
|
|
@@ -4363,6 +5010,184 @@ window.__ModuleLoader__.load({
|
|
|
4363
5010
|
}
|
|
4364
5011
|
}
|
|
4365
5012
|
/**
|
|
5013
|
+
* 分区(changelist)批量操作:关联或解除工作副本条目的 changelist 登记。
|
|
5014
|
+
* `svn changelist` 成功时输出静默(host count 为 0),反馈计数以客户端 paths.length 为准;
|
|
5015
|
+
* 上限分块由面板层(svnChunksOf)负责,本包装单次直发。
|
|
5016
|
+
* @author ddj 2026年09月23号
|
|
5017
|
+
* @param sessionId 会话 id
|
|
5018
|
+
* @param paths 工作区相对路径列表
|
|
5019
|
+
* @param name 目标分区名;null = 移出当前分区
|
|
5020
|
+
* @returns 执行结果与反馈文案
|
|
5021
|
+
*/
|
|
5022
|
+
async function svnChangelist(sessionId, paths, name) {
|
|
5023
|
+
const count = paths.length;
|
|
5024
|
+
const remove = name === null;
|
|
5025
|
+
try {
|
|
5026
|
+
const res = await rpc("svn.changelist", remove ? {
|
|
5027
|
+
sessionId,
|
|
5028
|
+
paths,
|
|
5029
|
+
remove: true
|
|
5030
|
+
} : {
|
|
5031
|
+
sessionId,
|
|
5032
|
+
paths,
|
|
5033
|
+
name: name ?? void 0
|
|
5034
|
+
});
|
|
5035
|
+
if (!res.ok) return {
|
|
5036
|
+
ok: false,
|
|
5037
|
+
message: "分区操作失败:" + res.error
|
|
5038
|
+
};
|
|
5039
|
+
return {
|
|
5040
|
+
ok: true,
|
|
5041
|
+
message: remove ? "已移出分区(" + count + " 项)" : "已移入分区「" + name + "」(" + count + " 项)"
|
|
5042
|
+
};
|
|
5043
|
+
} catch (error) {
|
|
5044
|
+
return {
|
|
5045
|
+
ok: false,
|
|
5046
|
+
message: "分区操作失败:" + String(error)
|
|
5047
|
+
};
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
/**
|
|
5051
|
+
* AI 智能整理分析(host `svn.aiPlan`;只读,LLM 一次性调用最长约 120s)。
|
|
5052
|
+
* @author ddj 2026年09月23号
|
|
5053
|
+
* @param sessionId 会话 id
|
|
5054
|
+
* @returns 三段方案与分析元信息
|
|
5055
|
+
*/
|
|
5056
|
+
async function svnAiPlan(sessionId) {
|
|
5057
|
+
try {
|
|
5058
|
+
const res = await rpc("svn.aiPlan", { sessionId });
|
|
5059
|
+
if (!res.ok) return {
|
|
5060
|
+
ok: false,
|
|
5061
|
+
message: "AI 分析失败:" + res.error
|
|
5062
|
+
};
|
|
5063
|
+
return {
|
|
5064
|
+
ok: true,
|
|
5065
|
+
plan: res.plan,
|
|
5066
|
+
entriesCount: res.entriesCount,
|
|
5067
|
+
diffIncluded: res.diffIncluded,
|
|
5068
|
+
dropped: res.dropped,
|
|
5069
|
+
model: res.model,
|
|
5070
|
+
message: "AI 分析完成"
|
|
5071
|
+
};
|
|
5072
|
+
} catch (error) {
|
|
5073
|
+
return {
|
|
5074
|
+
ok: false,
|
|
5075
|
+
message: "AI 分析失败:" + String(error)
|
|
5076
|
+
};
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
/**
|
|
5080
|
+
* AI 整理执行段③:按目录合并写入 svn:ignore 属性(host `svn.ignore`)。
|
|
5081
|
+
* @author ddj 2026年09月23号
|
|
5082
|
+
* @param sessionId 会话 id
|
|
5083
|
+
* @param items 目录聚合写入项
|
|
5084
|
+
* @returns 执行结果与反馈文案
|
|
5085
|
+
*/
|
|
5086
|
+
async function svnIgnore(sessionId, items) {
|
|
5087
|
+
try {
|
|
5088
|
+
const res = await rpc("svn.ignore", {
|
|
5089
|
+
sessionId,
|
|
5090
|
+
items
|
|
5091
|
+
});
|
|
5092
|
+
if (!res.ok) return {
|
|
5093
|
+
ok: false,
|
|
5094
|
+
message: "SVN 忽略失败:" + res.error
|
|
5095
|
+
};
|
|
5096
|
+
return {
|
|
5097
|
+
ok: true,
|
|
5098
|
+
message: res.summary
|
|
5099
|
+
};
|
|
5100
|
+
} catch (error) {
|
|
5101
|
+
return {
|
|
5102
|
+
ok: false,
|
|
5103
|
+
message: "SVN 忽略失败:" + String(error)
|
|
5104
|
+
};
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
/**
|
|
5108
|
+
* 混合通道取件(host `svn.aiPlanPending`):轮询取会话 agent 投递的方案。
|
|
5109
|
+
* @author ddj 2026年09月23号
|
|
5110
|
+
* @param sessionId 会话 id
|
|
5111
|
+
* @param since 注入时刻时间戳(早于它的旧投递不算新件)
|
|
5112
|
+
* @returns 取件结果
|
|
5113
|
+
*/
|
|
5114
|
+
async function svnAiPlanPending(sessionId, since) {
|
|
5115
|
+
try {
|
|
5116
|
+
const res = await rpc("svn.aiPlanPending", {
|
|
5117
|
+
sessionId,
|
|
5118
|
+
since
|
|
5119
|
+
});
|
|
5120
|
+
if (!res.ok) return {
|
|
5121
|
+
ok: false,
|
|
5122
|
+
message: "取件失败:" + res.error
|
|
5123
|
+
};
|
|
5124
|
+
return {
|
|
5125
|
+
ok: true,
|
|
5126
|
+
plan: res.plan,
|
|
5127
|
+
dropped: res.dropped,
|
|
5128
|
+
at: res.at,
|
|
5129
|
+
message: res.plan ? "方案已送达" : "暂无方案"
|
|
5130
|
+
};
|
|
5131
|
+
} catch (error) {
|
|
5132
|
+
return {
|
|
5133
|
+
ok: false,
|
|
5134
|
+
message: "取件失败:" + String(error)
|
|
5135
|
+
};
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
/**
|
|
5139
|
+
* 可暂停执行引擎(AI 整理执行段编排):顺序执行步骤队列。
|
|
5140
|
+
* - 暂停只在**步骤边界**生效(svn 子进程不可安全中断):每步前 `ctl.wait()`;
|
|
5141
|
+
* - 取消后不再取步(已执行不回滚);
|
|
5142
|
+
* - 单步失败(ok=false 或抛错)记日志后**继续**后续步骤。
|
|
5143
|
+
* @author ddj 2026年09月23号
|
|
5144
|
+
* @param steps 步骤队列(执行顺序即数组顺序)
|
|
5145
|
+
* @param ctl 暂停/取消控制面
|
|
5146
|
+
* @param onTick 进度回调(步骤前后各一次)
|
|
5147
|
+
* @returns 逐步日志与取消标记
|
|
5148
|
+
*/
|
|
5149
|
+
async function runPlanSteps(steps, ctl, onTick) {
|
|
5150
|
+
const log = [];
|
|
5151
|
+
const tick = (done) => onTick?.({
|
|
5152
|
+
done,
|
|
5153
|
+
total: steps.length,
|
|
5154
|
+
cur: done < steps.length ? steps[done].label : "",
|
|
5155
|
+
log: [...log]
|
|
5156
|
+
});
|
|
5157
|
+
let cancelled = false;
|
|
5158
|
+
tick(0);
|
|
5159
|
+
for (let index = 0; index < steps.length; index++) {
|
|
5160
|
+
if (ctl.cancelled()) {
|
|
5161
|
+
cancelled = true;
|
|
5162
|
+
break;
|
|
5163
|
+
}
|
|
5164
|
+
await ctl.wait();
|
|
5165
|
+
if (ctl.cancelled()) {
|
|
5166
|
+
cancelled = true;
|
|
5167
|
+
break;
|
|
5168
|
+
}
|
|
5169
|
+
const step = steps[index];
|
|
5170
|
+
let outcome;
|
|
5171
|
+
try {
|
|
5172
|
+
outcome = await step.run();
|
|
5173
|
+
} catch (error) {
|
|
5174
|
+
outcome = {
|
|
5175
|
+
ok: false,
|
|
5176
|
+
message: String(error)
|
|
5177
|
+
};
|
|
5178
|
+
}
|
|
5179
|
+
log.push({
|
|
5180
|
+
ok: outcome.ok,
|
|
5181
|
+
text: step.label + ":" + outcome.message
|
|
5182
|
+
});
|
|
5183
|
+
tick(index + 1);
|
|
5184
|
+
}
|
|
5185
|
+
return {
|
|
5186
|
+
log,
|
|
5187
|
+
cancelled
|
|
5188
|
+
};
|
|
5189
|
+
}
|
|
5190
|
+
/**
|
|
4366
5191
|
* 生成统一格式补丁文本(W2-1,只读不落盘)。
|
|
4367
5192
|
* @author ddj 2026年09月20号
|
|
4368
5193
|
* @param sessionId 会话 id
|
|
@@ -6875,9 +7700,9 @@ window.__ModuleLoader__.load({
|
|
|
6875
7700
|
if (tab === "pending") body = sum.pendingFiles.length === 0 ? react.default.createElement("div", { className: "edrv-search-empty" }, "暂无待处理差异(已全部处理)") : sum.pendingFiles.map((f) => react.default.createElement("div", {
|
|
6876
7701
|
key: f.path,
|
|
6877
7702
|
className: "edrv-launch-row",
|
|
6878
|
-
title: "打开并跳转到差异 " + f.path,
|
|
7703
|
+
title: "打开并跳转到差异 " + (f.label ?? f.path),
|
|
6879
7704
|
onClick: () => onOpenFile(f.path)
|
|
6880
|
-
}, react.default.createElement("span", { className: "edrv-launch-path" }, f.path), react.default.createElement("span", { className: "edrv-launch-cnt" }, "待处理 " + f.pending + " 处"), react.default.createElement("button", {
|
|
7705
|
+
}, react.default.createElement("span", { className: "edrv-launch-path" }, f.label ?? f.path), react.default.createElement("span", { className: "edrv-launch-cnt" }, "待处理 " + f.pending + " 处"), react.default.createElement("button", {
|
|
6881
7706
|
className: "edrv-btn-mini edrv-btn-keep",
|
|
6882
7707
|
onClick: (e) => {
|
|
6883
7708
|
e.stopPropagation();
|
|
@@ -6941,7 +7766,7 @@ window.__ModuleLoader__.load({
|
|
|
6941
7766
|
} }, react.default.createElement("span", { style: {
|
|
6942
7767
|
color: "var(--dsw-alias-label-primary,#ddd)",
|
|
6943
7768
|
fontWeight: 600
|
|
6944
|
-
} }, rec.toolName === "write" ? rec.create ? "新建" : "写入" : "编辑"), react.default.createElement("span", { style: { color: "var(--dsw-alias-state-success-primary,#2e9e44)" } }, "采纳 " + sm.accepted), react.default.createElement("span", { style: { color: "var(--dsw-alias-state-error-primary,#d9534f)" } }, "拒绝 " + sm.rejected), rec.superseded ? react.default.createElement("span", { style: { color: "var(--dsw-alias-state-warn-primary,#b7791f)" } }, "被覆盖") : null, react.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary,#888)" } }, new Date(rec.at).toLocaleTimeString())), react.default.createElement("pre", { style: {
|
|
7769
|
+
} }, rec.toolName === "remote_ssh_write" ? rec.create ? "远端新建" : "远端写入" : rec.toolName === "write" ? rec.create ? "新建" : "写入" : "编辑"), react.default.createElement("span", { style: { color: "var(--dsw-alias-state-success-primary,#2e9e44)" } }, "采纳 " + sm.accepted), react.default.createElement("span", { style: { color: "var(--dsw-alias-state-error-primary,#d9534f)" } }, "拒绝 " + sm.rejected), rec.superseded ? react.default.createElement("span", { style: { color: "var(--dsw-alias-state-warn-primary,#b7791f)" } }, "被覆盖") : null, react.default.createElement("span", { style: { color: "var(--dsw-alias-label-tertiary,#888)" } }, new Date(rec.at).toLocaleTimeString())), react.default.createElement("pre", { style: {
|
|
6945
7770
|
margin: 0,
|
|
6946
7771
|
whiteSpace: "pre",
|
|
6947
7772
|
fontFamily: MONO.fontFamily,
|
|
@@ -6988,7 +7813,7 @@ window.__ModuleLoader__.load({
|
|
|
6988
7813
|
fontSize: 12,
|
|
6989
7814
|
color: "var(--dsw-alias-label-primary,#ddd)",
|
|
6990
7815
|
wordBreak: "break-all"
|
|
6991
|
-
} }, e.path), e.batch != null ? react.default.createElement("span", { style: {
|
|
7816
|
+
} }, e.displayPath ?? e.path), e.batch != null ? react.default.createElement("span", { style: {
|
|
6992
7817
|
fontSize: 11,
|
|
6993
7818
|
color: "var(--dsw-alias-label-tertiary,#888)"
|
|
6994
7819
|
} }, "批次 " + e.batch) : null, e.reason ? react.default.createElement("span", { style: {
|
|
@@ -7998,6 +8823,133 @@ window.__ModuleLoader__.load({
|
|
|
7998
8823
|
}
|
|
7999
8824
|
};
|
|
8000
8825
|
}
|
|
8826
|
+
/** apiproxy wire 端点(POST /api/<method>;浏览器同源 fetch 自带 cookie 鉴权;实证 401=未鉴权而非 404)。 */
|
|
8827
|
+
const SESSION_CREATE_PATH = "/api/session/create";
|
|
8828
|
+
/** apiproxy wire 端点:发送任务(session/prompt;点击即发送方案)。 */
|
|
8829
|
+
const SESSION_PROMPT_PATH = "/api/session/prompt";
|
|
8830
|
+
/** mint RPC 请求 id(RpcId 品牌类型运行时即普通字符串)。 */
|
|
8831
|
+
function mintRpcId() {
|
|
8832
|
+
try {
|
|
8833
|
+
return globalThis.crypto?.randomUUID?.() ?? "rpc-" + Date.now();
|
|
8834
|
+
} catch {
|
|
8835
|
+
return "rpc-" + Date.now();
|
|
8836
|
+
}
|
|
8837
|
+
}
|
|
8838
|
+
/**
|
|
8839
|
+
* RPC 返回解析(照官方 dsh-client-runtime 调用形态:`const { result } = await api.sessions.create(...)`)。
|
|
8840
|
+
* 官方 api.sessions 面返回**信封** `{ rpcId, result: RpcResult }`——须解 .result;
|
|
8841
|
+
* 兼容裸 RpcResult 形态(个别补丁面直接回 {ok, value})。
|
|
8842
|
+
* @author ddj 2026年09月23号
|
|
8843
|
+
* @param res 信封或裸 RpcResult
|
|
8844
|
+
* @returns 归一后的 { ok, value }
|
|
8845
|
+
*/
|
|
8846
|
+
function rpcOk(res) {
|
|
8847
|
+
const r = res?.result ?? res;
|
|
8848
|
+
return {
|
|
8849
|
+
ok: r?.ok === true,
|
|
8850
|
+
value: r?.value
|
|
8851
|
+
};
|
|
8852
|
+
}
|
|
8853
|
+
/**
|
|
8854
|
+
* wire 直调 session/create(主路径):ClientRequest 信封 → ServerResponse.result(RpcResult 形态)。
|
|
8855
|
+
* 契约以当前安装版 dsh-host-apiproxy 为准(api/sessions.d.ts「Method signatures are the
|
|
8856
|
+
* source of truth」):`create({cwd}) → {sessionId}`;信封 = `{type:'client-request', rpcId,
|
|
8857
|
+
* method, payload}` → `{result: {ok:true, value:{sessionId}} | {ok:false, error}}`。
|
|
8858
|
+
* @author ddj 2026年09月23号
|
|
8859
|
+
* @param cwd 新会话工作区目录
|
|
8860
|
+
* @returns 新会话 id(失败空串)
|
|
8861
|
+
*/
|
|
8862
|
+
async function apiSessionCreate(cwd) {
|
|
8863
|
+
const json = await (await fetch(SESSION_CREATE_PATH, {
|
|
8864
|
+
method: "POST",
|
|
8865
|
+
headers: { "content-type": "application/json" },
|
|
8866
|
+
body: JSON.stringify({
|
|
8867
|
+
type: "client-request",
|
|
8868
|
+
rpcId: mintRpcId(),
|
|
8869
|
+
method: "session/create",
|
|
8870
|
+
payload: cwd ? { cwd } : {}
|
|
8871
|
+
})
|
|
8872
|
+
})).json().catch(() => null);
|
|
8873
|
+
const id = json?.result?.value?.sessionId;
|
|
8874
|
+
return json?.result?.ok === true && typeof id === "string" ? id : "";
|
|
8875
|
+
}
|
|
8876
|
+
/**
|
|
8877
|
+
* wire 直发 session/prompt(点击即发送方案):任务文本投给目标会话 agent。
|
|
8878
|
+
* 契约 = 当前安装树 SessionsApi.prompt:`{sessionId, mode:'queue', content:[{type:'text',text}]}`。
|
|
8879
|
+
* 发送成功会话即有内容 → 不再被 blank 隐藏(列表可见)。
|
|
8880
|
+
* @author ddj 2026年09月23号
|
|
8881
|
+
* @param sessionId 目标会话 id(新会话)
|
|
8882
|
+
* @param text 任务文本
|
|
8883
|
+
* @returns 是否受理
|
|
8884
|
+
*/
|
|
8885
|
+
async function apiSessionPrompt(sessionId, text) {
|
|
8886
|
+
try {
|
|
8887
|
+
return (await (await fetch(SESSION_PROMPT_PATH, {
|
|
8888
|
+
method: "POST",
|
|
8889
|
+
headers: { "content-type": "application/json" },
|
|
8890
|
+
body: JSON.stringify({
|
|
8891
|
+
type: "client-request",
|
|
8892
|
+
rpcId: mintRpcId(),
|
|
8893
|
+
method: "session/prompt",
|
|
8894
|
+
payload: {
|
|
8895
|
+
sessionId,
|
|
8896
|
+
mode: "queue",
|
|
8897
|
+
content: [{
|
|
8898
|
+
type: "text",
|
|
8899
|
+
text
|
|
8900
|
+
}]
|
|
8901
|
+
}
|
|
8902
|
+
})
|
|
8903
|
+
})).json().catch(() => null))?.result?.ok === true;
|
|
8904
|
+
} catch {
|
|
8905
|
+
return false;
|
|
8906
|
+
}
|
|
8907
|
+
}
|
|
8908
|
+
/**
|
|
8909
|
+
* 新建独立会话承载旁路任务(02-deep-session-prompt:深度分析不污染当前对话)。
|
|
8910
|
+
* 主路径 = apiproxy wire 直调(不依赖 service 面形态猜测);回落 remote.session.create
|
|
8911
|
+
* 与旧 sessions.create 两代 service 面(宽松解析)。任一步不符返回 ok:false,调用方降级。
|
|
8912
|
+
* @author ddj 2026年09月23号
|
|
8913
|
+
* @param ctx 客户端服务上下文(remote.session + sessions,仅回落用)
|
|
8914
|
+
* @param cwd 新会话工作区目录(与当前会话同工作区)
|
|
8915
|
+
* @returns 新会话结果(含 id)
|
|
8916
|
+
*/
|
|
8917
|
+
async function newDraftSession(ctx, cwd) {
|
|
8918
|
+
try {
|
|
8919
|
+
const id = await apiSessionCreate(cwd);
|
|
8920
|
+
if (id) return {
|
|
8921
|
+
ok: true,
|
|
8922
|
+
id
|
|
8923
|
+
};
|
|
8924
|
+
} catch {}
|
|
8925
|
+
try {
|
|
8926
|
+
const remote = ctx.get("remote.session");
|
|
8927
|
+
if (typeof remote?.create === "function") {
|
|
8928
|
+
const payload = cwd ? { cwd } : {};
|
|
8929
|
+
for (const arg of [payload, {
|
|
8930
|
+
rpcId: mintRpcId(),
|
|
8931
|
+
payload
|
|
8932
|
+
}]) {
|
|
8933
|
+
const r = rpcOk(await remote.create(arg));
|
|
8934
|
+
const id = r.value;
|
|
8935
|
+
if (r.ok && typeof id?.sessionId === "string") return {
|
|
8936
|
+
ok: true,
|
|
8937
|
+
id: id.sessionId
|
|
8938
|
+
};
|
|
8939
|
+
}
|
|
8940
|
+
}
|
|
8941
|
+
const sessions = ctx.get("sessions");
|
|
8942
|
+
if (typeof sessions?.create === "function") {
|
|
8943
|
+
const created = await sessions.create(cwd ? { cwd } : void 0);
|
|
8944
|
+
const id = typeof created === "string" && created ? created : typeof created?.id === "string" ? created.id : typeof created?.sessionId === "string" ? created.sessionId : typeof created?.session?.id === "string" ? created.session.id : "";
|
|
8945
|
+
if (id) return {
|
|
8946
|
+
ok: true,
|
|
8947
|
+
id
|
|
8948
|
+
};
|
|
8949
|
+
}
|
|
8950
|
+
} catch {}
|
|
8951
|
+
return { ok: false };
|
|
8952
|
+
}
|
|
8001
8953
|
/**
|
|
8002
8954
|
* 解析会话输入门面;缺失时返回 undefined(动作侧守卫降级)。
|
|
8003
8955
|
* @author ddj 2026年08月25号
|
|
@@ -8104,7 +9056,79 @@ window.__ModuleLoader__.load({
|
|
|
8104
9056
|
if (safeInsertText(input, (pad.lead ? " " : "") + mention + (pad.tail ? " " : ""), span)) return "busy";
|
|
8105
9057
|
return "failed";
|
|
8106
9058
|
};
|
|
8107
|
-
|
|
9059
|
+
/**
|
|
9060
|
+
* 追加纯文本(AI 深度分析任务注入):走官方 plain-text 通道插到当前光标。
|
|
9061
|
+
* 失败语义与 appendReference 一致:宁可不写也不整篇重建(保护既有 chip)。
|
|
9062
|
+
* @author ddj 2026年09月23号
|
|
9063
|
+
* @param sessionId 会话 id
|
|
9064
|
+
* @param text 待注入文本
|
|
9065
|
+
* @returns 注入结果(ok / unavailable / failed)
|
|
9066
|
+
*/
|
|
9067
|
+
const appendText = async (sessionId, text) => {
|
|
9068
|
+
const input = inputFor(ctx, sessionId);
|
|
9069
|
+
if (!input) return "unavailable";
|
|
9070
|
+
const cur = draftCursor(input);
|
|
9071
|
+
if (!cur) return "unavailable";
|
|
9072
|
+
const at = insertOffsetOf(caretOf(input), detectEndOf(cur));
|
|
9073
|
+
return safeInsertText(input, text, {
|
|
9074
|
+
start: at,
|
|
9075
|
+
end: at,
|
|
9076
|
+
draftRev: cur.draftRev
|
|
9077
|
+
}) ? "ok" : "failed";
|
|
9078
|
+
};
|
|
9079
|
+
/**
|
|
9080
|
+
* 新建独立会话(02-deep-session-prompt:旁路任务不污染当前对话)。
|
|
9081
|
+
* 闭包持有真服务 ctx(面板 SidebarCtx 无 .get,会话操作必须经此门面);
|
|
9082
|
+
* 创建后尽力 `sessions.open(id)` 切换视图(缺失不致命,草稿仍可写入)。
|
|
9083
|
+
* @author ddj 2026年09月23号
|
|
9084
|
+
* @param cwd 新会话工作区目录(与当前会话同工作区)
|
|
9085
|
+
* @returns 新会话结果(失败供降级)
|
|
9086
|
+
*/
|
|
9087
|
+
const startDraftSession = async (cwd) => {
|
|
9088
|
+
const result = await newDraftSession(ctx, cwd);
|
|
9089
|
+
if (result.ok && result.id) try {
|
|
9090
|
+
ctx.get("sessions")?.open?.(result.id);
|
|
9091
|
+
} catch {}
|
|
9092
|
+
return result;
|
|
9093
|
+
};
|
|
9094
|
+
/**
|
|
9095
|
+
* 发送任务给目标会话 agent(点击即发送方案:任务落地会话即非 blank,列表可见)。
|
|
9096
|
+
* 主路径 wire 直发 session/prompt;回落 remote.session.prompt service 面。
|
|
9097
|
+
* @author ddj 2026年09月23号
|
|
9098
|
+
* @param sessionId 目标会话 id
|
|
9099
|
+
* @param text 任务文本
|
|
9100
|
+
* @returns 是否受理
|
|
9101
|
+
*/
|
|
9102
|
+
const sendTask = async (sessionId, text) => {
|
|
9103
|
+
if (!sessionId) return false;
|
|
9104
|
+
try {
|
|
9105
|
+
if (await apiSessionPrompt(sessionId, text)) return true;
|
|
9106
|
+
} catch {}
|
|
9107
|
+
try {
|
|
9108
|
+
const remote = ctx.get("remote.session");
|
|
9109
|
+
if (typeof remote?.prompt === "function") {
|
|
9110
|
+
const payload = {
|
|
9111
|
+
sessionId,
|
|
9112
|
+
mode: "queue",
|
|
9113
|
+
content: [{
|
|
9114
|
+
type: "text",
|
|
9115
|
+
text
|
|
9116
|
+
}]
|
|
9117
|
+
};
|
|
9118
|
+
for (const arg of [payload, {
|
|
9119
|
+
rpcId: mintRpcId(),
|
|
9120
|
+
payload
|
|
9121
|
+
}]) if (rpcOk(await remote.prompt(arg)).ok) return true;
|
|
9122
|
+
}
|
|
9123
|
+
} catch {}
|
|
9124
|
+
return false;
|
|
9125
|
+
};
|
|
9126
|
+
return {
|
|
9127
|
+
appendReference,
|
|
9128
|
+
appendText,
|
|
9129
|
+
startDraftSession,
|
|
9130
|
+
sendTask
|
|
9131
|
+
};
|
|
8108
9132
|
}
|
|
8109
9133
|
/**
|
|
8110
9134
|
* 由选区文本推导搜索种子。
|
|
@@ -8266,6 +9290,43 @@ window.__ModuleLoader__.load({
|
|
|
8266
9290
|
return out;
|
|
8267
9291
|
}
|
|
8268
9292
|
/**
|
|
9293
|
+
* LSP CompletionItemKind(协议 1-based)→ Monaco CompletionItemKind 枚举名(0-based)。
|
|
9294
|
+
*
|
|
9295
|
+
* 为什么按**名称**而不是编号直传:两侧枚举编号并不一致 —— LSP 2=Method,而 Monaco
|
|
9296
|
+
* 0=Method。若把协议编号当 Monaco 编号塞回去,草稿纸上的「只差一位」会变成全表错位
|
|
9297
|
+
* (属性显示成方法图标等)。用名称映射让两侧各自的编号在各自类型系统里解析,杜绝硬编码。
|
|
9298
|
+
* 索引 i 对应 LSP kind i(索引 0 占位,协议从 1 起)。
|
|
9299
|
+
* @author ddj 2026年09月22号
|
|
9300
|
+
*/
|
|
9301
|
+
const LSP_COMPLETION_KIND_NAMES = [
|
|
9302
|
+
"",
|
|
9303
|
+
"Text",
|
|
9304
|
+
"Method",
|
|
9305
|
+
"Function",
|
|
9306
|
+
"Constructor",
|
|
9307
|
+
"Field",
|
|
9308
|
+
"Variable",
|
|
9309
|
+
"Class",
|
|
9310
|
+
"Interface",
|
|
9311
|
+
"Module",
|
|
9312
|
+
"Property",
|
|
9313
|
+
"Unit",
|
|
9314
|
+
"Value",
|
|
9315
|
+
"Enum",
|
|
9316
|
+
"Keyword",
|
|
9317
|
+
"Snippet",
|
|
9318
|
+
"Color",
|
|
9319
|
+
"File",
|
|
9320
|
+
"Reference",
|
|
9321
|
+
"Folder",
|
|
9322
|
+
"EnumMember",
|
|
9323
|
+
"Constant",
|
|
9324
|
+
"Struct",
|
|
9325
|
+
"Event",
|
|
9326
|
+
"Operator",
|
|
9327
|
+
"TypeParameter"
|
|
9328
|
+
];
|
|
9329
|
+
/**
|
|
8269
9330
|
* Monaco 位置(1-based lineNumber/column)→ LSP Position(0-based)。
|
|
8270
9331
|
* @author ddj 2026年08月27号
|
|
8271
9332
|
* @param lineNumber Monaco 行(≥1)
|
|
@@ -8458,6 +9519,58 @@ window.__ModuleLoader__.load({
|
|
|
8458
9519
|
if (!res) return null;
|
|
8459
9520
|
return res.hover || null;
|
|
8460
9521
|
}
|
|
9522
|
+
/**
|
|
9523
|
+
* 查询补全列表(列表阶段不带 documentation,由 resolve 补全)。
|
|
9524
|
+
* @author ddj 2026年09月22号
|
|
9525
|
+
* @param path 工作区相对路径
|
|
9526
|
+
* @param text 文档全文(未保存内容必须进服务器,否则按旧文本解析)
|
|
9527
|
+
* @param position Monaco 位置(1-based)
|
|
9528
|
+
* @param context LSP CompletionContext(触发类型/触发字符)
|
|
9529
|
+
* @returns { items, incomplete };失败或空时 null
|
|
9530
|
+
*/
|
|
9531
|
+
async function fetchCompletions(path, text, position, context) {
|
|
9532
|
+
await syncDoc(path, text, true);
|
|
9533
|
+
const args = {
|
|
9534
|
+
path,
|
|
9535
|
+
position: monoToLsp(position.lineNumber, position.column)
|
|
9536
|
+
};
|
|
9537
|
+
if (context) args.context = context;
|
|
9538
|
+
const res = await queryLsp("查询补全", "edrv.lsp.completion", args);
|
|
9539
|
+
if (!res || !res.completions) return null;
|
|
9540
|
+
return res.completions;
|
|
9541
|
+
}
|
|
9542
|
+
/**
|
|
9543
|
+
* 惰性补全单个候选项(completionItem/resolve)。
|
|
9544
|
+
* @author ddj 2026年09月22号
|
|
9545
|
+
* @param path 工作区相对路径
|
|
9546
|
+
* @param item host 归一化的补全项(含 data)
|
|
9547
|
+
* @returns 补全后的条目;失败时回落原条目
|
|
9548
|
+
*/
|
|
9549
|
+
async function resolveCompletion(path, item) {
|
|
9550
|
+
const res = await queryLsp("解析补全", "edrv.lsp.resolveCompletion", {
|
|
9551
|
+
path,
|
|
9552
|
+
item
|
|
9553
|
+
});
|
|
9554
|
+
if (!res || !res.item) return item;
|
|
9555
|
+
return res.item;
|
|
9556
|
+
}
|
|
9557
|
+
/**
|
|
9558
|
+
* 查询签名帮助。
|
|
9559
|
+
* @author ddj 2026年09月22号
|
|
9560
|
+
* @param path 工作区相对路径
|
|
9561
|
+
* @param text 文档全文
|
|
9562
|
+
* @param position Monaco 位置(1-based)
|
|
9563
|
+
* @returns 归一化签名帮助;无签名时 null
|
|
9564
|
+
*/
|
|
9565
|
+
async function fetchSignatureHelp(path, text, position) {
|
|
9566
|
+
await syncDoc(path, text, true);
|
|
9567
|
+
const res = await queryLsp("查询签名", "edrv.lsp.signatureHelp", {
|
|
9568
|
+
path,
|
|
9569
|
+
position: monoToLsp(position.lineNumber, position.column)
|
|
9570
|
+
});
|
|
9571
|
+
if (!res) return null;
|
|
9572
|
+
return res.signatureHelp || null;
|
|
9573
|
+
}
|
|
8461
9574
|
/** 查询当前文档的 semantic tokens(host 已归一化 legend 与 delta data)。 */
|
|
8462
9575
|
async function fetchSemanticTokens(path, text) {
|
|
8463
9576
|
await syncDoc(path, text, true);
|
|
@@ -8557,6 +9670,33 @@ window.__ModuleLoader__.load({
|
|
|
8557
9670
|
* 作者 ddj 2026-08-27 / 2026-09-11
|
|
8558
9671
|
*/
|
|
8559
9672
|
const LSP_LANGS = ["lua", "csharp"];
|
|
9673
|
+
/**
|
|
9674
|
+
* 补全触发字符(按语言)。
|
|
9675
|
+
*
|
|
9676
|
+
* 取 EmmyLua / DotRush 声明字符的**交集**且去掉噪声项:`"` `'` `\` `/` `#` 等在多语言里
|
|
9677
|
+
* 过于聒噪(每次输入都弹列表打扰手写),故只保留「成员访问 + 调用 + 索引」三类主场景。
|
|
9678
|
+
* @author ddj 2026年09月22号
|
|
9679
|
+
*/
|
|
9680
|
+
const COMPLETION_TRIGGERS = {
|
|
9681
|
+
lua: [
|
|
9682
|
+
".",
|
|
9683
|
+
":",
|
|
9684
|
+
"(",
|
|
9685
|
+
"["
|
|
9686
|
+
],
|
|
9687
|
+
csharp: [
|
|
9688
|
+
".",
|
|
9689
|
+
":",
|
|
9690
|
+
"(",
|
|
9691
|
+
"[",
|
|
9692
|
+
"<",
|
|
9693
|
+
"@"
|
|
9694
|
+
]
|
|
9695
|
+
};
|
|
9696
|
+
/** 签名帮助触发字符(与 LSP 服务器声明的 triggerCharacters 对齐)。 */
|
|
9697
|
+
const SIGNATURE_TRIGGERS = ["(", ","];
|
|
9698
|
+
/** 签名参数/签名文档上浮窗渲染的兜底最大长度(防超长 doc 撑爆浮窗)。 */
|
|
9699
|
+
const MAX_DOC_LENGTH = 4e3;
|
|
8560
9700
|
const SEMANTIC_LEGEND = {
|
|
8561
9701
|
tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
8562
9702
|
tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS]
|
|
@@ -8638,6 +9778,13 @@ window.__ModuleLoader__.load({
|
|
|
8638
9778
|
};
|
|
8639
9779
|
window.addEventListener("edrv:lsp-redetect", onRedetect);
|
|
8640
9780
|
disposables.push(() => window.removeEventListener("edrv:lsp-redetect", onRedetect));
|
|
9781
|
+
const withCapability = (register) => {
|
|
9782
|
+
try {
|
|
9783
|
+
const disposer = register();
|
|
9784
|
+
if (Array.isArray(disposer)) disposables.push(...disposer.filter(Boolean));
|
|
9785
|
+
else if (disposer) disposables.push(disposer);
|
|
9786
|
+
} catch (error) {}
|
|
9787
|
+
};
|
|
8641
9788
|
disposables.push(monaco.editor.registerEditorOpener({ openCodeEditor: (source, resource, selectionOrPosition) => {
|
|
8642
9789
|
const path = lspUriToAbs(resource);
|
|
8643
9790
|
if (!path) return false;
|
|
@@ -8676,6 +9823,26 @@ window.__ModuleLoader__.load({
|
|
|
8676
9823
|
},
|
|
8677
9824
|
releaseDocumentSemanticTokens: () => {}
|
|
8678
9825
|
}));
|
|
9826
|
+
withCapability(() => LSP_LANGS.map((lang) => completionsFor(monaco, lang)));
|
|
9827
|
+
withCapability(() => monaco.languages.registerSignatureHelpProvider(LSP_LANGS, {
|
|
9828
|
+
signatureHelpTriggerCharacters: SIGNATURE_TRIGGERS,
|
|
9829
|
+
signatureHelpRetriggerCharacters: SIGNATURE_TRIGGERS,
|
|
9830
|
+
provideSignatureHelp: async (model, position, token, context) => {
|
|
9831
|
+
const path = pathOfModel(model);
|
|
9832
|
+
if (!path) return null;
|
|
9833
|
+
if (token?.isCancellationRequested) return null;
|
|
9834
|
+
const help = await fetchSignatureHelp(path, model.getValue(), position);
|
|
9835
|
+
if (!help || !help.signatures.length) return null;
|
|
9836
|
+
return {
|
|
9837
|
+
value: {
|
|
9838
|
+
signatures: help.signatures.map(toMonacoSignature),
|
|
9839
|
+
activeSignature: help.activeSignature,
|
|
9840
|
+
activeParameter: help.activeParameter
|
|
9841
|
+
},
|
|
9842
|
+
dispose: () => {}
|
|
9843
|
+
};
|
|
9844
|
+
}
|
|
9845
|
+
}));
|
|
8679
9846
|
if (host) host[LSP_PROVIDERS_GLOBAL] = true;
|
|
8680
9847
|
}
|
|
8681
9848
|
/**
|
|
@@ -8762,12 +9929,168 @@ window.__ModuleLoader__.load({
|
|
|
8762
9929
|
return false;
|
|
8763
9930
|
}
|
|
8764
9931
|
}
|
|
8765
|
-
/** Shift+F12 / 右键「查找所有引用」:走 Monaco 原生 References Peek(两栏引用视图)。 */
|
|
8766
|
-
async function runFindReferences(ed) {
|
|
8767
|
-
const position = ed.getPosition();
|
|
8768
|
-
if (!position) return;
|
|
8769
|
-
ed.setPosition(position);
|
|
8770
|
-
await triggerReferencePeek(ed);
|
|
9932
|
+
/** Shift+F12 / 右键「查找所有引用」:走 Monaco 原生 References Peek(两栏引用视图)。 */
|
|
9933
|
+
async function runFindReferences(ed) {
|
|
9934
|
+
const position = ed.getPosition();
|
|
9935
|
+
if (!position) return;
|
|
9936
|
+
ed.setPosition(position);
|
|
9937
|
+
await triggerReferencePeek(ed);
|
|
9938
|
+
}
|
|
9939
|
+
/**
|
|
9940
|
+
* 构造某语言的补全 provider(触发字符按语言区分)。
|
|
9941
|
+
*
|
|
9942
|
+
* 为什么有 documentPath 传递:Monaco 调 `resolveCompletionItem(item, token)` 时**不传 model**
|
|
9943
|
+
* (实测 vendored 0.42 构建:`provider.resolveCompletionItem(this.completion, token)`),
|
|
9944
|
+
* 因此路径必须在列表阶段随候选项带上,否则 resolve 阶段无处得知该查哪个文档。
|
|
9945
|
+
* @author ddj 2026年09月22号
|
|
9946
|
+
* @param monaco Monaco 实例
|
|
9947
|
+
* @param lang 语言 id
|
|
9948
|
+
* @returns provider 注销器
|
|
9949
|
+
*/
|
|
9950
|
+
function completionsFor(monaco, lang) {
|
|
9951
|
+
return monaco.languages.registerCompletionItemProvider(lang, {
|
|
9952
|
+
triggerCharacters: COMPLETION_TRIGGERS[lang] ?? [".", ":"],
|
|
9953
|
+
provideCompletionItems: async (model, position, context, token) => {
|
|
9954
|
+
const path = pathOfModel(model);
|
|
9955
|
+
if (!path) return { suggestions: [] };
|
|
9956
|
+
const list = await fetchCompletions(path, model.getValue(), position, toLspContext(context));
|
|
9957
|
+
if (!list || !list.items.length || token?.isCancellationRequested) return { suggestions: [] };
|
|
9958
|
+
const word = model.getWordUntilPosition(position);
|
|
9959
|
+
const fallback = {
|
|
9960
|
+
startLineNumber: position.lineNumber,
|
|
9961
|
+
endLineNumber: position.lineNumber,
|
|
9962
|
+
startColumn: word.startColumn,
|
|
9963
|
+
endColumn: word.endColumn
|
|
9964
|
+
};
|
|
9965
|
+
return {
|
|
9966
|
+
suggestions: list.items.map((item) => toMonoSuggestion(monaco, item, fallback, path)),
|
|
9967
|
+
incomplete: list.incomplete === true
|
|
9968
|
+
};
|
|
9969
|
+
},
|
|
9970
|
+
resolveCompletionItem: async (item, token) => {
|
|
9971
|
+
const raw = item?.__edrvRaw;
|
|
9972
|
+
const path = item?.__edrvPath;
|
|
9973
|
+
if (!raw || !path || token?.isCancellationRequested) return item;
|
|
9974
|
+
const resolved = await resolveCompletion(path, raw).catch(() => null);
|
|
9975
|
+
if (!resolved) return item;
|
|
9976
|
+
return mergeResolved(monaco, item, resolved);
|
|
9977
|
+
}
|
|
9978
|
+
});
|
|
9979
|
+
}
|
|
9980
|
+
/**
|
|
9981
|
+
* Monaco 补全上下文 → LSP CompletionContext。
|
|
9982
|
+
*
|
|
9983
|
+
* ⚠️ 两侧 triggerKind **编号不同,必须换算**(实测 vendored 0.42 与 LSP 3.17 规范):
|
|
9984
|
+
* Monaco:0=Invoke, 1=TriggerCharacter, 2=TriggerForIncompleteCompletions
|
|
9985
|
+
* LSP :1=Invoked, 2=TriggerCharacter, 3=TriggerForIncomplete
|
|
9986
|
+
* 恒差 1。直传会把「按字符触发」谎报成 Invoked,服务器据此可能不返回成员补全
|
|
9987
|
+
* (部分服务器严格按 triggerKind 决定是否给 table 成员)。
|
|
9988
|
+
* @author ddj 2026年09月22号
|
|
9989
|
+
* @param context Monaco 上下文(可能缺字段)
|
|
9990
|
+
* @returns LSP 上下文;无触发信息时 undefined(不占载荷)
|
|
9991
|
+
*/
|
|
9992
|
+
function toLspContext(context) {
|
|
9993
|
+
const kind = context?.triggerKind;
|
|
9994
|
+
if (typeof kind !== "number" && !context?.triggerCharacter) return void 0;
|
|
9995
|
+
const out = { triggerKind: typeof kind === "number" ? Math.min(3, Math.max(1, kind + 1)) : 1 };
|
|
9996
|
+
if (typeof context?.triggerCharacter === "string") out.triggerCharacter = context.triggerCharacter;
|
|
9997
|
+
return out;
|
|
9998
|
+
}
|
|
9999
|
+
/**
|
|
10000
|
+
* LSP 补全项 → Monaco suggestion。
|
|
10001
|
+
* @author ddj 2026年09月22号
|
|
10002
|
+
* @param monaco Monaco 实例(取枚举)
|
|
10003
|
+
* @param item host 归一化补全项
|
|
10004
|
+
* @param fallback 无 textEdit 时的词范围
|
|
10005
|
+
* @param path 工作区相对路径(resolve 阶段回传用)
|
|
10006
|
+
* @returns Monaco suggestion
|
|
10007
|
+
*/
|
|
10008
|
+
function toMonoSuggestion(monaco, item, fallback, path) {
|
|
10009
|
+
const kindName = LSP_COMPLETION_KIND_NAMES[item.kind ?? 0];
|
|
10010
|
+
const kinds = monaco.languages.CompletionItemKind ?? {};
|
|
10011
|
+
const asSnippet = monaco.languages.CompletionItemInsertTextRule?.InsertAsSnippet;
|
|
10012
|
+
const suggestion = {
|
|
10013
|
+
label: item.label,
|
|
10014
|
+
kind: kindName && typeof kinds[kindName] === "number" ? kinds[kindName] : kinds.Text ?? 0,
|
|
10015
|
+
insertText: textOfCompletion(item),
|
|
10016
|
+
range: rangeOfCompletion(monaco, item, fallback),
|
|
10017
|
+
__edrvRaw: item,
|
|
10018
|
+
__edrvPath: path
|
|
10019
|
+
};
|
|
10020
|
+
if (item.detail) suggestion.detail = item.detail;
|
|
10021
|
+
if (item.documentation) suggestion.documentation = { value: clip$1(item.documentation) };
|
|
10022
|
+
if (item.filterText) suggestion.filterText = item.filterText;
|
|
10023
|
+
if (item.sortText) suggestion.sortText = item.sortText;
|
|
10024
|
+
if (item.preselect === true) suggestion.preselect = true;
|
|
10025
|
+
if (item.commitCharacters) suggestion.commitCharacters = item.commitCharacters;
|
|
10026
|
+
if (item.deprecated === true) suggestion.tags = [monaco.languages.CompletionItemTag?.Deprecated ?? 1];
|
|
10027
|
+
if (item.additionalTextEdits) suggestion.additionalTextEdits = item.additionalTextEdits.map((edit) => toMonoEdit(monaco, edit));
|
|
10028
|
+
if (item.insertTextFormat === 2 && typeof asSnippet === "number") suggestion.insertTextRules = asSnippet;
|
|
10029
|
+
return suggestion;
|
|
10030
|
+
}
|
|
10031
|
+
/**
|
|
10032
|
+
* 取补全插入文本:`textEdit.newText` 优先于 `insertText`(协议规定 textEdit 生效时以它为准)。
|
|
10033
|
+
* @author ddj 2026年09月22号
|
|
10034
|
+
*/
|
|
10035
|
+
function textOfCompletion(item) {
|
|
10036
|
+
if (item.textEdit?.newText) return item.textEdit.newText;
|
|
10037
|
+
return item.insertText ?? item.label;
|
|
10038
|
+
}
|
|
10039
|
+
/**
|
|
10040
|
+
* 取补全替换范围(Monaco 1-based):单 range 或 insert/replace 双 range 两种形态都映射。
|
|
10041
|
+
* 无 textEdit 时回落词范围 —— 缺范围会让 Monaco 用默认范围替换,可能吃掉已输入前缀。
|
|
10042
|
+
* @author ddj 2026年09月22号
|
|
10043
|
+
*/
|
|
10044
|
+
function rangeOfCompletion(monaco, item, fallback) {
|
|
10045
|
+
const edit = item.textEdit;
|
|
10046
|
+
if (!edit) return fallback;
|
|
10047
|
+
if (edit.range) return toMonoRange(monaco, edit.range);
|
|
10048
|
+
if (edit.insert && edit.replace) return {
|
|
10049
|
+
insert: toMonoRange(monaco, edit.insert),
|
|
10050
|
+
replace: toMonoRange(monaco, edit.replace)
|
|
10051
|
+
};
|
|
10052
|
+
return fallback;
|
|
10053
|
+
}
|
|
10054
|
+
/** LSP Range(0-based)→ Monaco Range(1-based)。 */
|
|
10055
|
+
function toMonoRange(monaco, range) {
|
|
10056
|
+
const s = (p) => ({
|
|
10057
|
+
lineNumber: Math.max(1, (p?.line ?? 0) + 1),
|
|
10058
|
+
column: Math.max(1, (p?.character ?? 0) + 1)
|
|
10059
|
+
});
|
|
10060
|
+
const start = s(range?.start);
|
|
10061
|
+
const end = s(range?.end);
|
|
10062
|
+
return new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column);
|
|
10063
|
+
}
|
|
10064
|
+
/** LSP TextEdit → Monaco IEditOperation(additionalTextEdits 用)。 */
|
|
10065
|
+
function toMonoEdit(monaco, edit) {
|
|
10066
|
+
return {
|
|
10067
|
+
range: toMonoRange(monaco, edit.range),
|
|
10068
|
+
text: edit.newText
|
|
10069
|
+
};
|
|
10070
|
+
}
|
|
10071
|
+
/**
|
|
10072
|
+
* 把 resolve 结果合并进已展示的候选项(保留列表阶段的 range/kind 等 Monaco 侧字段)。
|
|
10073
|
+
* @author ddj 2026年09月22号
|
|
10074
|
+
* @param monaco Monaco 实例
|
|
10075
|
+
* @param suggestion 原候选项
|
|
10076
|
+
* @param resolved resolve 后的归一化条目
|
|
10077
|
+
* @returns 合并后的候选项
|
|
10078
|
+
*/
|
|
10079
|
+
function mergeResolved(monaco, suggestion, resolved) {
|
|
10080
|
+
const merged = {
|
|
10081
|
+
...suggestion,
|
|
10082
|
+
__edrvRaw: resolved
|
|
10083
|
+
};
|
|
10084
|
+
if (resolved.detail) merged.detail = resolved.detail;
|
|
10085
|
+
if (resolved.documentation) merged.documentation = { value: clip$1(resolved.documentation) };
|
|
10086
|
+
if (resolved.additionalTextEdits) merged.additionalTextEdits = resolved.additionalTextEdits.map((edit) => toMonoEdit(monaco, edit));
|
|
10087
|
+
return merged;
|
|
10088
|
+
}
|
|
10089
|
+
/** 文档裁剪:超长 doc(少数服务器返回整段源码)会撑爆浮窗,截断并标注。 */
|
|
10090
|
+
function clip$1(text) {
|
|
10091
|
+
const value = String(text ?? "");
|
|
10092
|
+
if (value.length <= MAX_DOC_LENGTH) return value;
|
|
10093
|
+
return value.slice(0, MAX_DOC_LENGTH) + "\n\n…(内容过长已截断)";
|
|
8771
10094
|
}
|
|
8772
10095
|
/** 编辑器状态栏提示(复用 EditorView 的 status 通道)。 */
|
|
8773
10096
|
function setStatus(text) {
|
|
@@ -12427,13 +13750,70 @@ window.__ModuleLoader__.load({
|
|
|
12427
13750
|
/** 外部侧栏插件名(dsh-web-ui-all 家族的文件打开能力提供方)。 */
|
|
12428
13751
|
const SIDEBAR_PLUGIN = "dsh-better-sidebar";
|
|
12429
13752
|
/**
|
|
12430
|
-
*
|
|
13753
|
+
* 形状守卫:判断 configForms.get 返回值是否具备适配所需的最小面。
|
|
13754
|
+
* @author ddj 2026年09月22号
|
|
13755
|
+
* @param form configForms.get(entryId) 的返回值
|
|
13756
|
+
* @returns 是否为可用 ConfigForm
|
|
13757
|
+
*/
|
|
13758
|
+
function isConfigForm(form) {
|
|
13759
|
+
if (!form || typeof form !== "object") return false;
|
|
13760
|
+
const candidate = form;
|
|
13761
|
+
return typeof candidate.getSnapshot === "function" && typeof candidate.subscribe === "function" && typeof candidate.set === "function";
|
|
13762
|
+
}
|
|
13763
|
+
/**
|
|
13764
|
+
* 将 DSH 0.1.7 ConfigForm 适配为既有 SettingsScopeLike(调用方零改动)。
|
|
13765
|
+
* status/value/writable 直接透传('ready'|'loading'|'unavailable' 与既有 status 词汇一致);
|
|
13766
|
+
* set 丢弃返回的 boolean(避免给未 catch 的既有调用方新增拒绝;form.set 自身 reject 仍传导)。
|
|
13767
|
+
* @author ddj 2026年09月22号
|
|
13768
|
+
* @param form configForms.get(PLUGIN_NAME) 返回的表单
|
|
13769
|
+
* @returns 适配后的设置 scope
|
|
13770
|
+
*/
|
|
13771
|
+
function adaptConfigForm(form) {
|
|
13772
|
+
return {
|
|
13773
|
+
getSnapshot: () => {
|
|
13774
|
+
const snap = form.getSnapshot();
|
|
13775
|
+
return {
|
|
13776
|
+
status: snap.status,
|
|
13777
|
+
value: snap.value,
|
|
13778
|
+
writable: snap.writable
|
|
13779
|
+
};
|
|
13780
|
+
},
|
|
13781
|
+
subscribe: (listener) => form.subscribe(listener),
|
|
13782
|
+
set: async (field, value) => {
|
|
13783
|
+
await form.set(field, value);
|
|
13784
|
+
}
|
|
13785
|
+
};
|
|
13786
|
+
}
|
|
13787
|
+
/**
|
|
13788
|
+
* 探测 DSH 0.1.7+ configForms 官方新设置桥(entryId = PLUGIN_NAME)。
|
|
13789
|
+
* 0.1.6 无该服务、ctx.get/表单 get 抛错或形状不符时返回 undefined,交由原两级桥降级。
|
|
13790
|
+
* @author ddj 2026年09月22号
|
|
13791
|
+
* @param ctx 客户端服务上下文
|
|
13792
|
+
* @returns 适配后的设置 scope 或 undefined
|
|
13793
|
+
*/
|
|
13794
|
+
function bindConfigForm(ctx) {
|
|
13795
|
+
try {
|
|
13796
|
+
const form = ctx.get("configForms")?.get?.(PLUGIN_NAME);
|
|
13797
|
+
if (!isConfigForm(form)) return void 0;
|
|
13798
|
+
return adaptConfigForm(form);
|
|
13799
|
+
} catch {
|
|
13800
|
+
return;
|
|
13801
|
+
}
|
|
13802
|
+
}
|
|
13803
|
+
/**
|
|
13804
|
+
* 设置桥优先序:configForms(DSH 0.1.7+ 官方新桥)→ webUiSettings(linxin666 兼容桥,
|
|
13805
|
+
* 旧版现网优先级不变)→ settingsScope(DSH 0.1.6 官方旧桥)→ 无。
|
|
12431
13806
|
* 桥绑定抛错时降级到下一级,不让单个桥异常拖垮设置装配。
|
|
12432
|
-
* @author ddj 2026年08月24号
|
|
13807
|
+
* @author ddj 2026年08月24号(2026年09月22号 增补 configForms 桥)
|
|
12433
13808
|
* @param ctx 客户端服务上下文
|
|
12434
13809
|
* @returns 激活的桥名称与绑定 scope
|
|
12435
13810
|
*/
|
|
12436
13811
|
function pickSettingsBinder(ctx) {
|
|
13812
|
+
const configForm = bindConfigForm(ctx);
|
|
13813
|
+
if (configForm) return {
|
|
13814
|
+
service: "configForms",
|
|
13815
|
+
scope: configForm
|
|
13816
|
+
};
|
|
12437
13817
|
for (const service of ["webUiSettings", "settingsScope"]) try {
|
|
12438
13818
|
const scope = ctx.get(service)?.bind?.({ namespace: PLUGIN_NAME });
|
|
12439
13819
|
if (scope) return {
|
|
@@ -12447,6 +13827,55 @@ window.__ModuleLoader__.load({
|
|
|
12447
13827
|
};
|
|
12448
13828
|
}
|
|
12449
13829
|
/**
|
|
13830
|
+
* 设置桥装配(带晚到有界重试):设置桥服务一律不进 inject(0.1.6 settingsScope /
|
|
13831
|
+
* 0.1.7 configForms 互斥,写死任一桥名都会在另一版本停等),因此提供方可能晚于本插件
|
|
13832
|
+
* apply 就绪——构造时探测一次,未命中则按 schedule 节奏有界重试,命中即通知 whenReady
|
|
13833
|
+
* 订阅者(重挂设置同步 effect);重试用尽保持未就绪(与旧行为一致,不无限轮询)。
|
|
13834
|
+
* @author ddj 2026年09月22号
|
|
13835
|
+
* @param ctx 客户端服务上下文
|
|
13836
|
+
* @param options schedule 注入(缺省不重试=旧行为);attempts/intervalMs 重试节奏
|
|
13837
|
+
* @returns 设置桥
|
|
13838
|
+
*/
|
|
13839
|
+
function settingsBridge(ctx, options) {
|
|
13840
|
+
let hit = pickSettingsBinder(ctx);
|
|
13841
|
+
let attemptsLeft = options?.attempts ?? 0;
|
|
13842
|
+
const interval = options?.intervalMs ?? 2e3;
|
|
13843
|
+
const waiters = [];
|
|
13844
|
+
const notify = () => {
|
|
13845
|
+
for (const listener of waiters.splice(0)) try {
|
|
13846
|
+
listener();
|
|
13847
|
+
} catch {}
|
|
13848
|
+
};
|
|
13849
|
+
const retry = () => {
|
|
13850
|
+
if (hit.scope || attemptsLeft <= 0 || !options?.schedule) return;
|
|
13851
|
+
attemptsLeft -= 1;
|
|
13852
|
+
options.schedule(() => {
|
|
13853
|
+
hit = pickSettingsBinder(ctx);
|
|
13854
|
+
if (hit.scope) {
|
|
13855
|
+
notify();
|
|
13856
|
+
return;
|
|
13857
|
+
}
|
|
13858
|
+
retry();
|
|
13859
|
+
}, interval);
|
|
13860
|
+
};
|
|
13861
|
+
retry();
|
|
13862
|
+
return {
|
|
13863
|
+
scope: () => hit.scope,
|
|
13864
|
+
service: () => hit.service,
|
|
13865
|
+
whenReady: (listener) => {
|
|
13866
|
+
if (hit.scope) {
|
|
13867
|
+
listener();
|
|
13868
|
+
return () => {};
|
|
13869
|
+
}
|
|
13870
|
+
waiters.push(listener);
|
|
13871
|
+
return () => {
|
|
13872
|
+
const at = waiters.indexOf(listener);
|
|
13873
|
+
if (at >= 0) waiters.splice(at, 1);
|
|
13874
|
+
};
|
|
13875
|
+
}
|
|
13876
|
+
};
|
|
13877
|
+
}
|
|
13878
|
+
/**
|
|
12450
13879
|
* 跨版本 slot 安全注册:封装 ctx.slots.inject 等待声明模式(新老 DSH 通用),
|
|
12451
13880
|
* 服务缺失或注册抛错时降级为警告,不让单条 slot 拖垮装配。
|
|
12452
13881
|
* 注意:方法必须挂在服务对象上调用(slots.register 是依赖 this.records 的类方法,
|
|
@@ -12548,103 +13977,13 @@ window.__ModuleLoader__.load({
|
|
|
12548
13977
|
const OFFICIAL_FILE_TAB_ID = "dsh-vscode-mode/file";
|
|
12549
13978
|
/** file 资源类型的 kind(与页类型 edrvEditor 区分:同 kind 只允许一份 extension)。 */
|
|
12550
13979
|
const OFFICIAL_FILE_KIND = "edrvEditorFile";
|
|
12551
|
-
/**
|
|
12552
|
-
* Office 文档后缀:官方 `dsh-client-ui-sidebar-documentpreview` 的 Office 渲染器
|
|
12553
|
-
* 声明 `doc/docx/xls/xlsx/ppt/pptx`,并在 alpha.2 起提供侧栏 Office 预览。
|
|
12554
|
-
* 本插件让位官方(不认领),否则会把这些文件路由进 Monaco 而丢掉 Office 预览。
|
|
12555
|
-
*/
|
|
12556
|
-
const OFFICE_EXT = [
|
|
12557
|
-
"doc",
|
|
12558
|
-
"docx",
|
|
12559
|
-
"xls",
|
|
12560
|
-
"xlsx",
|
|
12561
|
-
"ppt",
|
|
12562
|
-
"pptx",
|
|
12563
|
-
"odt",
|
|
12564
|
-
"ods",
|
|
12565
|
-
"odp",
|
|
12566
|
-
"pages",
|
|
12567
|
-
"numbers"
|
|
12568
|
-
];
|
|
12569
|
-
/**
|
|
12570
|
-
* 官方「不可预览二进制容器」清单(`UNVIEWABLE_BINARY_EXTENSIONS`,逐项取自
|
|
12571
|
-
* `dsh-client-ui-sidebar-documentpreview/lib/client.js` 的 `UNVIEWABLE_BINARY_EXTENSIONS`)。
|
|
12572
|
-
* 这些后缀官方会给出「无法预览」提示;本插件让位官方,避免把二进制当文本读成乱码。
|
|
12573
|
-
*
|
|
12574
|
-
* ⚠️ 刻意例外:官方清单含 `avif`,但本插件 {@link IMAGE_MIME} 支持 avif 图片预览
|
|
12575
|
-
* (浏览器原生解码),故从本表**移除** `avif` —— 保留本插件的图片预览优于官方的「不可预览」。
|
|
12576
|
-
* @author ddj 2026年09月18号
|
|
12577
|
-
*/
|
|
12578
|
-
const BLIND_EXT = [
|
|
12579
|
-
"mp4",
|
|
12580
|
-
"mov",
|
|
12581
|
-
"avi",
|
|
12582
|
-
"mkv",
|
|
12583
|
-
"webm",
|
|
12584
|
-
"flv",
|
|
12585
|
-
"wmv",
|
|
12586
|
-
"m4v",
|
|
12587
|
-
"mp3",
|
|
12588
|
-
"wav",
|
|
12589
|
-
"flac",
|
|
12590
|
-
"ogg",
|
|
12591
|
-
"m4a",
|
|
12592
|
-
"aac",
|
|
12593
|
-
"wma",
|
|
12594
|
-
"opus",
|
|
12595
|
-
"zip",
|
|
12596
|
-
"gz",
|
|
12597
|
-
"tgz",
|
|
12598
|
-
"bz2",
|
|
12599
|
-
"xz",
|
|
12600
|
-
"zst",
|
|
12601
|
-
"7z",
|
|
12602
|
-
"rar",
|
|
12603
|
-
"tar",
|
|
12604
|
-
"jar",
|
|
12605
|
-
"exe",
|
|
12606
|
-
"dll",
|
|
12607
|
-
"so",
|
|
12608
|
-
"dylib",
|
|
12609
|
-
"bin",
|
|
12610
|
-
"o",
|
|
12611
|
-
"class",
|
|
12612
|
-
"pyc",
|
|
12613
|
-
"wasm",
|
|
12614
|
-
"ttf",
|
|
12615
|
-
"otf",
|
|
12616
|
-
"woff",
|
|
12617
|
-
"woff2",
|
|
12618
|
-
"eot",
|
|
12619
|
-
"dmg",
|
|
12620
|
-
"iso",
|
|
12621
|
-
"img",
|
|
12622
|
-
"sqlite",
|
|
12623
|
-
"db",
|
|
12624
|
-
"psd",
|
|
12625
|
-
"ai",
|
|
12626
|
-
"sketch",
|
|
12627
|
-
"tiff",
|
|
12628
|
-
"tif",
|
|
12629
|
-
"heic",
|
|
12630
|
-
"heif"
|
|
12631
|
-
];
|
|
12632
13980
|
/** 让位判定用后缀集合(模块级构建一次;小写、无前导点)。 */
|
|
12633
13981
|
const DEFER_EXT_SET = /* @__PURE__ */ new Set([...OFFICE_EXT, ...BLIND_EXT]);
|
|
12634
13982
|
/**
|
|
12635
|
-
* 取 path 的 basename 后缀(小写、无点);无后缀/隐藏文件返回 ''。
|
|
12636
|
-
* @author ddj 2026年09月18号
|
|
12637
|
-
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
12638
|
-
* @returns 小写后缀或 ''
|
|
12639
|
-
*/
|
|
12640
|
-
function suffixOf(path) {
|
|
12641
|
-
const base = String(path ?? "").replace(/\\/g, "/").split("/").pop() ?? "";
|
|
12642
|
-
const dot = base.lastIndexOf(".");
|
|
12643
|
-
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
12644
|
-
}
|
|
12645
|
-
/**
|
|
12646
13983
|
* 该路径是否应让位官方查看器(Office 文档 + 官方不可预览的二进制容器)。
|
|
12647
13984
|
* 命中即不认领 `dsh-resource://file/**`,回落官方预览/提示。
|
|
13985
|
+
* 注:用户经设置 nativeOpenExts 追加的范围不走本函数,由 claim 的 canOpen 与
|
|
13986
|
+
* 文件树 openFile 叠加 nativeOpenStore 判定(见 registerOfficialFileClaim)。
|
|
12648
13987
|
* @author ddj 2026年09月18号
|
|
12649
13988
|
* @param path 文件路径(工作区相对或绝对)
|
|
12650
13989
|
* @returns 是否让位官方
|
|
@@ -12885,7 +14224,7 @@ window.__ModuleLoader__.load({
|
|
|
12885
14224
|
canOpen: (address) => {
|
|
12886
14225
|
const parsed = parseOfficialFileAddress(address);
|
|
12887
14226
|
if (parsed === null) return false;
|
|
12888
|
-
return !deferToOfficial(parsed.path);
|
|
14227
|
+
return !deferToOfficial(parsed.path) && !inNativePath(parsed.path);
|
|
12889
14228
|
},
|
|
12890
14229
|
title: (address) => officialFileTitle(address)
|
|
12891
14230
|
});
|
|
@@ -15989,6 +17328,20 @@ window.__ModuleLoader__.load({
|
|
|
15989
17328
|
}
|
|
15990
17329
|
/** 跳转目标高亮的保留时长(LSP/搜索跳转落地后给用户的位置提示,到期自动清除)。 */
|
|
15991
17330
|
const NAV_FLASH_MS = 1200;
|
|
17331
|
+
/** 二进制通道回退日志的一次性闸(整页仅报一次,避免每次读图/读 PDF 刷屏)。 */
|
|
17332
|
+
let binFallbackLogged = false;
|
|
17333
|
+
/**
|
|
17334
|
+
* 二进制通道回退的一次性诊断日志:readBinaryPreview 本次尝试过新通道并失败时上报。
|
|
17335
|
+
* 有效期:随页面生命周期仅一条;dbg 本身受诊断开关控制(关=零输出零落盘)。
|
|
17336
|
+
* @author ddj 2026年09月22号
|
|
17337
|
+
* @param sid 会话 id
|
|
17338
|
+
* @param out readBinaryPreview 统一结果(带 probed 才说明本次尝试过新通道)
|
|
17339
|
+
*/
|
|
17340
|
+
const noteBinFallback = (sid, out) => {
|
|
17341
|
+
if (binFallbackLogged || !out?.probed) return;
|
|
17342
|
+
binFallbackLogged = true;
|
|
17343
|
+
dbg(sid, "edrv.readBinary 不可用(host 未接线/旧版或网络失败),已回退 base64", "debug");
|
|
17344
|
+
};
|
|
15992
17345
|
/**
|
|
15993
17346
|
* 从 Monaco 语言目录取可选语言 id 列表(代码片段新建时的语言下拉来源)。
|
|
15994
17347
|
* 旧版 Monaco / 目录不可读时返回 undefined,由调用方回落 shared 的内置常量。
|
|
@@ -16052,6 +17405,8 @@ window.__ModuleLoader__.load({
|
|
|
16052
17405
|
const [imageSrc, setImageSrc] = react.default.useState(null);
|
|
16053
17406
|
const [imgSize, setImgSize] = react.default.useState(null);
|
|
16054
17407
|
const [imgBroken, setImgBroken] = react.default.useState(false);
|
|
17408
|
+
const [imgZoom, setImgZoom] = react.default.useState(null);
|
|
17409
|
+
const imgElRef = react.default.useRef(null);
|
|
16055
17410
|
const svgTextRef = react.default.useRef(/* @__PURE__ */ new Set());
|
|
16056
17411
|
const mdPreviewRef = react.default.useRef(/* @__PURE__ */ new Set());
|
|
16057
17412
|
const [pdfBytes, setPdfBytes] = react.default.useState(null);
|
|
@@ -16710,23 +18065,34 @@ window.__ModuleLoader__.load({
|
|
|
16710
18065
|
progress: monaco ? 72 : 12,
|
|
16711
18066
|
message: "读取图片…"
|
|
16712
18067
|
});
|
|
16713
|
-
rpc
|
|
18068
|
+
readBinaryPreview(rpc, {
|
|
16714
18069
|
sessionId: sid,
|
|
16715
|
-
path
|
|
16716
|
-
|
|
16717
|
-
}).then((res) => {
|
|
18070
|
+
path
|
|
18071
|
+
}).then((out) => {
|
|
16718
18072
|
if (seq !== loadSeqRef.current || path !== active) return;
|
|
16719
|
-
if (
|
|
16720
|
-
|
|
18073
|
+
if (out.ok && out.via === "binary") {
|
|
18074
|
+
recordBaseline(scope, path, out.version || version);
|
|
18075
|
+
clearReadVersion(scope, path);
|
|
18076
|
+
setImageSrc(dataUrlOf(bytesToBase64(out.bytes), out.mime));
|
|
18077
|
+
setLoadStage({
|
|
18078
|
+
progress: 100,
|
|
18079
|
+
message: "图片已就绪"
|
|
18080
|
+
});
|
|
18081
|
+
setStatus("已加载");
|
|
18082
|
+
return;
|
|
18083
|
+
}
|
|
18084
|
+
noteBinFallback(sid, out);
|
|
18085
|
+
if (out.ok && out.via === "base64") {
|
|
18086
|
+
if (!out.mime) {
|
|
16721
18087
|
setLoadError("host 版本过旧:图片响应缺少 mime");
|
|
16722
18088
|
setError("host 版本过旧:图片响应缺少 mime");
|
|
16723
18089
|
setStatus("读取失败");
|
|
16724
18090
|
return;
|
|
16725
18091
|
}
|
|
16726
|
-
const imgVersion =
|
|
18092
|
+
const imgVersion = out.version ?? version;
|
|
16727
18093
|
recordBaseline(scope, path, imgVersion);
|
|
16728
18094
|
clearReadVersion(scope, path);
|
|
16729
|
-
setImageSrc(dataUrlOf(
|
|
18095
|
+
setImageSrc(dataUrlOf(out.content, out.mime));
|
|
16730
18096
|
setLoadStage({
|
|
16731
18097
|
progress: 100,
|
|
16732
18098
|
message: "图片已就绪"
|
|
@@ -16734,8 +18100,8 @@ window.__ModuleLoader__.load({
|
|
|
16734
18100
|
setStatus("已加载");
|
|
16735
18101
|
return;
|
|
16736
18102
|
}
|
|
16737
|
-
const base =
|
|
16738
|
-
const message =
|
|
18103
|
+
const base = out.error ? String(out.error) : "读取失败";
|
|
18104
|
+
const message = out.resolvedPath ? base + ":" + String(out.resolvedPath) : base;
|
|
16739
18105
|
setLoadError(message);
|
|
16740
18106
|
setError(message);
|
|
16741
18107
|
setStatus("读取失败");
|
|
@@ -16776,19 +18142,33 @@ window.__ModuleLoader__.load({
|
|
|
16776
18142
|
setStatus("已加载");
|
|
16777
18143
|
return;
|
|
16778
18144
|
}
|
|
16779
|
-
rpc
|
|
18145
|
+
readBinaryPreview(rpc, {
|
|
16780
18146
|
sessionId: sid,
|
|
16781
|
-
path
|
|
16782
|
-
|
|
16783
|
-
}).then((res) => {
|
|
18147
|
+
path
|
|
18148
|
+
}).then((out) => {
|
|
16784
18149
|
if (seq !== loadSeqRef.current || path !== active) return;
|
|
16785
|
-
if (
|
|
16786
|
-
const pdfVersion =
|
|
18150
|
+
if (out.ok && out.via === "binary") {
|
|
18151
|
+
const pdfVersion = out.version || version;
|
|
18152
|
+
recordBaseline(scope, path, pdfVersion);
|
|
18153
|
+
clearReadVersion(scope, path);
|
|
18154
|
+
cache.set(path, bytesToBase64(out.bytes));
|
|
18155
|
+
while (cache.size > 6) cache.delete(cache.keys().next().value);
|
|
18156
|
+
setPdfBytes(out.bytes);
|
|
18157
|
+
setLoadStage({
|
|
18158
|
+
progress: 100,
|
|
18159
|
+
message: "PDF 已就绪"
|
|
18160
|
+
});
|
|
18161
|
+
setStatus("已加载");
|
|
18162
|
+
return;
|
|
18163
|
+
}
|
|
18164
|
+
noteBinFallback(sid, out);
|
|
18165
|
+
if (out.ok && out.via === "base64") {
|
|
18166
|
+
const pdfVersion = out.version ?? version;
|
|
16787
18167
|
recordBaseline(scope, path, pdfVersion);
|
|
16788
18168
|
clearReadVersion(scope, path);
|
|
16789
|
-
cache.set(path,
|
|
18169
|
+
cache.set(path, out.content);
|
|
16790
18170
|
while (cache.size > 6) cache.delete(cache.keys().next().value);
|
|
16791
|
-
setPdfBytes(base64ToBytes(
|
|
18171
|
+
setPdfBytes(base64ToBytes(out.content));
|
|
16792
18172
|
setLoadStage({
|
|
16793
18173
|
progress: 100,
|
|
16794
18174
|
message: "PDF 已就绪"
|
|
@@ -16796,8 +18176,8 @@ window.__ModuleLoader__.load({
|
|
|
16796
18176
|
setStatus("已加载");
|
|
16797
18177
|
return;
|
|
16798
18178
|
}
|
|
16799
|
-
const base =
|
|
16800
|
-
const message =
|
|
18179
|
+
const base = out.error ? String(out.error) : "读取失败";
|
|
18180
|
+
const message = out.resolvedPath ? base + ":" + String(out.resolvedPath) : base;
|
|
16801
18181
|
setLoadError(message);
|
|
16802
18182
|
setError(message);
|
|
16803
18183
|
setStatus("读取失败");
|
|
@@ -17258,6 +18638,7 @@ window.__ModuleLoader__.load({
|
|
|
17258
18638
|
if (!event?.detail?.scope || event.detail.scope === scope) setSvnStatus(getSvnStatus(scope));
|
|
17259
18639
|
};
|
|
17260
18640
|
window.addEventListener("edrv:svn-status", onSvnStatus);
|
|
18641
|
+
setSvnStatus(getSvnStatus(scope));
|
|
17261
18642
|
return () => window.removeEventListener("edrv:svn-status", onSvnStatus);
|
|
17262
18643
|
}, [sessionId, scope]);
|
|
17263
18644
|
react.default.useEffect(() => {
|
|
@@ -17846,6 +19227,7 @@ window.__ModuleLoader__.load({
|
|
|
17846
19227
|
}
|
|
17847
19228
|
for (const ctl of pdfCtlRef.current.values()) ctl.destroy();
|
|
17848
19229
|
pdfCtlRef.current.clear();
|
|
19230
|
+
clearZoomMem();
|
|
17849
19231
|
const root = editorRef.current && editorRef.current.getDomNode ? editorRef.current.getDomNode() : null;
|
|
17850
19232
|
if (root) {
|
|
17851
19233
|
const ov = root.querySelector(".edrv-minus-overlay");
|
|
@@ -18175,10 +19557,24 @@ window.__ModuleLoader__.load({
|
|
|
18175
19557
|
ed.focus();
|
|
18176
19558
|
return;
|
|
18177
19559
|
}
|
|
18178
|
-
const
|
|
19560
|
+
const refPf = pendingFocusRef.current;
|
|
19561
|
+
const slotNav = refPf ? null : readPendingNav();
|
|
19562
|
+
const pf = refPf || (slotNav ? {
|
|
19563
|
+
path: slotNav.path,
|
|
19564
|
+
region: null,
|
|
19565
|
+
line: slotNav.line ?? null,
|
|
19566
|
+
column: slotNav.column ?? 1,
|
|
19567
|
+
endLine: slotNav.endLine ?? null,
|
|
19568
|
+
endColumn: slotNav.endColumn ?? null
|
|
19569
|
+
} : null);
|
|
18179
19570
|
if (!pf || !sameFile(pf.path, active)) return;
|
|
18180
|
-
|
|
19571
|
+
const dropPending = () => {
|
|
18181
19572
|
pendingFocusRef.current = null;
|
|
19573
|
+
if (pf.line != null) clearPendingNav();
|
|
19574
|
+
};
|
|
19575
|
+
if (pf.line != null) {
|
|
19576
|
+
if (!sameFile(modelPathOf(ed), pf.path)) return;
|
|
19577
|
+
dropPending();
|
|
18182
19578
|
const line = Math.max(1, pf.line);
|
|
18183
19579
|
const column = Math.max(1, pf.column ?? 1);
|
|
18184
19580
|
ed.revealLineInCenter(line);
|
|
@@ -18197,7 +19593,8 @@ window.__ModuleLoader__.load({
|
|
|
18197
19593
|
}
|
|
18198
19594
|
const target = pf.region || pendingRegions[0];
|
|
18199
19595
|
if (!target) return;
|
|
18200
|
-
|
|
19596
|
+
if (!sameFile(modelPathOf(ed), pf.path)) return;
|
|
19597
|
+
dropPending();
|
|
18201
19598
|
ed.revealLineInCenter(Math.max(1, target.start ?? 1));
|
|
18202
19599
|
ed.setPosition({
|
|
18203
19600
|
lineNumber: Math.max(1, target.start ?? 1),
|
|
@@ -18372,11 +19769,50 @@ window.__ModuleLoader__.load({
|
|
|
18372
19769
|
setStatus(entering ? "已切换为 Markdown 预览" : "已切换为源码编辑");
|
|
18373
19770
|
};
|
|
18374
19771
|
toggleMdPreviewRef.current = toggleMdPreview;
|
|
19772
|
+
/** 当前图片的会话+路径级缩放记忆键(页签切换回来保留同一缩放)。 */
|
|
19773
|
+
const imgZoomKey = zoomKeyOf(sessionId, active);
|
|
19774
|
+
react.default.useEffect(() => {
|
|
19775
|
+
setImgZoom(recallZoom(imgZoomKey));
|
|
19776
|
+
}, [imgZoomKey]);
|
|
18375
19777
|
/**
|
|
18376
|
-
*
|
|
18377
|
-
* @author ddj 2026年09月
|
|
19778
|
+
* 应用图片缩放并写入页签记忆(null = 适应宽度;数值经 clampZoom 收敛 25%–400%)。
|
|
19779
|
+
* @author ddj 2026年09月22号
|
|
19780
|
+
* @param next 目标缩放(null = 适应宽度)
|
|
19781
|
+
*/
|
|
19782
|
+
const applyImgZoom = (next) => {
|
|
19783
|
+
const value = next === null ? null : clampZoom(next);
|
|
19784
|
+
setImgZoom(value);
|
|
19785
|
+
rememberZoom(imgZoomKey, value);
|
|
19786
|
+
};
|
|
19787
|
+
/**
|
|
19788
|
+
* 图片缩放步进(10%/档,clamp 25%–400%);适应宽度态先按渲染尺寸实测当前缩放作基准。
|
|
19789
|
+
* @author ddj 2026年09月22号
|
|
19790
|
+
* @param dir 方向:1 放大 / -1 缩小
|
|
19791
|
+
*/
|
|
19792
|
+
const stepImgZoom = (dir) => {
|
|
19793
|
+
const el = imgElRef.current;
|
|
19794
|
+
const measured = el && el.naturalWidth ? el.clientWidth / el.naturalWidth : 1;
|
|
19795
|
+
applyImgZoom(zoomStepOf(imgZoom ?? measured, dir));
|
|
19796
|
+
};
|
|
19797
|
+
/**
|
|
19798
|
+
* 图片预览面板:工具条(尺寸 meta / 缩放 −+适应宽度 / SVG 文本切换 / 刷新)+ 棋盘底图片。
|
|
19799
|
+
* 初始适应宽度(等价 PDF page-width,0.1.7 官方统一缩放);按钮样式与 PDF 工具条同款。
|
|
19800
|
+
* @author ddj 2026年09月08号 / 2026年09月22号
|
|
18378
19801
|
*/
|
|
18379
|
-
const imagePanel = () => react.default.createElement("div", { className: "edrv-imgview" }, react.default.createElement("div", { className: "edrv-imgview-bar" }, react.default.createElement("span", { className: "edrv-imgview-meta" }, imgBroken ? "图片无法显示" : imgSize ? imgSize.w + "×" + imgSize.h : "…"), react.default.createElement("span", { style: { flex: 1 } }),
|
|
19802
|
+
const imagePanel = () => react.default.createElement("div", { className: "edrv-imgview" }, react.default.createElement("div", { className: "edrv-imgview-bar" }, react.default.createElement("span", { className: "edrv-imgview-meta" }, imgBroken ? "图片无法显示" : imgSize ? imgSize.w + "×" + imgSize.h : "…"), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("span", { className: "edrv-pdf-page" }, imgZoom === null ? "适应" : Math.round(imgZoom * 100) + "%"), react.default.createElement("button", {
|
|
19803
|
+
className: "edrv-pill edrv-pill-ghost edrv-pdf-zoom",
|
|
19804
|
+
title: "缩小(10%/档)",
|
|
19805
|
+
onClick: () => stepImgZoom(-1)
|
|
19806
|
+
}, "−"), react.default.createElement("button", {
|
|
19807
|
+
className: "edrv-pill edrv-pill-ghost edrv-pdf-zoom",
|
|
19808
|
+
title: "放大(10%/档)",
|
|
19809
|
+
onClick: () => stepImgZoom(1)
|
|
19810
|
+
}, "+"), react.default.createElement("button", {
|
|
19811
|
+
className: "edrv-pill edrv-pill-ghost",
|
|
19812
|
+
title: "恢复适应宽度",
|
|
19813
|
+
disabled: imgZoom === null,
|
|
19814
|
+
onClick: () => applyImgZoom(null)
|
|
19815
|
+
}, "适应宽度"), isSvgPath(active) ? react.default.createElement("button", {
|
|
18380
19816
|
className: "edrv-pill edrv-pill-ghost",
|
|
18381
19817
|
title: "以文本方式查看/编辑(SVG 为文本格式)",
|
|
18382
19818
|
onClick: () => toggleSvgText(active)
|
|
@@ -18389,8 +19825,14 @@ window.__ModuleLoader__.load({
|
|
|
18389
19825
|
onClick: () => reloadFile()
|
|
18390
19826
|
}, "重试")) : react.default.createElement("img", {
|
|
18391
19827
|
className: "edrv-imgview-img",
|
|
19828
|
+
ref: imgElRef,
|
|
18392
19829
|
src: imageSrc,
|
|
18393
19830
|
alt: active || "",
|
|
19831
|
+
style: imgZoom !== null && imgSize ? {
|
|
19832
|
+
width: Math.round(imgSize.w * imgZoom) + "px",
|
|
19833
|
+
maxWidth: "none",
|
|
19834
|
+
maxHeight: "none"
|
|
19835
|
+
} : void 0,
|
|
18394
19836
|
onLoad: (e) => {
|
|
18395
19837
|
setImgBroken(false);
|
|
18396
19838
|
setImgSize({
|
|
@@ -19040,6 +20482,7 @@ window.__ModuleLoader__.load({
|
|
|
19040
20482
|
};
|
|
19041
20483
|
const openFile = (path, focusDiff) => {
|
|
19042
20484
|
if (!path) return;
|
|
20485
|
+
if (!focusDiff && inNativePath(path) && tryNativeOpen(path)) return;
|
|
19043
20486
|
setSvnDiff(null);
|
|
19044
20487
|
recordNav();
|
|
19045
20488
|
const normalized = addTabNorm(path, true);
|
|
@@ -19077,6 +20520,13 @@ window.__ModuleLoader__.load({
|
|
|
19077
20520
|
endLine: endLine ?? null,
|
|
19078
20521
|
endColumn: endColumn ?? null
|
|
19079
20522
|
};
|
|
20523
|
+
putPendingNav({
|
|
20524
|
+
path: normalized,
|
|
20525
|
+
line: line ?? null,
|
|
20526
|
+
column: column ?? 1,
|
|
20527
|
+
endLine: endLine ?? null,
|
|
20528
|
+
endColumn: endColumn ?? null
|
|
20529
|
+
});
|
|
19080
20530
|
setFocusRequest((value) => value + 1);
|
|
19081
20531
|
};
|
|
19082
20532
|
const dapOpenRef = react.default.useRef(null);
|
|
@@ -19559,6 +21009,7 @@ window.__ModuleLoader__.load({
|
|
|
19559
21009
|
onClose: () => setSnippetPicker(null)
|
|
19560
21010
|
}) : null;
|
|
19561
21011
|
const sidebarPanels = props.sidebarPanels;
|
|
21012
|
+
const svnChangeMapMemo = react.default.useMemo(() => svnChangeMapOf(scope), [scope, svnChanges]);
|
|
19562
21013
|
const sidebarCtx = {
|
|
19563
21014
|
sessionId,
|
|
19564
21015
|
cwd: cwd ?? null,
|
|
@@ -19578,7 +21029,7 @@ window.__ModuleLoader__.load({
|
|
|
19578
21029
|
addToConversation,
|
|
19579
21030
|
svn: svnStatus,
|
|
19580
21031
|
svnChanges,
|
|
19581
|
-
svnChangeMap:
|
|
21032
|
+
svnChangeMap: svnChangeMapMemo,
|
|
19582
21033
|
openSvnDiff: (p) => runSvnDiffBase(p),
|
|
19583
21034
|
openSvnLocalPair: runSvnDiffLocalPair,
|
|
19584
21035
|
refreshSvnChanges: () => refreshSvnChanges(sessionId, scope),
|
|
@@ -20233,7 +21684,7 @@ window.__ModuleLoader__.load({
|
|
|
20233
21684
|
if (otherFiles.length) othersEl = react.default.createElement("div", { className: "edrv-diffbox-others" }, react.default.createElement("span", { className: "edrv-diffrow-tag" }, "其他差异文件 (" + otherFiles.length + "):"), otherFiles.map((f) => react.default.createElement("button", {
|
|
20234
21685
|
key: f.path,
|
|
20235
21686
|
className: "edrv-diffrow-file",
|
|
20236
|
-
title: f.path,
|
|
21687
|
+
title: f.label ?? f.path,
|
|
20237
21688
|
onClick: () => onOpenOther(f.path)
|
|
20238
21689
|
}, String(f.path).split(/[\\/]/).pop() + " (" + f.pending + ")")));
|
|
20239
21690
|
bodyEl = react.default.createElement("div", { className: "edrv-diffbar-body" }, ...rows, ...stale, othersEl);
|
|
@@ -20392,8 +21843,9 @@ window.__ModuleLoader__.load({
|
|
|
20392
21843
|
//#region src/client/settingsContext.ts
|
|
20393
21844
|
/**
|
|
20394
21845
|
* dsh-vscode-mode client — 设置 scope 的跨组件上下文。
|
|
20395
|
-
* 绑定逻辑(webUiSettings 兼容桥 → 官方 settingsScope
|
|
20396
|
-
*
|
|
21846
|
+
* 绑定逻辑(configForms 0.1.7+ → webUiSettings 兼容桥 → 官方 settingsScope 降级,
|
|
21847
|
+
* 带晚到有界重试)统一由 compat 层提供。
|
|
21848
|
+
* 作者 ddj 2026年08月24号 / 2026年09月22号
|
|
20397
21849
|
*/
|
|
20398
21850
|
const SettingsContext = react.default.createContext(void 0);
|
|
20399
21851
|
//#endregion
|
|
@@ -21102,7 +22554,7 @@ window.__ModuleLoader__.load({
|
|
|
21102
22554
|
//#region src/client/ui/PerfSettings.ts
|
|
21103
22555
|
/**
|
|
21104
22556
|
* dsh-vscode-mode client — PerfSettings:「性能优化」设置子页(会话卫生 + 压缩调优)。
|
|
21105
|
-
* -
|
|
22557
|
+
* - 会话盘点:全工作区/每会话体积、活跃标记、官方归档/置顶徽标,按体积排序;
|
|
21106
22558
|
* - 移出到归档(先规划→确认→执行→可恢复)与归档清除;
|
|
21107
22559
|
* - DSH 内置压缩调优:写 profile patch(compaction-basic / tool-result-pruner 更低阈值,
|
|
21108
22560
|
* 带备份与撤销,需重启生效);
|
|
@@ -21170,7 +22622,13 @@ window.__ModuleLoader__.load({
|
|
|
21170
22622
|
}, row.sessionId), react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-size " + (row.bytes >= 1048576 ? "big" : "") }, formatBytes(row.bytes)), react.default.createElement("span", {
|
|
21171
22623
|
className: "vsm-perf-cell vsm-perf-mtime",
|
|
21172
22624
|
title: new Date(row.mtime).toLocaleString()
|
|
21173
|
-
}, when), row.active ? react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-active" }, "活跃") : null
|
|
22625
|
+
}, when), row.active ? react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-active" }, "活跃") : null, row.archived ? react.default.createElement("span", {
|
|
22626
|
+
className: "vsm-perf-cell vsm-perf-active",
|
|
22627
|
+
title: "官方侧栏已归档(持久标志位,未搬目录)"
|
|
22628
|
+
}, "已归档") : null, row.pinned ? react.default.createElement("span", {
|
|
22629
|
+
className: "vsm-perf-cell vsm-perf-active",
|
|
22630
|
+
title: "官方侧栏已置顶"
|
|
22631
|
+
}, "已置顶") : null);
|
|
21174
22632
|
}
|
|
21175
22633
|
/**
|
|
21176
22634
|
* 工作区列表分页器:顶部文案 + 上一页/下一页交互。
|
|
@@ -21650,6 +23108,8 @@ window.__ModuleLoader__.load({
|
|
|
21650
23108
|
* 开关 + 模型下拉(edrv.ai.models 目录,按 provider 分组,默认「自动」)+
|
|
21651
23109
|
* 思考强度下拉(随所选模型的 efforts 元数据动态渲染:无档位隐藏;含「跟随默认」)。
|
|
21652
23110
|
* 保存走 edrv.ai.configUpdate;保存后广播 edrv:ai-config 事件让 provider 即时生效。
|
|
23111
|
+
* 第二组「AI 任务模型」:非补全 AI 调用(AI 智能整理等)专用路由,独立于补全模型;
|
|
23112
|
+
* 留空 = 跟随补全模型配置(向后兼容)。
|
|
21653
23113
|
* 作者 ddj
|
|
21654
23114
|
*/
|
|
21655
23115
|
/**
|
|
@@ -21668,7 +23128,10 @@ window.__ModuleLoader__.load({
|
|
|
21668
23128
|
enabled: c.enabled,
|
|
21669
23129
|
provider: c.provider,
|
|
21670
23130
|
model: c.model,
|
|
21671
|
-
effort: c.effort
|
|
23131
|
+
effort: c.effort,
|
|
23132
|
+
taskProvider: c.taskProvider || "",
|
|
23133
|
+
taskModel: c.taskModel || "",
|
|
23134
|
+
taskEffort: c.taskEffort || ""
|
|
21672
23135
|
});
|
|
21673
23136
|
else setError(c.error || "配置读取失败");
|
|
21674
23137
|
if (d.ok) setDir(d);
|
|
@@ -21681,11 +23144,11 @@ window.__ModuleLoader__.load({
|
|
|
21681
23144
|
if (!provider) return dir.providers[0]?.models ?? [];
|
|
21682
23145
|
return dir.providers.find((p) => p.id === provider)?.models ?? [];
|
|
21683
23146
|
};
|
|
21684
|
-
/**
|
|
21685
|
-
const
|
|
21686
|
-
const models = modelsOf(
|
|
21687
|
-
return models.find((m) => m.model ===
|
|
21688
|
-
}
|
|
23147
|
+
/** 档位可选项(补全/任务两组选择器共用):模型无 reasoning 元数据 → 空数组(隐藏)。 */
|
|
23148
|
+
const effortsOf = (provider, model) => {
|
|
23149
|
+
const models = modelsOf(provider);
|
|
23150
|
+
return models.find((m) => m.model === model)?.efforts ?? models[0]?.efforts ?? [];
|
|
23151
|
+
};
|
|
21689
23152
|
const save = async (patch) => {
|
|
21690
23153
|
setBusy(true);
|
|
21691
23154
|
setError("");
|
|
@@ -21696,7 +23159,10 @@ window.__ModuleLoader__.load({
|
|
|
21696
23159
|
enabled: res.enabled,
|
|
21697
23160
|
provider: res.provider,
|
|
21698
23161
|
model: res.model,
|
|
21699
|
-
effort: res.effort
|
|
23162
|
+
effort: res.effort,
|
|
23163
|
+
taskProvider: res.taskProvider || "",
|
|
23164
|
+
taskModel: res.taskModel || "",
|
|
23165
|
+
taskEffort: res.taskEffort || ""
|
|
21700
23166
|
});
|
|
21701
23167
|
setAiInlineEnabled(res.enabled);
|
|
21702
23168
|
setNote("已保存");
|
|
@@ -21708,27 +23174,41 @@ window.__ModuleLoader__.load({
|
|
|
21708
23174
|
}
|
|
21709
23175
|
};
|
|
21710
23176
|
const toggle = () => save({ enabled: !cfg.enabled });
|
|
21711
|
-
|
|
21712
|
-
|
|
21713
|
-
|
|
21714
|
-
|
|
21715
|
-
|
|
23177
|
+
/** 下拉值 `provider/model` → 换模型 patch(档位不属新模型时一并清空;补全/任务两组共用)。 */
|
|
23178
|
+
const patchOfModel = (value, keys) => {
|
|
23179
|
+
if (!value) return {
|
|
23180
|
+
[keys.provider]: "",
|
|
23181
|
+
[keys.model]: ""
|
|
23182
|
+
};
|
|
21716
23183
|
const idx = value.indexOf("/");
|
|
21717
23184
|
const provider = value.slice(0, idx);
|
|
21718
23185
|
const model = value.slice(idx + 1);
|
|
21719
|
-
|
|
21720
|
-
|
|
21721
|
-
|
|
21722
|
-
model
|
|
23186
|
+
return (modelsOf(provider).find((m) => m.model === model)?.efforts ?? []).some((e) => e.id === cfg[keys.effort]) ? {
|
|
23187
|
+
[keys.provider]: provider,
|
|
23188
|
+
[keys.model]: model
|
|
21723
23189
|
} : {
|
|
21724
|
-
provider,
|
|
21725
|
-
model,
|
|
21726
|
-
effort: ""
|
|
21727
|
-
}
|
|
23190
|
+
[keys.provider]: provider,
|
|
23191
|
+
[keys.model]: model,
|
|
23192
|
+
[keys.effort]: ""
|
|
23193
|
+
};
|
|
21728
23194
|
};
|
|
23195
|
+
const pickModel = (value) => save(patchOfModel(value, {
|
|
23196
|
+
provider: "provider",
|
|
23197
|
+
model: "model",
|
|
23198
|
+
effort: "effort"
|
|
23199
|
+
}));
|
|
23200
|
+
const pickTaskModel = (value) => save(patchOfModel(value, {
|
|
23201
|
+
provider: "taskProvider",
|
|
23202
|
+
model: "taskModel",
|
|
23203
|
+
effort: "taskEffort"
|
|
23204
|
+
}));
|
|
21729
23205
|
if (!cfg) return react.default.createElement("div", { className: "vsm-mcp-empty" }, error || "正在读取 AI 补全配置…");
|
|
21730
23206
|
const modelValue = cfg.provider && cfg.model ? cfg.provider + "/" + cfg.model : "";
|
|
23207
|
+
const taskModelValue = cfg.taskProvider && cfg.taskModel ? cfg.taskProvider + "/" + cfg.taskModel : "";
|
|
23208
|
+
const efforts = effortsOf(cfg.provider, cfg.model);
|
|
23209
|
+
const taskEfforts = effortsOf(cfg.taskProvider, cfg.taskModel);
|
|
21731
23210
|
const delisted = isDelistedModel(cfg.model);
|
|
23211
|
+
const taskDelisted = isDelistedModel(cfg.taskModel);
|
|
21732
23212
|
return react.default.createElement("section", { className: "vsm-lsp-card" }, react.default.createElement("h3", null, "AI 自动补全(实验)"), react.default.createElement("p", { className: "vsm-lsp-note" }, "编辑停顿后由模型生成内联建议(ghost text),Tab 接受,Alt+\\ 手动触发。每次补全是一次模型调用;建议选择非推理模型,且思考强度选「跟随默认」或最低档以获得更快响应。"), delisted ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "当前模型「" + cfg.model + "」已从 DSH 默认模型列表移除(0.1.6-alpha.2 起)。该模型可能不再可用;将「模型」改回「自动」即可使用第一个可用模型(现有配置不会被自动改动)。") : null, react.default.createElement("div", { className: "vsm-lsp-row" }, react.default.createElement("button", {
|
|
21733
23213
|
className: cfg.enabled ? "vsm-primary" : "",
|
|
21734
23214
|
disabled: busy,
|
|
@@ -21747,6 +23227,20 @@ window.__ModuleLoader__.load({
|
|
|
21747
23227
|
}, react.default.createElement("option", { value: "" }, "跟随默认"), efforts.map((e) => react.default.createElement("option", {
|
|
21748
23228
|
key: e.id,
|
|
21749
23229
|
value: e.id
|
|
23230
|
+
}, e.name)))) : null), react.default.createElement("h3", null, "AI 任务模型(AI 智能整理等非补全调用)"), react.default.createElement("p", { className: "vsm-lsp-note" }, "独立于补全模型:整理分析可选推理模型 + 较高思考强度,补全仍可选快模型。留空 = 跟随补全模型配置。"), taskDelisted ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "任务模型「" + cfg.taskModel + "」已从 DSH 默认模型列表移除(0.1.6-alpha.2 起)。该模型可能不再可用;将「模型」改回「自动」即可跟随补全配置(现有配置不会被自动改动)。") : null, react.default.createElement("div", { className: "vsm-lsp-row" }, react.default.createElement("label", null, "模型", react.default.createElement("select", {
|
|
23231
|
+
value: taskModelValue,
|
|
23232
|
+
disabled: busy || !dir?.providers?.length,
|
|
23233
|
+
onChange: (e) => pickTaskModel(e.target.value)
|
|
23234
|
+
}, react.default.createElement("option", { value: "" }, "自动(跟随补全配置)"), (dir?.providers ?? []).flatMap((p) => p.models.map((m) => react.default.createElement("option", {
|
|
23235
|
+
key: p.id + "/" + m.model,
|
|
23236
|
+
value: p.id + "/" + m.model
|
|
23237
|
+
}, p.name + " · " + m.name))))), taskEfforts.length ? react.default.createElement("label", null, "思考强度", react.default.createElement("select", {
|
|
23238
|
+
value: cfg.taskEffort,
|
|
23239
|
+
disabled: busy,
|
|
23240
|
+
onChange: (e) => save({ taskEffort: e.target.value })
|
|
23241
|
+
}, react.default.createElement("option", { value: "" }, "跟随默认"), taskEfforts.map((e) => react.default.createElement("option", {
|
|
23242
|
+
key: e.id,
|
|
23243
|
+
value: e.id
|
|
21750
23244
|
}, e.name)))) : null), note && react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, note)), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error));
|
|
21751
23245
|
}
|
|
21752
23246
|
//#endregion
|
|
@@ -21914,6 +23408,8 @@ window.__ModuleLoader__.load({
|
|
|
21914
23408
|
const [minDraft, setMinDraft] = react.default.useState(null);
|
|
21915
23409
|
const [limit, setLimit] = react.default.useState(10);
|
|
21916
23410
|
const [limitDraft, setLimitDraft] = react.default.useState(null);
|
|
23411
|
+
const [nativeSaved, setNativeSaved] = react.default.useState(DEFAULT_NATIVE_CSV);
|
|
23412
|
+
const [nativeDraft, setNativeDraft] = react.default.useState(null);
|
|
21917
23413
|
const settings = react.default.useContext(SettingsContext);
|
|
21918
23414
|
const snapshot = settings?.getSnapshot?.();
|
|
21919
23415
|
const loading = !snapshot || snapshot.status === "loading";
|
|
@@ -21926,6 +23422,8 @@ window.__ModuleLoader__.load({
|
|
|
21926
23422
|
if (typeof next === "string") setTool(next);
|
|
21927
23423
|
setMinW(normalizeSidebarMinWidth(snap?.value?.sidebarMinWidth));
|
|
21928
23424
|
setLimit(normalizeMaxOpenEditors(snap?.value?.maxOpenEditors));
|
|
23425
|
+
const native = snap?.value?.nativeOpenExts;
|
|
23426
|
+
setNativeSaved(typeof native === "string" && native.trim() ? native : DEFAULT_NATIVE_CSV);
|
|
21929
23427
|
};
|
|
21930
23428
|
onChange();
|
|
21931
23429
|
return settings?.subscribe?.(onChange);
|
|
@@ -21984,6 +23482,25 @@ window.__ModuleLoader__.load({
|
|
|
21984
23482
|
setLimitDraft(null);
|
|
21985
23483
|
saveLimit(limitDraft);
|
|
21986
23484
|
};
|
|
23485
|
+
/** 提交原生打开范围(逗号分隔后缀;空串回落默认让位清单后持久化)。 */
|
|
23486
|
+
const saveNative = (raw) => {
|
|
23487
|
+
const next = String(raw ?? "").trim() || DEFAULT_NATIVE_CSV;
|
|
23488
|
+
setNativeSaved(next);
|
|
23489
|
+
setNativeDraft(null);
|
|
23490
|
+
setBusy(true);
|
|
23491
|
+
setError("");
|
|
23492
|
+
if (!settings?.set) {
|
|
23493
|
+
setError("设置服务不可用");
|
|
23494
|
+
setBusy(false);
|
|
23495
|
+
return;
|
|
23496
|
+
}
|
|
23497
|
+
settings.set("nativeOpenExts", next).catch((e) => setError(String(e))).finally(() => setBusy(false));
|
|
23498
|
+
};
|
|
23499
|
+
/** 结束输入(blur/Enter)时提交原生打开范围草稿。 */
|
|
23500
|
+
const commitNative = () => {
|
|
23501
|
+
if (nativeDraft === null) return;
|
|
23502
|
+
saveNative(nativeDraft);
|
|
23503
|
+
};
|
|
21987
23504
|
const closeDevForm = () => {
|
|
21988
23505
|
if (!window.confirm("关闭开发形态:插件将切换为正式版安装(版本依赖 + 删除工作区链接),pnpm 装配后需重启 DSH 生效。确认关闭?")) return;
|
|
21989
23506
|
setDevBusy(true);
|
|
@@ -22045,7 +23562,18 @@ window.__ModuleLoader__.load({
|
|
|
22045
23562
|
onKeyDown: (event) => {
|
|
22046
23563
|
if (event.key === "Enter") commitLimit();
|
|
22047
23564
|
}
|
|
22048
|
-
}), react.default.createElement("small", null, "0 = 不限制;超限时关闭最久未使用的页签(固定页签除外)"))
|
|
23565
|
+
}), react.default.createElement("small", null, "0 = 不限制;超限时关闭最久未使用的页签(固定页签除外)")), react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "原生打开范围"), react.default.createElement("input", {
|
|
23566
|
+
type: "text",
|
|
23567
|
+
value: nativeDraft ?? nativeSaved,
|
|
23568
|
+
disabled: loading || unavailable || notReady || busy || snapshot?.writable === false,
|
|
23569
|
+
title: "逗号分隔后缀(如 doc,xlsx):文件树点击命中时用 DSH 官方预览原生打开;留空 = 默认让位清单(Office + 不可预览二进制)",
|
|
23570
|
+
placeholder: "doc,xlsx,…(留空恢复默认)",
|
|
23571
|
+
onChange: (event) => setNativeDraft(event.target.value),
|
|
23572
|
+
onBlur: commitNative,
|
|
23573
|
+
onKeyDown: (event) => {
|
|
23574
|
+
if (event.key === "Enter") commitNative();
|
|
23575
|
+
}
|
|
23576
|
+
}), react.default.createElement("small", null, "逗号分隔;命中的文件用 DSH 原生预览打开,留空恢复默认(Office + 二进制,csv/tsv 需显式加入)")))), react.default.createElement(SvnSettingsSection, null), react.default.createElement(IntegrationSettings, null));
|
|
22049
23577
|
}
|
|
22050
23578
|
/** 检测快照 → 一行状态文案(未检测/未检出/能力可用性)。 */
|
|
22051
23579
|
function svnStatusText(info) {
|
|
@@ -23294,6 +24822,93 @@ window.__ModuleLoader__.load({
|
|
|
23294
24822
|
};
|
|
23295
24823
|
}
|
|
23296
24824
|
//#endregion
|
|
24825
|
+
//#region src/client/ui/icons.ts
|
|
24826
|
+
/**
|
|
24827
|
+
* dsh-vscode-mode client — UI 原语图标跨版本出口(消费方导出名保持 ≤0.1.6 旧名)。
|
|
24828
|
+
* 背景:DSH 0.1.7 图标命名重构——`Icon<Name>16`(0.1.6 及更旧)→
|
|
24829
|
+
* `Icon<Name>Regular/Medium`(0.1.7+,size 参数化 + 双笔画档),旧名在 0.1.7
|
|
24830
|
+
* 全树已不存在(整树 grep 实证)。解析策略:namespace import 属性探测,旧名优先
|
|
24831
|
+
* (现网 0.1.6)→ 新名次之(0.1.7+)→ 皆缺走通用占位(第三态不炸渲染);
|
|
24832
|
+
* 打包期 external 不校验成员存在性,两代运行时均稳(能力探测,不做版本判定)。
|
|
24833
|
+
* 调用点零语义变化:导出名与旧垫片一致,仅改 import 来源。
|
|
24834
|
+
* @author ddj 2026年09月22号
|
|
24835
|
+
*/
|
|
24836
|
+
/**
|
|
24837
|
+
* 通用占位图标(两代皆缺的第三态兜底:空态方框,不抛错不白屏)。
|
|
24838
|
+
* @author ddj 2026年09月22号
|
|
24839
|
+
* @param props 图标 props(size/className 透传)
|
|
24840
|
+
* @returns React 元素
|
|
24841
|
+
*/
|
|
24842
|
+
function fallbackIcon(props) {
|
|
24843
|
+
return react.default.createElement("svg", {
|
|
24844
|
+
width: props.size ?? 16,
|
|
24845
|
+
height: props.size ?? 16,
|
|
24846
|
+
viewBox: "0 0 16 16",
|
|
24847
|
+
className: props.className,
|
|
24848
|
+
"aria-hidden": true,
|
|
24849
|
+
focusable: false
|
|
24850
|
+
}, react.default.createElement("rect", {
|
|
24851
|
+
x: 3.5,
|
|
24852
|
+
y: 3.5,
|
|
24853
|
+
width: 9,
|
|
24854
|
+
height: 9,
|
|
24855
|
+
rx: 2,
|
|
24856
|
+
fill: "none",
|
|
24857
|
+
stroke: "currentColor",
|
|
24858
|
+
strokeWidth: 1.2,
|
|
24859
|
+
opacity: .45
|
|
24860
|
+
}));
|
|
24861
|
+
}
|
|
24862
|
+
/**
|
|
24863
|
+
* 按候选名序列解析图标组件(首个存在的函数命中;皆缺返回占位)。
|
|
24864
|
+
* @author ddj 2026年09月22号
|
|
24865
|
+
* @param names 候选导出名(旧名在前、新名在后)
|
|
24866
|
+
* @returns 图标组件
|
|
24867
|
+
*/
|
|
24868
|
+
function iconOf(...names) {
|
|
24869
|
+
const table = _deepseek_ai_dsh_client_ui_primitives;
|
|
24870
|
+
for (const name of names) {
|
|
24871
|
+
const hit = table[name];
|
|
24872
|
+
if (typeof hit === "function") return hit;
|
|
24873
|
+
}
|
|
24874
|
+
return fallbackIcon;
|
|
24875
|
+
}
|
|
24876
|
+
/** 目录闭合图标(官方文件树同款)。 */
|
|
24877
|
+
const IconFolderClose16 = iconOf("IconFolderClose16", "IconFolderCloseRegular");
|
|
24878
|
+
/** 目录展开图标(官方文件树同款)。 */
|
|
24879
|
+
const IconFolderOpen16 = iconOf("IconFolderOpen16", "IconFolderOpenRegular");
|
|
24880
|
+
/** 目录轮廓图标(活动栏「文件管理」)。 */
|
|
24881
|
+
const IconFolderOpenOutline16 = iconOf("IconFolderOpenOutline16", "IconFolderOpenOutlineRegular");
|
|
24882
|
+
/** 刷新图标(面板工具条)。 */
|
|
24883
|
+
const IconRefreshOutline16 = iconOf("IconRefreshOutline16", "IconRefreshOutlineRegular");
|
|
24884
|
+
/** 搜索图标(活动栏「搜索」)。 */
|
|
24885
|
+
const IconSearchOutline16 = iconOf("IconSearchOutline16", "IconSearchOutlineRegular");
|
|
24886
|
+
/** 列表+笔图标(活动栏「规则」)。 */
|
|
24887
|
+
const IconListPenOutline16 = iconOf("IconListPenOutline16", "IconListPenOutlineRegular");
|
|
24888
|
+
/** 代码图标(活动栏「大纲」)。 */
|
|
24889
|
+
const IconCodeOutline16 = iconOf("IconCodeOutline16", "IconCodeOutlineRegular");
|
|
24890
|
+
/** 分支图标(SVN 面板)。 */
|
|
24891
|
+
const IconBranchOutline16 = iconOf("IconBranchOutline16", "IconBranchOutlineRegular");
|
|
24892
|
+
/**
|
|
24893
|
+
* 文件图标元素(官方 FileTypeIcon + classifyFileType 分类,未知类型为 other)。
|
|
24894
|
+
* 文件树与 SVN 变更行共用(原为 FileExplorer 私有副本,上移共享防两处漂移)。
|
|
24895
|
+
* @author ddj 2026年09月23号
|
|
24896
|
+
* @param name 文件名(含扩展名;目录由调用方走目录图标)
|
|
24897
|
+
* @returns 图标元素;原语缺失或分类异常返回 null
|
|
24898
|
+
*/
|
|
24899
|
+
function fileIconEl(name) {
|
|
24900
|
+
if (typeof _deepseek_ai_dsh_client_ui_primitives.FileTypeIcon !== "function" || typeof _deepseek_ai_dsh_client_ui_primitives.classifyFileType !== "function") return null;
|
|
24901
|
+
try {
|
|
24902
|
+
return (0, _deepseek_ai_dsh_client_ui_primitives.FileTypeIcon)({
|
|
24903
|
+
kind: (0, _deepseek_ai_dsh_client_ui_primitives.classifyFileType)(String(name ?? "")),
|
|
24904
|
+
size: 16,
|
|
24905
|
+
className: "edrv-tree-icon"
|
|
24906
|
+
});
|
|
24907
|
+
} catch (error) {
|
|
24908
|
+
return null;
|
|
24909
|
+
}
|
|
24910
|
+
}
|
|
24911
|
+
//#endregion
|
|
23297
24912
|
//#region src/client/sidebar/contextMenu.ts
|
|
23298
24913
|
/** 校验菜单项并写入注册表(缺 id/run/label 抛 TypeError)。 */
|
|
23299
24914
|
function itemRegister(entries, notify, item) {
|
|
@@ -23671,7 +25286,7 @@ window.__ModuleLoader__.load({
|
|
|
23671
25286
|
* @returns 图标元素;原语缺失返回 null
|
|
23672
25287
|
*/
|
|
23673
25288
|
function dirIconEl(isOpen) {
|
|
23674
|
-
const Icon = isOpen ?
|
|
25289
|
+
const Icon = isOpen ? IconFolderOpen16 : IconFolderClose16;
|
|
23675
25290
|
if (typeof Icon !== "function") return null;
|
|
23676
25291
|
return react.default.createElement(Icon, {
|
|
23677
25292
|
size: 16,
|
|
@@ -23679,24 +25294,6 @@ window.__ModuleLoader__.load({
|
|
|
23679
25294
|
});
|
|
23680
25295
|
}
|
|
23681
25296
|
/**
|
|
23682
|
-
* 文件图标元素(官方 FileTypeIcon + classifyFileType 分类,未知类型为 other)。
|
|
23683
|
-
* @author ddj 2026年09月10号
|
|
23684
|
-
* @param name 文件名(含扩展名)
|
|
23685
|
-
* @returns 图标元素;原语缺失或分类异常返回 null
|
|
23686
|
-
*/
|
|
23687
|
-
function fileIconEl(name) {
|
|
23688
|
-
if (typeof _deepseek_ai_dsh_client_ui_primitives.FileTypeIcon !== "function" || typeof _deepseek_ai_dsh_client_ui_primitives.classifyFileType !== "function") return null;
|
|
23689
|
-
try {
|
|
23690
|
-
return react.default.createElement(_deepseek_ai_dsh_client_ui_primitives.FileTypeIcon, {
|
|
23691
|
-
kind: (0, _deepseek_ai_dsh_client_ui_primitives.classifyFileType)(String(name ?? "")),
|
|
23692
|
-
size: 16,
|
|
23693
|
-
className: "edrv-tree-icon"
|
|
23694
|
-
});
|
|
23695
|
-
} catch (error) {
|
|
23696
|
-
return null;
|
|
23697
|
-
}
|
|
23698
|
-
}
|
|
23699
|
-
/**
|
|
23700
25297
|
* 行图标分派:目录 → 文件夹图标,文件 → 官方类型图标。
|
|
23701
25298
|
* @author ddj 2026年09月10号
|
|
23702
25299
|
* @param e 目录条目(name/type)
|
|
@@ -23713,8 +25310,8 @@ window.__ModuleLoader__.load({
|
|
|
23713
25310
|
* @returns 图标元素或回落文本
|
|
23714
25311
|
*/
|
|
23715
25312
|
function refreshIconEl$1() {
|
|
23716
|
-
if (typeof
|
|
23717
|
-
return react.default.createElement(
|
|
25313
|
+
if (typeof IconRefreshOutline16 !== "function") return "⟳";
|
|
25314
|
+
return react.default.createElement(IconRefreshOutline16, { size: 14 });
|
|
23718
25315
|
}
|
|
23719
25316
|
/**
|
|
23720
25317
|
* 转义 CSS 属性选择器的值(路径含引号/反斜杠时避免选择器语法错误)。
|
|
@@ -25208,6 +26805,327 @@ window.__ModuleLoader__.load({
|
|
|
25208
26805
|
}, renderTabs(), renderWsLine(), react.default.createElement("div", { className: "edrv-rules-hint" }, tab === "user" ? "用户规则对所有会话生效(存放于 ~/.dsh/rules/)" : "项目规则仅对所在工作区的会话生效(存放于 <工作区>/.dsh/rules/)"), renderBody());
|
|
25209
26806
|
}
|
|
25210
26807
|
//#endregion
|
|
26808
|
+
//#region src/client/ui/SvnAiPlanDialog.ts
|
|
26809
|
+
/**
|
|
26810
|
+
* dsh-vscode-mode client — AI 智能整理弹窗(11-ai-changelist-triage)。
|
|
26811
|
+
*
|
|
26812
|
+
* 双阶段单弹窗:
|
|
26813
|
+
* - 预览(run = null):按**整理功能**划分三个分组卡(建议分组 changelist / 建议还原 revert /
|
|
26814
|
+
* 建议忽略 svn:ignore),每段段级全选 + 逐项勾选确认;默认「分组勾选(可逆无副作用)、
|
|
26815
|
+
* 还原/忽略不勾(有副作用)」,未勾选项一律不动。
|
|
26816
|
+
* - 执行(run 非空):弹窗不关闭,切换为进度视图——阶段徽标 + 步骤计数 + 总进度条 +
|
|
26817
|
+
* 逐步日志(✓/✗,失败红字);「暂停」在当前 RPC 返回后的步骤边界生效(svn 子进程不可
|
|
26818
|
+
* 安全中断,如实标注「等待当前步骤完成…」),「继续」从断点续跑,「取消剩余」终止未执行
|
|
26819
|
+
* 步骤(已执行不回滚)。
|
|
26820
|
+
* Esc/遮罩仅在预览阶段生效(= 取消零副作用);执行阶段防误关。
|
|
26821
|
+
* 作者 ddj 2026年09月23号
|
|
26822
|
+
*/
|
|
26823
|
+
/**
|
|
26824
|
+
* 预览勾选初值:分组默认全勾(changelist 可逆),还原/忽略默认不勾(有副作用)。
|
|
26825
|
+
* @author ddj 2026年09月23号
|
|
26826
|
+
* @param plan 三段方案
|
|
26827
|
+
* @returns 勾选态(组级 + 逐项)
|
|
26828
|
+
*/
|
|
26829
|
+
function initPlanSel(plan) {
|
|
26830
|
+
return {
|
|
26831
|
+
groups: (plan.groups || []).map((group) => ({
|
|
26832
|
+
name: group.name,
|
|
26833
|
+
reason: group.reason || "",
|
|
26834
|
+
checked: true,
|
|
26835
|
+
paths: (group.paths || []).map((path) => ({
|
|
26836
|
+
path,
|
|
26837
|
+
checked: true
|
|
26838
|
+
}))
|
|
26839
|
+
})),
|
|
26840
|
+
reverts: (plan.reverts || []).map((item) => ({
|
|
26841
|
+
path: item.path,
|
|
26842
|
+
reason: item.reason || "",
|
|
26843
|
+
checked: false
|
|
26844
|
+
})),
|
|
26845
|
+
ignores: (plan.ignores || []).map((item) => ({
|
|
26846
|
+
path: item.path,
|
|
26847
|
+
reason: item.reason || "",
|
|
26848
|
+
checked: false
|
|
26849
|
+
}))
|
|
26850
|
+
};
|
|
26851
|
+
}
|
|
26852
|
+
/**
|
|
26853
|
+
* 勾选态 → 执行载荷(空段/空组剔除;保持勾选顺序)。
|
|
26854
|
+
* @author ddj 2026年09月23号
|
|
26855
|
+
* @param sel 勾选态
|
|
26856
|
+
* @returns 执行载荷 { groups, reverts, ignores }
|
|
26857
|
+
*/
|
|
26858
|
+
function selectedOf(sel) {
|
|
26859
|
+
const groups = [];
|
|
26860
|
+
for (const group of sel.groups) {
|
|
26861
|
+
const paths = group.paths.filter((p) => p.checked).map((p) => p.path);
|
|
26862
|
+
if (group.checked && paths.length) groups.push({
|
|
26863
|
+
name: group.name,
|
|
26864
|
+
paths
|
|
26865
|
+
});
|
|
26866
|
+
}
|
|
26867
|
+
return {
|
|
26868
|
+
groups,
|
|
26869
|
+
reverts: sel.reverts.filter((item) => item.checked).map((item) => item.path),
|
|
26870
|
+
ignores: sel.ignores.filter((item) => item.checked).map((item) => item.path)
|
|
26871
|
+
};
|
|
26872
|
+
}
|
|
26873
|
+
/** 执行载荷 → 已选动作总数(「执行所选」禁用判定)。 */
|
|
26874
|
+
function selectedCount(selected) {
|
|
26875
|
+
let n = selected.reverts.length + selected.ignores.length;
|
|
26876
|
+
for (const group of selected.groups) n += group.paths.length;
|
|
26877
|
+
return n;
|
|
26878
|
+
}
|
|
26879
|
+
/** 复选框元素(原生 input;仓库 UI 原语无 Checkbox,此处沿用补丁对话框的原生控件口径)。 */
|
|
26880
|
+
function checkBoxEl(checked, onToggle, title) {
|
|
26881
|
+
return react.default.createElement("input", {
|
|
26882
|
+
type: "checkbox",
|
|
26883
|
+
checked,
|
|
26884
|
+
title,
|
|
26885
|
+
onChange: () => onToggle(),
|
|
26886
|
+
onClick: (event) => event.stopPropagation()
|
|
26887
|
+
});
|
|
26888
|
+
}
|
|
26889
|
+
/** 段头(全选框 + 标题 + 计数 + 副标题)。 */
|
|
26890
|
+
function sectionHeadEl(title, note, allChecked, onToggleAll, countText) {
|
|
26891
|
+
return react.default.createElement("div", { className: "edrv-svnplan-sechead" }, checkBoxEl(allChecked, onToggleAll, "全选/全不选"), react.default.createElement("span", { className: "edrv-svnplan-sectitle" }, title), react.default.createElement("span", { className: "edrv-svn-count" }, countText), note ? react.default.createElement("span", { className: "edrv-svnplan-note" }, note) : null);
|
|
26892
|
+
}
|
|
26893
|
+
/**
|
|
26894
|
+
* 阶段一 · 预览确认(按整理功能三分组)。
|
|
26895
|
+
* @author ddj 2026年09月23号
|
|
26896
|
+
* @param sel 勾选态
|
|
26897
|
+
* @param setSel 勾选态更新
|
|
26898
|
+
* @param open 组卡展开集合(组名)
|
|
26899
|
+
* @param toggleOpen 组卡展开切换
|
|
26900
|
+
* @returns 预览主体元素
|
|
26901
|
+
*/
|
|
26902
|
+
function previewBodyEl(sel, setSel, open, toggleOpen) {
|
|
26903
|
+
/** 通用段更新(reverts/ignores 共用)。 */
|
|
26904
|
+
const patchItems = (key, index, checked) => setSel((old) => ({
|
|
26905
|
+
...old,
|
|
26906
|
+
[key]: old[key].map((item, i) => i === index ? {
|
|
26907
|
+
...item,
|
|
26908
|
+
checked
|
|
26909
|
+
} : item)
|
|
26910
|
+
}));
|
|
26911
|
+
const toggleItemsAll = (key, checked) => setSel((old) => ({
|
|
26912
|
+
...old,
|
|
26913
|
+
[key]: old[key].map((item) => ({
|
|
26914
|
+
...item,
|
|
26915
|
+
checked
|
|
26916
|
+
}))
|
|
26917
|
+
}));
|
|
26918
|
+
/** 组级/组内更新。 */
|
|
26919
|
+
const patchGroup = (index, patch) => setSel((old) => ({
|
|
26920
|
+
...old,
|
|
26921
|
+
groups: old.groups.map((group, i) => i === index ? {
|
|
26922
|
+
...group,
|
|
26923
|
+
...patch
|
|
26924
|
+
} : group)
|
|
26925
|
+
}));
|
|
26926
|
+
const toggleGroupAll = (index, checked) => setSel((old) => ({
|
|
26927
|
+
...old,
|
|
26928
|
+
groups: old.groups.map((group, i) => i === index ? {
|
|
26929
|
+
...group,
|
|
26930
|
+
checked,
|
|
26931
|
+
paths: group.paths.map((p) => ({
|
|
26932
|
+
...p,
|
|
26933
|
+
checked
|
|
26934
|
+
}))
|
|
26935
|
+
} : group)
|
|
26936
|
+
}));
|
|
26937
|
+
const groupCards = sel.groups.map((group, index) => {
|
|
26938
|
+
const done = group.paths.filter((p) => p.checked).length;
|
|
26939
|
+
const isOpen = open.has(group.name);
|
|
26940
|
+
const fileRows = isOpen ? group.paths.map((item, pi) => react.default.createElement("div", {
|
|
26941
|
+
key: item.path,
|
|
26942
|
+
className: "edrv-svnplan-item"
|
|
26943
|
+
}, checkBoxEl(item.checked, () => patchGroup(index, { paths: group.paths.map((p, j) => j === pi ? {
|
|
26944
|
+
...p,
|
|
26945
|
+
checked: !p.checked
|
|
26946
|
+
} : p) }), item.path), react.default.createElement("span", {
|
|
26947
|
+
className: "edrv-svnplan-path",
|
|
26948
|
+
title: item.path
|
|
26949
|
+
}, item.path))) : null;
|
|
26950
|
+
return react.default.createElement("div", {
|
|
26951
|
+
key: group.name,
|
|
26952
|
+
className: "edrv-svnplan-card"
|
|
26953
|
+
}, react.default.createElement("div", { className: "edrv-svnplan-row" }, checkBoxEl(group.checked, () => toggleGroupAll(index, !group.checked), "全选该组"), react.default.createElement("span", {
|
|
26954
|
+
className: "edrv-svnplan-groupname",
|
|
26955
|
+
title: "点击展开/收起成员",
|
|
26956
|
+
onClick: () => toggleOpen(group.name)
|
|
26957
|
+
}, (isOpen ? "▾ " : "▸ ") + group.name), react.default.createElement("span", { className: "edrv-svn-count" }, done + "/" + group.paths.length + " 项"), group.reason ? react.default.createElement("span", {
|
|
26958
|
+
className: "edrv-svnplan-reason",
|
|
26959
|
+
title: group.reason
|
|
26960
|
+
}, group.reason) : null), fileRows);
|
|
26961
|
+
});
|
|
26962
|
+
const itemRowsOf = (key) => sel[key].map((item, index) => react.default.createElement("div", {
|
|
26963
|
+
key: item.path,
|
|
26964
|
+
className: "edrv-svnplan-item"
|
|
26965
|
+
}, checkBoxEl(item.checked, () => patchItems(key, index, !item.checked), item.path), react.default.createElement("span", {
|
|
26966
|
+
className: "edrv-svnplan-path",
|
|
26967
|
+
title: item.path
|
|
26968
|
+
}, item.path), item.reason ? react.default.createElement("span", {
|
|
26969
|
+
className: "edrv-svnplan-reason",
|
|
26970
|
+
title: item.reason
|
|
26971
|
+
}, item.reason) : null));
|
|
26972
|
+
const groupsAll = sel.groups.length > 0 && sel.groups.every((g) => g.checked && g.paths.every((p) => p.checked));
|
|
26973
|
+
const revertsAll = sel.reverts.length > 0 && sel.reverts.every((i) => i.checked);
|
|
26974
|
+
const ignoresAll = sel.ignores.length > 0 && sel.ignores.every((i) => i.checked);
|
|
26975
|
+
return react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "edrv-svnplan-sec" }, sectionHeadEl("建议分组(changelist)", "无副作用、可逆", groupsAll, () => setSel((old) => ({
|
|
26976
|
+
...old,
|
|
26977
|
+
groups: old.groups.map((group) => ({
|
|
26978
|
+
...group,
|
|
26979
|
+
checked: !groupsAll,
|
|
26980
|
+
paths: group.paths.map((p) => ({
|
|
26981
|
+
...p,
|
|
26982
|
+
checked: !groupsAll
|
|
26983
|
+
}))
|
|
26984
|
+
}))
|
|
26985
|
+
})), sel.groups.length + " 组"), react.default.createElement("div", { className: "edrv-svnplan-list" }, groupCards)), react.default.createElement("div", { className: "edrv-svnplan-sec" }, sectionHeadEl("建议还原(revert)", "放弃本地改动,默认不勾", revertsAll, () => toggleItemsAll("reverts", !revertsAll), sel.reverts.length + " 项"), react.default.createElement("div", { className: "edrv-svnplan-list" }, itemRowsOf("reverts"))), react.default.createElement("div", { className: "edrv-svnplan-sec" }, sectionHeadEl("建议忽略(svn:ignore)", "将写入 svn:ignore 属性,随提交进仓库,默认不勾", ignoresAll, () => toggleItemsAll("ignores", !ignoresAll), sel.ignores.length + " 项"), react.default.createElement("div", { className: "edrv-svnplan-list" }, itemRowsOf("ignores"))));
|
|
26986
|
+
}
|
|
26987
|
+
/**
|
|
26988
|
+
* 阶段二 · 执行进度(阶段徽标 + 计数 + 进度条 + 逐步日志)。
|
|
26989
|
+
* @author ddj 2026年09月23号
|
|
26990
|
+
* @param run 执行状态
|
|
26991
|
+
* @returns 进度主体元素
|
|
26992
|
+
*/
|
|
26993
|
+
function progressBodyEl(run) {
|
|
26994
|
+
const pct = run.total > 0 ? Math.floor(run.index / run.total * 100) : 0;
|
|
26995
|
+
const statusText = run.status === "pausing" ? "等待当前步骤完成…(暂停在步骤边界生效)" : run.status === "paused" ? "已暂停" : run.status === "cancelling" ? "正在取消剩余步骤…" : run.status === "done" ? run.cancelled ? "已取消剩余步骤" : "执行完毕" : "执行中…";
|
|
26996
|
+
const logRows = run.log.map((entry, index) => react.default.createElement("div", {
|
|
26997
|
+
key: index,
|
|
26998
|
+
className: "edrv-svnplan-log" + (entry.ok ? "" : " edrv-svnplan-log-bad")
|
|
26999
|
+
}, (entry.ok ? "✓ " : "✗ ") + entry.text));
|
|
27000
|
+
return react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "edrv-svnplan-stage" }, react.default.createElement("span", { className: "edrv-svnplan-badge" }, statusText), run.cur ? react.default.createElement("span", { className: "edrv-svnplan-badge edrv-svnplan-badge-cur" }, run.cur) : null, react.default.createElement("span", { className: "edrv-svn-count" }, run.index + "/" + run.total + " 步")), react.default.createElement("div", { className: "edrv-svnplan-bar" }, react.default.createElement("div", {
|
|
27001
|
+
className: "edrv-svnplan-bar-in",
|
|
27002
|
+
style: { width: pct + "%" }
|
|
27003
|
+
})), react.default.createElement("div", { className: "edrv-svnplan-logwrap" }, logRows));
|
|
27004
|
+
}
|
|
27005
|
+
/**
|
|
27006
|
+
* AI 智能整理弹窗(预览 + 进度双阶段)。
|
|
27007
|
+
* @author ddj 2026年09月23号
|
|
27008
|
+
* @param props.plan 三段方案(host 已归一)
|
|
27009
|
+
* @param props.meta 分析元信息 { entriesCount, diffIncluded, dropped, model, source? }(source='agent' = 混合通道)
|
|
27010
|
+
* @param props.run 执行状态;null = 预览阶段(shape 见 SvnPanel runPlanSteps)
|
|
27011
|
+
* @param props.onExecute 预览确认(选中载荷 → 启动执行引擎)
|
|
27012
|
+
* @param props.onPause 暂停(步骤边界生效)
|
|
27013
|
+
* @param props.onResume 继续
|
|
27014
|
+
* @param props.onCancelRest 取消剩余
|
|
27015
|
+
* @param props.onClose 关闭(预览 = 取消零副作用;执行阶段按钮仅在结束后可用)
|
|
27016
|
+
* @returns 弹窗元素
|
|
27017
|
+
*/
|
|
27018
|
+
function SvnAiPlanDialog(props) {
|
|
27019
|
+
const { plan, meta, run, onExecute, onPause, onResume, onCancelRest, onClose } = props;
|
|
27020
|
+
const [sel, setSel] = react.default.useState(() => initPlanSel(plan));
|
|
27021
|
+
const [open, setOpen] = react.default.useState(() => /* @__PURE__ */ new Set());
|
|
27022
|
+
const selected = selectedOf(sel);
|
|
27023
|
+
const count = selectedCount(selected);
|
|
27024
|
+
/** 组卡展开切换(纯 UI 态)。 */
|
|
27025
|
+
const toggleOpen = (name) => {
|
|
27026
|
+
const next = new Set(open);
|
|
27027
|
+
if (next.has(name)) next.delete(name);
|
|
27028
|
+
else next.add(name);
|
|
27029
|
+
setOpen(next);
|
|
27030
|
+
};
|
|
27031
|
+
const running = run !== null && run.status !== "done";
|
|
27032
|
+
const header = react.default.createElement("div", { className: "edrv-svnplan-head" }, react.default.createElement("h3", { className: "edrv-svnlog-title" }, "AI 智能整理 SVN 变更"), meta?.source === "agent" ? react.default.createElement("span", {
|
|
27033
|
+
className: "edrv-svnplan-badge",
|
|
27034
|
+
title: "会话 AI 助手深度分析(codegraph/读文件)产出"
|
|
27035
|
+
}, "AI 助手深度分析") : null, meta?.model ? react.default.createElement("span", {
|
|
27036
|
+
className: "edrv-svn-count",
|
|
27037
|
+
title: "分析模型"
|
|
27038
|
+
}, meta.model) : null, react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("button", {
|
|
27039
|
+
className: "edrv-svn-act",
|
|
27040
|
+
title: running ? "执行中不可关闭(可取消剩余)" : "关闭",
|
|
27041
|
+
disabled: running,
|
|
27042
|
+
onClick: onClose
|
|
27043
|
+
}, "✕"));
|
|
27044
|
+
const metaEl = react.default.createElement("div", { className: "edrv-svnplan-meta" }, react.default.createElement("span", { className: "edrv-svn-count" }, "分析条目 " + (meta?.entriesCount ?? 0) + " 项"), (meta?.dropped ?? 0) > 0 ? react.default.createElement("span", { className: "edrv-svn-count" }, "已丢弃 AI 无效建议 " + meta.dropped + " 项") : null, meta?.diffIncluded === false ? react.default.createElement("span", { className: "edrv-svnplan-note" }, "未含 diff 上下文,分析精度有限") : null);
|
|
27045
|
+
let body = null;
|
|
27046
|
+
let footer = null;
|
|
27047
|
+
if (run === null) {
|
|
27048
|
+
body = previewBodyEl(sel, setSel, open, toggleOpen);
|
|
27049
|
+
footer = react.default.createElement("div", { className: "edrv-svnplan-foot" }, react.default.createElement("span", { className: "edrv-svn-count" }, "已选 " + count + " 项"), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("button", {
|
|
27050
|
+
className: "edrv-svn-act",
|
|
27051
|
+
title: "不执行任何动作",
|
|
27052
|
+
onClick: onClose
|
|
27053
|
+
}, "取消"), react.default.createElement("button", {
|
|
27054
|
+
className: "edrv-svn-act edrv-svnplan-primary",
|
|
27055
|
+
title: "执行全部勾选项(未勾选不动)",
|
|
27056
|
+
disabled: count === 0,
|
|
27057
|
+
onClick: () => onExecute(selected)
|
|
27058
|
+
}, "执行所选(" + count + " 项)"));
|
|
27059
|
+
} else {
|
|
27060
|
+
body = progressBodyEl(run);
|
|
27061
|
+
footer = react.default.createElement("div", { className: "edrv-svnplan-foot" }, react.default.createElement("span", { style: { flex: 1 } }), run.status === "done" ? react.default.createElement("button", {
|
|
27062
|
+
className: "edrv-svn-act edrv-svnplan-primary",
|
|
27063
|
+
title: "关闭弹窗并刷新变更列表",
|
|
27064
|
+
onClick: onClose
|
|
27065
|
+
}, "关闭并刷新") : react.default.createElement(react.default.Fragment, null, react.default.createElement("button", {
|
|
27066
|
+
className: "edrv-svn-act",
|
|
27067
|
+
title: run.status === "running" || run.status === "pausing" ? "暂停(当前步骤完成后生效)" : "从断点继续执行",
|
|
27068
|
+
disabled: run.status === "pausing" || run.status === "cancelling",
|
|
27069
|
+
onClick: run.status === "paused" ? onResume : onPause
|
|
27070
|
+
}, run.status === "paused" ? "继续" : "暂停"), react.default.createElement("button", {
|
|
27071
|
+
className: "edrv-svn-act edrv-svn-act-danger",
|
|
27072
|
+
title: "终止未执行步骤(已执行部分不回滚)",
|
|
27073
|
+
disabled: run.status === "cancelling",
|
|
27074
|
+
onClick: onCancelRest
|
|
27075
|
+
}, "取消剩余")));
|
|
27076
|
+
}
|
|
27077
|
+
return react.default.createElement(ModalShell, {
|
|
27078
|
+
key: "edrv-svn-plan",
|
|
27079
|
+
dialogClass: "edrv-svnplan-dialog",
|
|
27080
|
+
width: "min(680px, calc(100vw - 48px))",
|
|
27081
|
+
closeOnMask: !running,
|
|
27082
|
+
closeOnEsc: !running,
|
|
27083
|
+
onClose
|
|
27084
|
+
}, header, metaEl, body, footer);
|
|
27085
|
+
}
|
|
27086
|
+
//#endregion
|
|
27087
|
+
//#region src/client/sidebar/panels/svnListBudget.ts
|
|
27088
|
+
/**
|
|
27089
|
+
* 按 DOM 预算切分分组行(渐进渲染)。
|
|
27090
|
+
*
|
|
27091
|
+
* 预算跨组依序分配:预算内组整组保留(透传原对象,引用稳定利于行 memo),
|
|
27092
|
+
* 最后触及预算的组被截断,预算耗尽后不再产出**需要行数**的组;
|
|
27093
|
+
* 零行组(如折叠映射的空 entries 组)不占预算,恒产出组头。
|
|
27094
|
+
* 组头计数恒显全量(full),行只渲染预算内(entries),避免「截断组计数变小」的误导。
|
|
27095
|
+
* @author ddj 2026年09月23号
|
|
27096
|
+
* @param groups groupByChangelist 产出的分组
|
|
27097
|
+
* @param budget 允许渲染的条目行数上限
|
|
27098
|
+
* @returns 截断后的分组与计数
|
|
27099
|
+
*/
|
|
27100
|
+
function budgetGroupsOf(groups, budget) {
|
|
27101
|
+
const total = groups.reduce((sum, group) => sum + group.entries.length, 0);
|
|
27102
|
+
const limit = Math.max(0, budget);
|
|
27103
|
+
if (total <= limit) return {
|
|
27104
|
+
groups: [...groups],
|
|
27105
|
+
shown: total,
|
|
27106
|
+
total,
|
|
27107
|
+
hasMore: false
|
|
27108
|
+
};
|
|
27109
|
+
const sliced = [];
|
|
27110
|
+
let used = 0;
|
|
27111
|
+
for (const group of groups) {
|
|
27112
|
+
if (used >= limit && group.entries.length > 0) break;
|
|
27113
|
+
const take = Math.min(group.entries.length, limit - used);
|
|
27114
|
+
sliced.push(take >= group.entries.length ? group : {
|
|
27115
|
+
name: group.name,
|
|
27116
|
+
entries: group.entries.slice(0, take),
|
|
27117
|
+
full: group.entries.length
|
|
27118
|
+
});
|
|
27119
|
+
used += take;
|
|
27120
|
+
}
|
|
27121
|
+
return {
|
|
27122
|
+
groups: sliced,
|
|
27123
|
+
shown: used,
|
|
27124
|
+
total,
|
|
27125
|
+
hasMore: true
|
|
27126
|
+
};
|
|
27127
|
+
}
|
|
27128
|
+
//#endregion
|
|
25211
27129
|
//#region src/client/sidebar/panels/SvnPanel.ts
|
|
25212
27130
|
/**
|
|
25213
27131
|
* dsh-vscode-mode client — 侧边栏「SVN 变更」面板(工作副本状态视图)。
|
|
@@ -25227,6 +27145,91 @@ window.__ModuleLoader__.load({
|
|
|
25227
27145
|
/** 还原确认文案(新增文件不会被删除是 svn revert 的实际语义,须如实告知)。 */
|
|
25228
27146
|
const REVERT_CONFIRM_HEAD = "确认还原以下 ";
|
|
25229
27147
|
const REVERT_CONFIRM_TAIL = " 个文件的本地改动?新增(A)文件在磁盘上会保留,仅取消登记。";
|
|
27148
|
+
/** 列表渐进渲染步长(初始也按此值):数据量大时避免一次建满 DOM(面板随编辑区任意刷新重渲)。 */
|
|
27149
|
+
const RENDER_STEP = 500;
|
|
27150
|
+
/** 还原确认弹窗最多完整列出的路径数(超出折叠为「…等 N 个文件」,防大列表拼超长文案卡 UI)。 */
|
|
27151
|
+
const CONFIRM_PREVIEW_CAP = 50;
|
|
27152
|
+
/** 混合通道取件轮询间隔(毫秒)与次数上限(3s × 200 = 10 分钟无件自动停)。 */
|
|
27153
|
+
const DEEP_POLL_MS = 3e3;
|
|
27154
|
+
const DEEP_POLL_MAX = 200;
|
|
27155
|
+
/**
|
|
27156
|
+
* 勾选载荷 → 步骤队列(顺序固定 还原 → 分组 → 忽略;块/组/目录各一步,进度粒度对齐)。
|
|
27157
|
+
* @author ddj 2026年09月23号
|
|
27158
|
+
* @param selected 勾选载荷
|
|
27159
|
+
* @param sessionId 会话 id
|
|
27160
|
+
* @returns 步骤队列
|
|
27161
|
+
*/
|
|
27162
|
+
function planStepsOf(selected, sessionId) {
|
|
27163
|
+
const steps = [];
|
|
27164
|
+
const reverts = selected.reverts;
|
|
27165
|
+
let revDone = 0;
|
|
27166
|
+
for (const chunk of svnChunksOf(reverts, 64)) {
|
|
27167
|
+
revDone += chunk.length;
|
|
27168
|
+
steps.push({
|
|
27169
|
+
label: "还原 " + revDone + "/" + reverts.length + " 项",
|
|
27170
|
+
kind: "revert",
|
|
27171
|
+
n: chunk.length,
|
|
27172
|
+
run: () => svnRevert(sessionId, chunk)
|
|
27173
|
+
});
|
|
27174
|
+
}
|
|
27175
|
+
const groups = selected.groups;
|
|
27176
|
+
groups.forEach((group, index) => {
|
|
27177
|
+
let done = 0;
|
|
27178
|
+
for (const chunk of svnChunksOf(group.paths, 64)) {
|
|
27179
|
+
done += chunk.length;
|
|
27180
|
+
steps.push({
|
|
27181
|
+
label: "分组 " + (index + 1) + "/" + groups.length + " 组(" + group.name + ")" + done + "/" + group.paths.length + " 项",
|
|
27182
|
+
kind: "group",
|
|
27183
|
+
n: chunk.length,
|
|
27184
|
+
key: group.name,
|
|
27185
|
+
run: () => svnChangelist(sessionId, chunk, group.name)
|
|
27186
|
+
});
|
|
27187
|
+
}
|
|
27188
|
+
});
|
|
27189
|
+
const items = ignoreItemsOf(selected.ignores);
|
|
27190
|
+
items.forEach((item, index) => {
|
|
27191
|
+
steps.push({
|
|
27192
|
+
label: "忽略目录 " + (index + 1) + "/" + items.length + "(" + (item.dir || "工作副本根") + ")",
|
|
27193
|
+
kind: "ignore",
|
|
27194
|
+
n: item.names.length,
|
|
27195
|
+
run: () => svnIgnore(sessionId, [item])
|
|
27196
|
+
});
|
|
27197
|
+
});
|
|
27198
|
+
return steps;
|
|
27199
|
+
}
|
|
27200
|
+
/**
|
|
27201
|
+
* 逐步日志 → 汇总文案(分段计数 + 失败数与首条原因)。
|
|
27202
|
+
* @author ddj 2026年09月23号
|
|
27203
|
+
* @param steps 步骤队列(提供 kind/n/key)
|
|
27204
|
+
* @param log 逐步日志
|
|
27205
|
+
* @param cancelled 是否取消剩余
|
|
27206
|
+
* @returns 汇总文案
|
|
27207
|
+
*/
|
|
27208
|
+
function planSummaryOf(steps, log, cancelled) {
|
|
27209
|
+
const sum = {
|
|
27210
|
+
revert: 0,
|
|
27211
|
+
group: 0,
|
|
27212
|
+
ignore: 0,
|
|
27213
|
+
failed: 0,
|
|
27214
|
+
firstFail: ""
|
|
27215
|
+
};
|
|
27216
|
+
const groupNames = /* @__PURE__ */ new Set();
|
|
27217
|
+
log.forEach((entry, index) => {
|
|
27218
|
+
const step = steps[index];
|
|
27219
|
+
if (!step) return;
|
|
27220
|
+
if (entry.ok) {
|
|
27221
|
+
sum[step.kind] += step.n;
|
|
27222
|
+
if (step.kind === "group" && step.key) groupNames.add(step.key);
|
|
27223
|
+
} else {
|
|
27224
|
+
sum.failed++;
|
|
27225
|
+
if (!sum.firstFail) sum.firstFail = entry.text;
|
|
27226
|
+
}
|
|
27227
|
+
});
|
|
27228
|
+
let text = "AI 整理完成:还原 " + sum.revert + " · 分组 " + groupNames.size + " 组 " + sum.group + " 项 · 忽略 " + sum.ignore + " 项";
|
|
27229
|
+
if (sum.failed) text += ";失败 " + sum.failed + "(首条:" + sum.firstFail + ")";
|
|
27230
|
+
if (cancelled) text += ";已取消剩余步骤";
|
|
27231
|
+
return text;
|
|
27232
|
+
}
|
|
25230
27233
|
/**
|
|
25231
27234
|
* 读取面板显示开关(损坏/不可用安全)。
|
|
25232
27235
|
* @author ddj 2026年09月16号
|
|
@@ -25266,13 +27269,43 @@ window.__ModuleLoader__.load({
|
|
|
25266
27269
|
} catch (error) {}
|
|
25267
27270
|
}
|
|
25268
27271
|
/**
|
|
27272
|
+
* 读取分组折叠集合(损坏/缺省安全;键独立于显示开关,旧数据无键 = 全展开,向后兼容)。
|
|
27273
|
+
* @author ddj 2026年09月23号
|
|
27274
|
+
* @param scope 作用域键
|
|
27275
|
+
* @returns 折叠组名集合
|
|
27276
|
+
*/
|
|
27277
|
+
function loadFold(scope) {
|
|
27278
|
+
const empty = /* @__PURE__ */ new Set();
|
|
27279
|
+
if (!scope) return empty;
|
|
27280
|
+
try {
|
|
27281
|
+
const raw = window.localStorage.getItem(CACHE_KEY.svn + scope + "#fold");
|
|
27282
|
+
if (!raw) return empty;
|
|
27283
|
+
const parsed = JSON.parse(raw);
|
|
27284
|
+
return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
|
|
27285
|
+
} catch (error) {
|
|
27286
|
+
return empty;
|
|
27287
|
+
}
|
|
27288
|
+
}
|
|
27289
|
+
/**
|
|
27290
|
+
* 写分组折叠集合(配额/隐私模式失败静默)。
|
|
27291
|
+
* @author ddj 2026年09月23号
|
|
27292
|
+
* @param scope 作用域键
|
|
27293
|
+
* @param names 折叠组名集合
|
|
27294
|
+
*/
|
|
27295
|
+
function saveFold(scope, names) {
|
|
27296
|
+
if (!scope) return;
|
|
27297
|
+
try {
|
|
27298
|
+
window.localStorage.setItem(CACHE_KEY.svn + scope + "#fold", JSON.stringify([...names]));
|
|
27299
|
+
} catch (error) {}
|
|
27300
|
+
}
|
|
27301
|
+
/**
|
|
25269
27302
|
* 刷新按钮图标(官方 IconRefreshOutline16;原语缺失回落文本 ⟳)。
|
|
25270
27303
|
* @author ddj 2026年09月16号
|
|
25271
27304
|
* @returns 图标元素或回落文本
|
|
25272
27305
|
*/
|
|
25273
27306
|
function refreshIconEl() {
|
|
25274
|
-
if (typeof
|
|
25275
|
-
return react.default.createElement(
|
|
27307
|
+
if (typeof IconRefreshOutline16 !== "function") return "⟳";
|
|
27308
|
+
return react.default.createElement(IconRefreshOutline16, { size: 14 });
|
|
25276
27309
|
}
|
|
25277
27310
|
/**
|
|
25278
27311
|
* 条目状态字母元素(按 tone 着色)。
|
|
@@ -25311,18 +27344,22 @@ window.__ModuleLoader__.load({
|
|
|
25311
27344
|
}
|
|
25312
27345
|
/**
|
|
25313
27346
|
* 单条变更行(状态字母 + 路径 + 行内动作;双击 = 与基线比较)。
|
|
25314
|
-
*
|
|
27347
|
+
* React.memo:行 props(entry/pairNote/busy + 稳定回调)不变时跳过重渲染,
|
|
27348
|
+
* 面板因名称过滤/尺寸表等面板内状态重渲时,未受影响的行不再重建。
|
|
27349
|
+
* @author ddj 2026年09月16号 / 2026年09月20号 / 2026年09月23号
|
|
25315
27350
|
* @param props.entry 变更条目
|
|
25316
27351
|
* @param props.busy 批量动作进行中(按钮置灰)
|
|
25317
27352
|
* @param props.onDiff 打开基线差异
|
|
25318
27353
|
* @param props.onAdd 加入版本控制
|
|
25319
27354
|
* @param props.onRevert 还原
|
|
25320
27355
|
* @param props.onConflict 冲突副本对比(W2-4;仅冲突行出现按钮)
|
|
27356
|
+
* @param props.onMenu 行右键菜单(分区管理入口;参数为条目与视口坐标)
|
|
27357
|
+
* @param props.onOpen 单击跳转(编辑区打开/聚焦该文件)
|
|
25321
27358
|
* @param props.pairNote 疑似改名标记文案(W2-5;空 = 无配对)
|
|
25322
27359
|
* @returns 行元素
|
|
25323
27360
|
*/
|
|
25324
|
-
function SvnRow(props) {
|
|
25325
|
-
const { entry, busy, onDiff, onAdd, onRevert, onConflict, pairNote } = props;
|
|
27361
|
+
const SvnRow = react.default.memo(function SvnRow(props) {
|
|
27362
|
+
const { entry, busy, onDiff, onAdd, onRevert, onConflict, onMenu, onOpen, pairNote } = props;
|
|
25326
27363
|
const diffable = isSvnDiffable(entry.path, entry.status);
|
|
25327
27364
|
const act = (label, title, danger, run) => react.default.createElement("button", {
|
|
25328
27365
|
className: "edrv-svn-act" + (danger ? " edrv-svn-act-danger" : ""),
|
|
@@ -25336,19 +27373,25 @@ window.__ModuleLoader__.load({
|
|
|
25336
27373
|
return react.default.createElement("div", {
|
|
25337
27374
|
className: "edrv-svn-row",
|
|
25338
27375
|
title: entry.path,
|
|
27376
|
+
onClick: () => onOpen?.(entry.path),
|
|
25339
27377
|
onDoubleClick: () => {
|
|
25340
27378
|
if (diffable) onDiff(entry.path);
|
|
27379
|
+
},
|
|
27380
|
+
onContextMenu: (event) => {
|
|
27381
|
+
if (!onMenu) return;
|
|
27382
|
+
event.preventDefault();
|
|
27383
|
+
onMenu(entry, event.clientX, event.clientY);
|
|
25341
27384
|
}
|
|
25342
|
-
}, statusLetterEl(entry), react.default.createElement("span", { className: "edrv-svn-path" }, entry.path), pairNote ? react.default.createElement("span", {
|
|
27385
|
+
}, fileIconEl(String(entry.path ?? "").split(/[\\/]/).pop() || ""), statusLetterEl(entry), react.default.createElement("span", { className: "edrv-svn-path" }, entry.path), pairNote ? react.default.createElement("span", {
|
|
25343
27386
|
className: "edrv-svn-pair-mark",
|
|
25344
27387
|
title: pairNote
|
|
25345
27388
|
}, "⇄") : null, react.default.createElement("span", { className: "edrv-svn-acts" }, entry.status === "conflicted" ? act("冲突对比", "与 .mine/.rN 冲突副本并排对比(解决指引见差异视图顶部)", false, () => onConflict(entry.path)) : null, diffable ? act("比较", "与基线比较(BASE 与工作区并排)", false, () => onDiff(entry.path)) : null, !entry.versioned ? act("加入", SVN_ADD_LABEL, false, () => onAdd([entry.path])) : null, entry.versioned ? act("还原", "SVN 还原(放弃本地改动)", true, () => onRevert([entry.path])) : null));
|
|
25346
|
-
}
|
|
27389
|
+
});
|
|
25347
27390
|
/**
|
|
25348
|
-
* 列表主体:分组行 +
|
|
25349
|
-
* @author ddj 2026年09月16号
|
|
25350
|
-
* @param state 列表状态(managed/entries/visible/
|
|
25351
|
-
* @param handlers 行动作(onDiff/onAdd/onRevert/busy)
|
|
27391
|
+
* 列表主体:分组行 + 条目行(按预算渐进渲染);未受管理/加载中/无变更各自给出稳定文案。
|
|
27392
|
+
* @author ddj 2026年09月16号 / 2026年09月23号
|
|
27393
|
+
* @param state 列表状态(managed/entries/visible/budget/nameFiltered/capped/collapsedSet/onFold)
|
|
27394
|
+
* @param handlers 行动作(onDiff/onAdd/onRevert/onMore/onGroupClRemove/onMenu/busy/pairNotes)
|
|
25352
27395
|
* @returns 主体元素或元素数组
|
|
25353
27396
|
*/
|
|
25354
27397
|
function svnListBody(state, handlers) {
|
|
@@ -25356,20 +27399,70 @@ window.__ModuleLoader__.load({
|
|
|
25356
27399
|
if (state.entries === null) return react.default.createElement("div", { className: "edrv-tree-loading" }, "读取变更中…");
|
|
25357
27400
|
if (!state.visible.length) return react.default.createElement("div", { className: "edrv-tree-loading" }, state.nameFiltered ? "无匹配条目(名称过滤生效中,清空过滤框可看全部)" : "无变更(工作副本干净)");
|
|
25358
27401
|
const rows = [];
|
|
25359
|
-
for (const group of state.groups) {
|
|
25360
|
-
|
|
27402
|
+
for (const group of state.budget.groups) {
|
|
27403
|
+
rows.push(react.default.createElement("div", {
|
|
25361
27404
|
key: "g:" + group.name,
|
|
25362
|
-
className: "edrv-svn-group"
|
|
25363
|
-
|
|
27405
|
+
className: "edrv-svn-group",
|
|
27406
|
+
title: "点击折叠/展开该分组",
|
|
27407
|
+
onClick: () => state.onFold?.(group.name)
|
|
27408
|
+
}, react.default.createElement("span", { className: "edrv-svn-fold-ch" }, group.folded ? "▸" : "▾"), react.default.createElement("span", null, "changelist: " + group.name), group.name !== NO_CHANGELIST && handlers.onGroupClRemove ? react.default.createElement("button", {
|
|
27409
|
+
className: "edrv-svn-act",
|
|
27410
|
+
disabled: handlers.busy,
|
|
27411
|
+
title: "把该分区全部文件移出 changelist(不改文件内容;超过 64 项自动分块执行)",
|
|
27412
|
+
onClick: (event) => {
|
|
27413
|
+
event.stopPropagation();
|
|
27414
|
+
handlers.onGroupClRemove(group.name);
|
|
27415
|
+
}
|
|
27416
|
+
}, "移出分区") : null, react.default.createElement("span", { className: "edrv-svn-group-n" }, String(group.full ?? group.entries.length))));
|
|
27417
|
+
if (group.folded) continue;
|
|
25364
27418
|
for (const entry of group.entries) rows.push(react.default.createElement(SvnRow, Object.assign({
|
|
25365
27419
|
key: entry.path,
|
|
25366
27420
|
entry,
|
|
25367
27421
|
pairNote: handlers.pairNotes?.[entry.path]
|
|
25368
27422
|
}, handlers)));
|
|
25369
27423
|
}
|
|
27424
|
+
if (state.budget.hasMore) rows.push(react.default.createElement("button", {
|
|
27425
|
+
key: "edrv-svn-more",
|
|
27426
|
+
className: "edrv-svn-act edrv-svn-more",
|
|
27427
|
+
title: "继续渲染更多条目(每次 500 条;批量动作与导出始终按全量集合计算,不受渲染预算影响)",
|
|
27428
|
+
onClick: handlers.onMore
|
|
27429
|
+
}, "已显示 " + state.budget.shown + " / " + state.budget.total + " 项,显示更多"));
|
|
27430
|
+
if (state.capped) rows.push(react.default.createElement("div", {
|
|
27431
|
+
key: "edrv-svn-capped",
|
|
27432
|
+
className: "edrv-tree-loading"
|
|
27433
|
+
}, "变更条目过多,清单已按上限截断(列表/批量/导出仅含已载入部分);可用名称过滤缩小范围"));
|
|
25370
27434
|
return rows;
|
|
25371
27435
|
}
|
|
25372
27436
|
/**
|
|
27437
|
+
* AI 助手 dock(设计定稿 12-panel-ai-bar):accent 左竖轨 + 「AI 整理」标签 +
|
|
27438
|
+
* 快速分析/深度分析两按钮 + 定宽状态位。按钮文案恒定(忙态只换状态位文案,不跳版);
|
|
27439
|
+
* AI 线程色三处同轨(竖轨/按钮描边/弹窗进度条,--dsw-alias-state-info-primary)。
|
|
27440
|
+
* @author ddj 2026年09月23号
|
|
27441
|
+
* @param props.managed/忙碌标志(managed/busy/aiBusy/deepWaiting/hasRun)
|
|
27442
|
+
* @param props.onQuick 快速分析回调(host LLM 直调)
|
|
27443
|
+
* @param props.onDeep 深度分析回调(会话 agent 投递回面板)
|
|
27444
|
+
* @returns dock 元素
|
|
27445
|
+
*/
|
|
27446
|
+
function aiBarEl(props) {
|
|
27447
|
+
const { managed, busy, aiBusy, deepWaiting, hasRun, onQuick, onDeep } = props;
|
|
27448
|
+
const locked = !managed || busy || aiBusy || hasRun;
|
|
27449
|
+
const status = aiBusy ? "分析中…" : deepWaiting ? "等待助手投递…" : "就绪";
|
|
27450
|
+
return react.default.createElement("div", { className: "edrv-svn-ai-bar" }, react.default.createElement("span", {
|
|
27451
|
+
className: "edrv-svn-ai-rail",
|
|
27452
|
+
"aria-hidden": "true"
|
|
27453
|
+
}), react.default.createElement("span", { className: "edrv-svn-ai-label" }, "AI 整理"), react.default.createElement("button", {
|
|
27454
|
+
className: "edrv-svn-act edrv-svn-ai-btn",
|
|
27455
|
+
title: "快速分析:由模型直读变更与差异,产出分组/还原/忽略方案(分析后需勾选确认才执行)",
|
|
27456
|
+
disabled: locked,
|
|
27457
|
+
onClick: onQuick
|
|
27458
|
+
}, "快速分析"), react.default.createElement("button", {
|
|
27459
|
+
className: "edrv-svn-act edrv-svn-ai-btn",
|
|
27460
|
+
title: "深度分析:把分析任务填入对话,由 AI 助手借 codegraph/读文件深度分析后投递方案回本面板(执行仍需勾选确认)",
|
|
27461
|
+
disabled: locked || deepWaiting,
|
|
27462
|
+
onClick: onDeep
|
|
27463
|
+
}, "深度分析"), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("span", { className: "edrv-svn-ai-status" }, status));
|
|
27464
|
+
}
|
|
27465
|
+
/**
|
|
25373
27466
|
* 工具条(显示开关 + 名称过滤 + 批量动作 + 导出)。
|
|
25374
27467
|
* @author ddj 2026年09月16号 / 2026年09月20号
|
|
25375
27468
|
* @param props.filter 当前开关(含 W1-2 name)
|
|
@@ -25406,11 +27499,11 @@ window.__ModuleLoader__.load({
|
|
|
25406
27499
|
className: "edrv-svn-act",
|
|
25407
27500
|
title: "导出当前列表为 CSV(带 BOM,Excel 直开;状态/路径/changelist/修订)",
|
|
25408
27501
|
onClick: handlers.onExportCsv
|
|
25409
|
-
}, "导出CSV"), react.default.createElement("button", {
|
|
27502
|
+
}, "导出 CSV"), react.default.createElement("button", {
|
|
25410
27503
|
className: "edrv-svn-act",
|
|
25411
27504
|
title: "导出当前列表为 HTML 报表(仅本地查看,不外发)",
|
|
25412
27505
|
onClick: handlers.onExportHtml
|
|
25413
|
-
}, "导出HTML"), paths.unversioned.length ? react.default.createElement("button", {
|
|
27506
|
+
}, "导出 HTML"), paths.unversioned.length || paths.revert.length ? react.default.createElement("span", { className: "edrv-svn-sep" }) : null, paths.unversioned.length ? react.default.createElement("button", {
|
|
25414
27507
|
className: "edrv-svn-act",
|
|
25415
27508
|
disabled: handlers.busy,
|
|
25416
27509
|
title: "把全部未版本控制文件加入版本控制(不受名称过滤影响)",
|
|
@@ -25439,33 +27532,51 @@ window.__ModuleLoader__.load({
|
|
|
25439
27532
|
const [remote, setRemote] = react.default.useState(null);
|
|
25440
27533
|
const [remoteBusy, setRemoteBusy] = react.default.useState(false);
|
|
25441
27534
|
const [sizes, setSizes] = react.default.useState({});
|
|
27535
|
+
const [renderLimit, setRenderLimit] = react.default.useState(RENDER_STEP);
|
|
27536
|
+
const ctxRef = react.default.useRef(ctx);
|
|
27537
|
+
ctxRef.current = ctx;
|
|
27538
|
+
const [menu, setMenu] = react.default.useState(null);
|
|
27539
|
+
const [collapsed, setCollapsed] = react.default.useState(() => loadFold(scope));
|
|
27540
|
+
const [aiBusy, setAiBusy] = react.default.useState(false);
|
|
27541
|
+
const [aiPlan, setAiPlan] = react.default.useState(null);
|
|
27542
|
+
const [aiRun, setAiRun] = react.default.useState(null);
|
|
27543
|
+
const aiCtl = react.default.useRef({
|
|
27544
|
+
paused: false,
|
|
27545
|
+
cancelled: false,
|
|
27546
|
+
wake: null
|
|
27547
|
+
});
|
|
25442
27548
|
react.default.useEffect(() => {
|
|
25443
27549
|
setFilter(loadFilter(scope));
|
|
25444
27550
|
setRemote(null);
|
|
25445
27551
|
setSizes({});
|
|
27552
|
+
setRenderLimit(RENDER_STEP);
|
|
27553
|
+
setCollapsed(loadFold(scope));
|
|
25446
27554
|
}, [scope]);
|
|
25447
27555
|
const applyFilter = (next) => {
|
|
25448
27556
|
setFilter(next);
|
|
27557
|
+
setRenderLimit(RENDER_STEP);
|
|
25449
27558
|
saveFilter(scope, next);
|
|
25450
27559
|
};
|
|
25451
27560
|
/**
|
|
25452
27561
|
* 批量动作统一流程:先 confirm(仅还原需要)→ 执行 → 反馈 → 强制重查。
|
|
25453
|
-
*
|
|
27562
|
+
* 确认文案只完整列前 CONFIRM_PREVIEW_CAP 条(大列表拼几千行弹窗文案会卡 UI),总数如实展示。
|
|
27563
|
+
* @author ddj 2026年09月16号 / 2026年09月23号
|
|
25454
27564
|
* @param kind 'add' | 'revert'
|
|
25455
27565
|
* @param paths 目标相对路径列表
|
|
25456
27566
|
*/
|
|
25457
|
-
const runBatch = (kind, paths) => {
|
|
27567
|
+
const runBatch = react.default.useCallback((kind, paths) => {
|
|
25458
27568
|
if (!paths.length) return;
|
|
25459
27569
|
if (kind === "revert") {
|
|
27570
|
+
const preview = paths.length > CONFIRM_PREVIEW_CAP ? paths.slice(0, CONFIRM_PREVIEW_CAP).join("\n") + "\n…等 " + paths.length + " 个文件" : paths.join("\n");
|
|
25460
27571
|
const head = REVERT_CONFIRM_HEAD + paths.length + REVERT_CONFIRM_TAIL;
|
|
25461
|
-
if (!window.confirm(head + "\n\n" +
|
|
27572
|
+
if (!window.confirm(head + "\n\n" + preview)) return;
|
|
25462
27573
|
}
|
|
25463
27574
|
setBusy(true);
|
|
25464
27575
|
(kind === "revert" ? svnRevert(sessionId, paths) : svnAdd(sessionId, paths)).then((outcome) => {
|
|
25465
|
-
|
|
27576
|
+
ctxRef.current?.notify?.(outcome.message);
|
|
25466
27577
|
refreshSvnChanges(sessionId, scope);
|
|
25467
27578
|
}).finally(() => setBusy(false));
|
|
25468
|
-
};
|
|
27579
|
+
}, [sessionId, scope]);
|
|
25469
27580
|
/**
|
|
25470
27581
|
* 远端更新检查(W2-3;host 侧 15s 短超时,离线/超时降级为提示条文案,不阻塞面板)。
|
|
25471
27582
|
* @author ddj 2026年09月20号
|
|
@@ -25476,7 +27587,7 @@ window.__ModuleLoader__.load({
|
|
|
25476
27587
|
svnRemoteStatus(sessionId, "").then((outcome) => {
|
|
25477
27588
|
if (!outcome.ok) {
|
|
25478
27589
|
setRemote({ failed: true });
|
|
25479
|
-
|
|
27590
|
+
ctxRef.current?.notify?.(outcome.message);
|
|
25480
27591
|
return;
|
|
25481
27592
|
}
|
|
25482
27593
|
setRemote({
|
|
@@ -25488,62 +27599,384 @@ window.__ModuleLoader__.load({
|
|
|
25488
27599
|
/**
|
|
25489
27600
|
* 冲突副本对比入口(W2-4,只读):扫描 .mine/.working/.rN → 默认首个 .rN ↔ .mine 并排;
|
|
25490
27601
|
* resolve 指引在差异视图顶部展示,本入口绝不自动执行 svn resolve(归 P2 破坏性批次)。
|
|
25491
|
-
* @author ddj 2026年09月20号
|
|
27602
|
+
* @author ddj 2026年09月20号 / 2026年09月23号
|
|
25492
27603
|
* @param path 冲突文件的工作区相对路径
|
|
25493
27604
|
*/
|
|
25494
|
-
const openConflictDiff = (path) => {
|
|
27605
|
+
const openConflictDiff = react.default.useCallback((path) => {
|
|
25495
27606
|
svnConflictArtifacts(sessionId, path).then((outcome) => {
|
|
27607
|
+
const live = ctxRef.current;
|
|
25496
27608
|
if (!outcome.ok) {
|
|
25497
|
-
|
|
27609
|
+
live?.notify?.(outcome.message);
|
|
25498
27610
|
return;
|
|
25499
27611
|
}
|
|
25500
27612
|
const artifacts = outcome.artifacts ?? [];
|
|
25501
27613
|
const revSide = artifacts.find((item) => item.kind === "rev");
|
|
25502
27614
|
const mineSide = artifacts.find((item) => item.kind === "mine");
|
|
25503
27615
|
if (!revSide || !mineSide) {
|
|
25504
|
-
|
|
27616
|
+
live?.notify?.("未发现冲突副本(.mine/.rN):" + artifacts.map((item) => item.name).join("、"));
|
|
25505
27617
|
return;
|
|
25506
27618
|
}
|
|
25507
|
-
if (!
|
|
25508
|
-
|
|
27619
|
+
if (!live?.openSvnLocalPair) {
|
|
27620
|
+
live?.notify?.("当前视图不支持冲突对比");
|
|
25509
27621
|
return;
|
|
25510
27622
|
}
|
|
25511
|
-
|
|
27623
|
+
live.openSvnLocalPair(revSide.path, mineSide.path, ".r" + (revSide.rev ?? "?"), ".mine", path);
|
|
25512
27624
|
});
|
|
25513
|
-
};
|
|
25514
|
-
const shown = entries === null ? [] : svnVisibleChanges(entries, filter);
|
|
25515
|
-
const actionable = entries === null ? [] : svnVisibleChanges(entries, {
|
|
27625
|
+
}, [sessionId]);
|
|
27626
|
+
const shown = react.default.useMemo(() => entries === null ? [] : svnVisibleChanges(entries, filter), [entries, filter]);
|
|
27627
|
+
const actionable = react.default.useMemo(() => entries === null ? [] : svnVisibleChanges(entries, {
|
|
25516
27628
|
unversioned: filter.unversioned,
|
|
25517
27629
|
ignored: filter.ignored
|
|
25518
|
-
});
|
|
27630
|
+
}), [entries, filter]);
|
|
25519
27631
|
const nameFiltered = String(filter.name || "").trim() !== "";
|
|
25520
|
-
const missingEntries =
|
|
25521
|
-
const unversionedEntries =
|
|
25522
|
-
const
|
|
25523
|
-
const
|
|
27632
|
+
const missingEntries = react.default.useMemo(() => (entries ?? []).filter((entry) => entry.status === "missing"), [entries]);
|
|
27633
|
+
const unversionedEntries = react.default.useMemo(() => (entries ?? []).filter((entry) => entry.status === "unversioned"), [entries]);
|
|
27634
|
+
const requested = react.default.useMemo(() => [...missingEntries, ...unversionedEntries].slice(0, 200).map((entry) => entry.path), [missingEntries, unversionedEntries]);
|
|
27635
|
+
const candidateKey = react.default.useMemo(() => requested.join("\n"), [requested]);
|
|
27636
|
+
const pairsNeeded = missingEntries.length > 0 && unversionedEntries.length > 0;
|
|
27637
|
+
const pairsKnown = pairsNeeded && requested.every((path) => path in sizes);
|
|
25524
27638
|
react.default.useEffect(() => {
|
|
25525
|
-
if (
|
|
27639
|
+
if (pairsKnown || !requested.length) return;
|
|
27640
|
+
svnFileSizes(sessionId, requested).then((table) => setSizes(table));
|
|
25526
27641
|
}, [
|
|
25527
27642
|
pairsKnown,
|
|
25528
|
-
|
|
27643
|
+
candidateKey,
|
|
25529
27644
|
sessionId
|
|
25530
27645
|
]);
|
|
25531
|
-
const pairNoteOf =
|
|
27646
|
+
const pairNoteOf = react.default.useMemo(() => {
|
|
27647
|
+
if (!pairsKnown) return {};
|
|
25532
27648
|
const notes = {};
|
|
25533
27649
|
for (const pair of pairMissingWithUnversioned(entries ?? [], sizes)) {
|
|
25534
27650
|
notes[pair.missingPath] = "疑似改名:原文件 → " + pair.unversionedPath;
|
|
25535
27651
|
notes[pair.unversionedPath] = "疑似改名:← 原文件 " + pair.missingPath;
|
|
25536
27652
|
}
|
|
25537
27653
|
return notes;
|
|
25538
|
-
}
|
|
25539
|
-
|
|
27654
|
+
}, [
|
|
27655
|
+
pairsKnown,
|
|
27656
|
+
entries,
|
|
27657
|
+
sizes
|
|
27658
|
+
]);
|
|
27659
|
+
const pairCapHint = pairsNeeded && missingEntries.length + unversionedEntries.length > 200;
|
|
27660
|
+
const versionedPathSet = react.default.useMemo(() => {
|
|
27661
|
+
const set = /* @__PURE__ */ new Set();
|
|
27662
|
+
for (const entry of entries ?? []) if (entry.versioned) set.add(entry.path);
|
|
27663
|
+
return set;
|
|
27664
|
+
}, [entries]);
|
|
27665
|
+
/**
|
|
27666
|
+
* 分区操作统一流程:busy → 按 host 上限分块顺序执行 → 聚合反馈 → 强制重查。
|
|
27667
|
+
* 反馈计数以面板层 paths.length 为准(svn changelist 成功输出静默,host count 为 0)。
|
|
27668
|
+
* @author ddj 2026年09月23号
|
|
27669
|
+
* @param paths 目标相对路径列表
|
|
27670
|
+
* @param name 目标分区名;null = 移出分区
|
|
27671
|
+
*/
|
|
27672
|
+
const runCl = react.default.useCallback(async (paths, name) => {
|
|
27673
|
+
if (!paths.length) return;
|
|
27674
|
+
setBusy(true);
|
|
27675
|
+
const failures = [];
|
|
27676
|
+
let done = 0;
|
|
27677
|
+
try {
|
|
27678
|
+
for (const chunk of svnChunksOf(paths, 64)) {
|
|
27679
|
+
const outcome = await svnChangelist(sessionId, chunk, name);
|
|
27680
|
+
if (outcome.ok) done += chunk.length;
|
|
27681
|
+
else failures.push(outcome.message);
|
|
27682
|
+
}
|
|
27683
|
+
} finally {
|
|
27684
|
+
setBusy(false);
|
|
27685
|
+
}
|
|
27686
|
+
const total = paths.length;
|
|
27687
|
+
if (!failures.length) ctxRef.current?.notify?.(name === null ? "已移出分区(" + total + " 项)" : "已移入分区「" + name + "」(" + total + " 项)");
|
|
27688
|
+
else ctxRef.current?.notify?.("分区完成 " + done + "/" + total + " 项;" + failures[0]);
|
|
27689
|
+
refreshSvnChanges(sessionId, scope);
|
|
27690
|
+
}, [sessionId, scope]);
|
|
27691
|
+
/**
|
|
27692
|
+
* 把单条条目移入分区:弹窗输入分区名(预填当前名,可直接改名)→ 共享校验 → 执行。
|
|
27693
|
+
* @author ddj 2026年09月23号
|
|
27694
|
+
* @param entry 变更条目
|
|
27695
|
+
*/
|
|
27696
|
+
const moveToChangelist = async (entry) => {
|
|
27697
|
+
const name = await ctxRef.current?.prompt?.("移入 SVN 分区(changelist)", entry.changelist || "");
|
|
27698
|
+
if (!name) return;
|
|
27699
|
+
const nameError = svnChangelistNameErrorOf(name);
|
|
27700
|
+
if (nameError) {
|
|
27701
|
+
ctxRef.current?.notify?.(nameError);
|
|
27702
|
+
return;
|
|
27703
|
+
}
|
|
27704
|
+
await runCl([entry.path], name);
|
|
27705
|
+
};
|
|
27706
|
+
/**
|
|
27707
|
+
* 整组移出分区:取该分区全部条目(不受名称过滤/显示开关影响,与批量动作口径一致)。
|
|
27708
|
+
* @author ddj 2026年09月23号
|
|
27709
|
+
* @param groupName 分区名
|
|
27710
|
+
*/
|
|
27711
|
+
const removeGroupCl = react.default.useCallback(async (groupName) => {
|
|
27712
|
+
const paths = (entries ?? []).filter((entry) => entry.changelist === groupName).map((entry) => entry.path);
|
|
27713
|
+
await runCl(paths, null);
|
|
27714
|
+
}, [entries, runCl]);
|
|
27715
|
+
const openRowMenu = react.default.useCallback((entry, x, y) => setMenu({
|
|
27716
|
+
entry,
|
|
27717
|
+
x,
|
|
27718
|
+
y
|
|
27719
|
+
}), []);
|
|
27720
|
+
const closeMenu = react.default.useCallback(() => setMenu(null), []);
|
|
27721
|
+
/**
|
|
27722
|
+
* AI 智能整理分析入口:host 一次性 LLM 分析(超时 AI_PLAN_TIMEOUT_MS=300s)→ 预览弹窗;失败 notify。
|
|
27723
|
+
* @author ddj 2026年09月23号
|
|
27724
|
+
*/
|
|
27725
|
+
const runAiPlan = react.default.useCallback(() => {
|
|
27726
|
+
if (aiBusy || aiRun) return;
|
|
27727
|
+
if (!entries || !entries.length) {
|
|
27728
|
+
ctxRef.current?.notify?.("无变更可分析");
|
|
27729
|
+
return;
|
|
27730
|
+
}
|
|
27731
|
+
setAiBusy(true);
|
|
27732
|
+
svnAiPlan(sessionId).then((outcome) => {
|
|
27733
|
+
if (!outcome.ok || !outcome.plan) {
|
|
27734
|
+
ctxRef.current?.notify?.(outcome.message);
|
|
27735
|
+
return;
|
|
27736
|
+
}
|
|
27737
|
+
setAiPlan(outcome);
|
|
27738
|
+
}).finally(() => setAiBusy(false));
|
|
27739
|
+
}, [
|
|
27740
|
+
aiBusy,
|
|
27741
|
+
aiRun,
|
|
27742
|
+
entries,
|
|
27743
|
+
sessionId
|
|
27744
|
+
]);
|
|
27745
|
+
/** 暂停(步骤边界生效;当前 in-flight RPC 不可中断,UI 标注「等待当前步骤完成…」)。 */
|
|
27746
|
+
const aiPause = react.default.useCallback(() => {
|
|
27747
|
+
aiCtl.current.paused = true;
|
|
27748
|
+
setAiRun((old) => old ? {
|
|
27749
|
+
...old,
|
|
27750
|
+
status: "pausing"
|
|
27751
|
+
} : old);
|
|
27752
|
+
}, []);
|
|
27753
|
+
/** 继续(唤醒步骤边界等待,从断点续跑)。 */
|
|
27754
|
+
const aiResume = react.default.useCallback(() => {
|
|
27755
|
+
aiCtl.current.paused = false;
|
|
27756
|
+
const wake = aiCtl.current.wake;
|
|
27757
|
+
aiCtl.current.wake = null;
|
|
27758
|
+
if (wake) wake();
|
|
27759
|
+
setAiRun((old) => old ? {
|
|
27760
|
+
...old,
|
|
27761
|
+
status: "running"
|
|
27762
|
+
} : old);
|
|
27763
|
+
}, []);
|
|
27764
|
+
/** 取消剩余(已执行不回滚;唤醒边界等待让引擎立刻收尾)。 */
|
|
27765
|
+
const aiCancelRest = react.default.useCallback(() => {
|
|
27766
|
+
aiCtl.current.cancelled = true;
|
|
27767
|
+
aiCtl.current.paused = false;
|
|
27768
|
+
const wake = aiCtl.current.wake;
|
|
27769
|
+
aiCtl.current.wake = null;
|
|
27770
|
+
if (wake) wake();
|
|
27771
|
+
setAiRun((old) => old ? {
|
|
27772
|
+
...old,
|
|
27773
|
+
status: "cancelling"
|
|
27774
|
+
} : old);
|
|
27775
|
+
}, []);
|
|
27776
|
+
/**
|
|
27777
|
+
* 预览确认 → 启动可暂停执行引擎(还原→分组→忽略固定顺序;单步失败继续)。
|
|
27778
|
+
* @author ddj 2026年09月23号
|
|
27779
|
+
* @param selected 勾选载荷
|
|
27780
|
+
*/
|
|
27781
|
+
const onAiExecute = react.default.useCallback(async (selected) => {
|
|
27782
|
+
const live = ctxRef.current;
|
|
27783
|
+
const steps = planStepsOf(selected, sessionId);
|
|
27784
|
+
if (!steps.length) return;
|
|
27785
|
+
aiCtl.current = {
|
|
27786
|
+
paused: false,
|
|
27787
|
+
cancelled: false,
|
|
27788
|
+
wake: null
|
|
27789
|
+
};
|
|
27790
|
+
setAiRun({
|
|
27791
|
+
status: "running",
|
|
27792
|
+
index: 0,
|
|
27793
|
+
total: steps.length,
|
|
27794
|
+
cur: steps[0].label,
|
|
27795
|
+
log: [],
|
|
27796
|
+
cancelled: false
|
|
27797
|
+
});
|
|
27798
|
+
const result = await runPlanSteps(steps, {
|
|
27799
|
+
paused: () => aiCtl.current.paused,
|
|
27800
|
+
cancelled: () => aiCtl.current.cancelled,
|
|
27801
|
+
/** 步骤边界等待:paused 且未 cancelled 时挂起,由 resume/cancel 唤醒。 */
|
|
27802
|
+
wait: () => aiCtl.current.paused && !aiCtl.current.cancelled ? new Promise((resolve) => {
|
|
27803
|
+
aiCtl.current.wake = () => resolve(void 0);
|
|
27804
|
+
}) : Promise.resolve(void 0)
|
|
27805
|
+
}, (tick) => {
|
|
27806
|
+
setAiRun((old) => old ? {
|
|
27807
|
+
...old,
|
|
27808
|
+
index: tick.done,
|
|
27809
|
+
total: tick.total,
|
|
27810
|
+
cur: tick.cur,
|
|
27811
|
+
log: tick.log,
|
|
27812
|
+
status: aiCtl.current.cancelled ? "cancelling" : old.status
|
|
27813
|
+
} : old);
|
|
27814
|
+
});
|
|
27815
|
+
setAiRun((old) => old ? {
|
|
27816
|
+
...old,
|
|
27817
|
+
status: "done",
|
|
27818
|
+
cur: "",
|
|
27819
|
+
cancelled: result.cancelled
|
|
27820
|
+
} : old);
|
|
27821
|
+
live?.notify?.(planSummaryOf(steps, result.log, result.cancelled));
|
|
27822
|
+
refreshSvnChanges(sessionId, scope);
|
|
27823
|
+
}, [sessionId, scope]);
|
|
27824
|
+
/** 关闭整理弹窗(「关闭并刷新」;预览阶段 = 取消零副作用)。 */
|
|
27825
|
+
const closeAiPlan = react.default.useCallback(() => {
|
|
27826
|
+
setAiPlan(null);
|
|
27827
|
+
setAiRun(null);
|
|
27828
|
+
refreshSvnChanges(sessionId, scope);
|
|
27829
|
+
}, [sessionId, scope]);
|
|
27830
|
+
/** 取件轮询句柄(null = 未在轮询;卸载/取到/超时清理)。 */
|
|
27831
|
+
const deepTimer = react.default.useRef(null);
|
|
27832
|
+
const [deepWaiting, setDeepWaiting] = react.default.useState(false);
|
|
27833
|
+
/** 停止取件轮询(幂等)。 */
|
|
27834
|
+
const stopDeepPoll = react.default.useCallback(() => {
|
|
27835
|
+
if (deepTimer.current !== null) {
|
|
27836
|
+
clearInterval(deepTimer.current);
|
|
27837
|
+
deepTimer.current = null;
|
|
27838
|
+
}
|
|
27839
|
+
setDeepWaiting(false);
|
|
27840
|
+
}, []);
|
|
27841
|
+
react.default.useEffect(() => () => {
|
|
27842
|
+
if (deepTimer.current !== null) clearInterval(deepTimer.current);
|
|
27843
|
+
}, []);
|
|
27844
|
+
/**
|
|
27845
|
+
* 开启取件轮询:每 DEEP_POLL_MS 取一次收件箱,取到方案即开预览弹窗并停轮询;
|
|
27846
|
+
* DEEP_POLL_MAX 次无件自动停并提示(agent 可能没投递)。
|
|
27847
|
+
* @author ddj 2026年09月23号
|
|
27848
|
+
* @param since 注入时刻时间戳(旧投递不算新件)
|
|
27849
|
+
*/
|
|
27850
|
+
const startDeepPoll = react.default.useCallback((since) => {
|
|
27851
|
+
stopDeepPoll();
|
|
27852
|
+
setDeepWaiting(true);
|
|
27853
|
+
let ticks = 0;
|
|
27854
|
+
deepTimer.current = setInterval(() => {
|
|
27855
|
+
ticks += 1;
|
|
27856
|
+
if (ticks > DEEP_POLL_MAX) {
|
|
27857
|
+
stopDeepPoll();
|
|
27858
|
+
ctxRef.current?.notify?.("等待 AI 助手方案超时(10 分钟),已停止取件");
|
|
27859
|
+
return;
|
|
27860
|
+
}
|
|
27861
|
+
svnAiPlanPending(sessionId, since).then((outcome) => {
|
|
27862
|
+
if (!outcome.ok || !outcome.plan) return;
|
|
27863
|
+
stopDeepPoll();
|
|
27864
|
+
setAiPlan({
|
|
27865
|
+
plan: outcome.plan,
|
|
27866
|
+
entriesCount: (ctxRef.current?.svnChanges ?? []).length,
|
|
27867
|
+
diffIncluded: true,
|
|
27868
|
+
dropped: outcome.dropped ?? 0,
|
|
27869
|
+
model: void 0,
|
|
27870
|
+
source: "agent"
|
|
27871
|
+
});
|
|
27872
|
+
ctxRef.current?.notify?.("AI 助手方案已送达,请勾选确认后执行");
|
|
27873
|
+
});
|
|
27874
|
+
}, DEEP_POLL_MS);
|
|
27875
|
+
}, [sessionId, stopDeepPoll]);
|
|
27876
|
+
/**
|
|
27877
|
+
* 深度分析入口(02-deep-session-prompt 修正版):**新建独立会话**承载任务
|
|
27878
|
+
* (不污染当前对话),草稿箱填入分析任务(变更清单由 agent 自跑只读 svn status
|
|
27879
|
+
* 取数,prompt 定长)→ 用户在新会话发送 → agent 投递方案回面板收件箱。
|
|
27880
|
+
* 降级:不支持自动新建会话时回落当前对话注入 + notify 注明。
|
|
27881
|
+
* @author ddj 2026年09月23号
|
|
27882
|
+
*/
|
|
27883
|
+
const runDeepPlan = react.default.useCallback(() => {
|
|
27884
|
+
const live = ctxRef.current;
|
|
27885
|
+
if (aiBusy || aiRun || deepWaiting) return;
|
|
27886
|
+
if (!entries || !entries.length) {
|
|
27887
|
+
live?.notify?.("无变更可分析");
|
|
27888
|
+
return;
|
|
27889
|
+
}
|
|
27890
|
+
const wcRoot = live?.svn?.wcRoot ? String(live.svn.wcRoot) : "";
|
|
27891
|
+
const since = Date.now();
|
|
27892
|
+
const add = live?.addToConversation;
|
|
27893
|
+
if (!add?.appendText) {
|
|
27894
|
+
live?.notify?.("无法填入任务(无会话或输入框不可用)");
|
|
27895
|
+
return;
|
|
27896
|
+
}
|
|
27897
|
+
/** 降级:当前对话注入(不支持自动新建会话时兜底)。 */
|
|
27898
|
+
const degrade = (prompt) => {
|
|
27899
|
+
add.appendText(sessionId, prompt).then((outcome) => {
|
|
27900
|
+
if (outcome === "ok" || outcome === "busy") {
|
|
27901
|
+
live?.notify?.("已填入当前对话(降级),任务已自动发送,请在本对话查看");
|
|
27902
|
+
startDeepPoll(since);
|
|
27903
|
+
} else live?.notify?.("发起失败:" + (outcome === "unavailable" ? "无会话或输入框不可用" : "输入框忙,请重试"));
|
|
27904
|
+
});
|
|
27905
|
+
};
|
|
27906
|
+
/** 统一收尾(点击即发送方案):create 后构建 prompt(内嵌新会话 id)→ 发送任务。 */
|
|
27907
|
+
const sendAndWatch = (id) => {
|
|
27908
|
+
const prompt = buildAgentPrompt(wcRoot, id || sessionId || "");
|
|
27909
|
+
if (!id || typeof add.sendTask !== "function") {
|
|
27910
|
+
degrade(prompt);
|
|
27911
|
+
return;
|
|
27912
|
+
}
|
|
27913
|
+
add.sendTask(id, prompt).then((sent) => {
|
|
27914
|
+
if (sent) {
|
|
27915
|
+
live?.notify?.("已在新会话发起深度分析(任务已自动发送),请在会话列表查看");
|
|
27916
|
+
startDeepPoll(since);
|
|
27917
|
+
} else degrade(prompt);
|
|
27918
|
+
});
|
|
27919
|
+
};
|
|
27920
|
+
(add.startDraftSession ? add.startDraftSession(wcRoot || void 0) : Promise.resolve({ ok: false })).then((result) => {
|
|
27921
|
+
sendAndWatch(result?.ok ? result.id : void 0);
|
|
27922
|
+
});
|
|
27923
|
+
}, [
|
|
27924
|
+
aiBusy,
|
|
27925
|
+
aiRun,
|
|
27926
|
+
deepWaiting,
|
|
27927
|
+
entries,
|
|
27928
|
+
sessionId,
|
|
27929
|
+
startDeepPoll
|
|
27930
|
+
]);
|
|
27931
|
+
/**
|
|
27932
|
+
* 切换分组折叠(纯 UI 状态;不重置渲染预算——折叠释放预算、展开继续用已有预算)。
|
|
27933
|
+
* @author ddj 2026年09月23号
|
|
27934
|
+
* @param name 组名
|
|
27935
|
+
*/
|
|
27936
|
+
const toggleFold = react.default.useCallback((name) => {
|
|
27937
|
+
const next = new Set(collapsed);
|
|
27938
|
+
if (next.has(name)) next.delete(name);
|
|
27939
|
+
else next.add(name);
|
|
27940
|
+
setCollapsed(next);
|
|
27941
|
+
saveFold(scope, next);
|
|
27942
|
+
}, [scope, collapsed]);
|
|
27943
|
+
/**
|
|
27944
|
+
* 行右键菜单条目:分区管理(svn changelist 仅支持受版本控制文件,
|
|
27945
|
+
* 未版本控制/忽略项禁用;目录条目的报错由 host 经 notify 透出)。
|
|
27946
|
+
* @author ddj 2026年09月23号
|
|
27947
|
+
* @param entry 变更条目
|
|
27948
|
+
* @returns 菜单条目
|
|
27949
|
+
*/
|
|
27950
|
+
const rowMenuEntriesOf = (entry) => [{
|
|
27951
|
+
id: "cl-set",
|
|
27952
|
+
label: entry.changelist ? "移到其他分区…" : "移入分区…",
|
|
27953
|
+
disabled: !entry.versioned || busy,
|
|
27954
|
+
onClick: () => {
|
|
27955
|
+
moveToChangelist(entry);
|
|
27956
|
+
}
|
|
27957
|
+
}, {
|
|
27958
|
+
id: "cl-remove",
|
|
27959
|
+
label: "移出分区" + (entry.changelist ? "「" + entry.changelist + "」" : ""),
|
|
27960
|
+
disabled: !entry.versioned || !entry.changelist || busy,
|
|
27961
|
+
onClick: () => {
|
|
27962
|
+
runCl([entry.path], null);
|
|
27963
|
+
}
|
|
27964
|
+
}];
|
|
27965
|
+
const openSvnDiff = react.default.useCallback((p) => ctxRef.current?.openSvnDiff?.(p), []);
|
|
27966
|
+
const openFileAt = react.default.useCallback((p) => ctxRef.current?.openFile?.(p), []);
|
|
27967
|
+
const onAdd = react.default.useCallback((paths) => runBatch("add", paths), [runBatch]);
|
|
27968
|
+
const onRevert = react.default.useCallback((paths) => runBatch("revert", paths), [runBatch]);
|
|
25540
27969
|
const handlers = {
|
|
25541
27970
|
busy,
|
|
25542
27971
|
onDiff: openSvnDiff,
|
|
25543
27972
|
onConflict: openConflictDiff,
|
|
25544
27973
|
pairNotes: pairNoteOf,
|
|
25545
|
-
onAdd
|
|
25546
|
-
onRevert
|
|
27974
|
+
onAdd,
|
|
27975
|
+
onRevert,
|
|
27976
|
+
onMore: react.default.useCallback(() => setRenderLimit((old) => old + RENDER_STEP), []),
|
|
27977
|
+
onMenu: openRowMenu,
|
|
27978
|
+
onGroupClRemove: removeGroupCl,
|
|
27979
|
+
onOpen: openFileAt
|
|
25547
27980
|
};
|
|
25548
27981
|
/**
|
|
25549
27982
|
* 导出当前展示列表(W1-4):CSV 带 BOM 供 Excel 直开;HTML 本地报表;失败经 notify 提示。
|
|
@@ -25566,21 +27999,35 @@ window.__ModuleLoader__.load({
|
|
|
25566
27999
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
25567
28000
|
if (!(kind === "html" ? downloadText("svn-changes-" + stamp + ".html", htmlTableOf(headers, rows, "SVN 变更列表 " + stamp), "text/html") : downloadText("svn-changes-" + stamp + ".csv", csvOf([headers, ...rows]), "text/csv"))) ctx?.notify?.("导出失败:浏览器下载能力不可用");
|
|
25568
28001
|
};
|
|
28002
|
+
const groups = react.default.useMemo(() => groupByChangelist(shown), [shown]);
|
|
25569
28003
|
const listState = {
|
|
25570
28004
|
managed,
|
|
25571
28005
|
entries,
|
|
25572
28006
|
visible: shown,
|
|
25573
|
-
|
|
25574
|
-
|
|
25575
|
-
|
|
28007
|
+
budget: react.default.useMemo(() => budgetGroupsOf(groups.map((g) => collapsed.has(g.name) ? {
|
|
28008
|
+
name: g.name,
|
|
28009
|
+
entries: [],
|
|
28010
|
+
full: g.entries.length,
|
|
28011
|
+
folded: true
|
|
28012
|
+
} : g), renderLimit), [
|
|
28013
|
+
groups,
|
|
28014
|
+
collapsed,
|
|
28015
|
+
renderLimit
|
|
28016
|
+
]),
|
|
28017
|
+
nameFiltered,
|
|
28018
|
+
capped: svnChangesCapped(scope),
|
|
28019
|
+
collapsedSet: collapsed,
|
|
28020
|
+
onFold: toggleFold
|
|
28021
|
+
};
|
|
28022
|
+
const bulkPaths = react.default.useMemo(() => ({
|
|
28023
|
+
unversioned: actionable.filter((entry) => !entry.versioned).map((entry) => entry.path),
|
|
28024
|
+
revert: actionable.filter((entry) => entry.versioned).map((entry) => entry.path)
|
|
28025
|
+
}), [actionable]);
|
|
25576
28026
|
const toolbar = svnToolbarEl({
|
|
25577
28027
|
filter,
|
|
25578
28028
|
count: shown.length,
|
|
25579
28029
|
countAll: actionable.length,
|
|
25580
|
-
paths:
|
|
25581
|
-
unversioned: actionable.filter((entry) => !entry.versioned).map((entry) => entry.path),
|
|
25582
|
-
revert: actionable.filter((entry) => entry.versioned).map((entry) => entry.path)
|
|
25583
|
-
},
|
|
28030
|
+
paths: bulkPaths,
|
|
25584
28031
|
handlers: Object.assign({}, handlers, {
|
|
25585
28032
|
toggleUnversioned: () => applyFilter({
|
|
25586
28033
|
...filter,
|
|
@@ -25596,20 +28043,45 @@ window.__ModuleLoader__.load({
|
|
|
25596
28043
|
}),
|
|
25597
28044
|
onExportCsv: () => onExport("csv"),
|
|
25598
28045
|
onExportHtml: () => onExport("html"),
|
|
25599
|
-
add:
|
|
25600
|
-
revert:
|
|
28046
|
+
add: onAdd,
|
|
28047
|
+
revert: onRevert
|
|
25601
28048
|
})
|
|
25602
28049
|
});
|
|
25603
28050
|
const rootName = ctx?.svn?.wcRoot ? String(ctx.svn.wcRoot).split(/[\\/]/).pop() || ctx.svn.wcRoot : "";
|
|
25604
|
-
const remoteEl = remote === null ? null : react.default.createElement("div", { className: "edrv-svn-hintbar" + (remote.failed ? " edrv-svn-hintbar-warn" : "") }, react.default.createElement("span", { style: { flex: 1 } }, remote.failed ? "远端检查失败(网络不可达或超时;可用 ⟳ 或「检查远端」重试)" : remote.outdated.length ? "远端有 " + remote.outdated.length + " 个更新(against r" + (remote.againstRev ?? "?") + "),其中本地已改 " + remote.outdated.filter((item) => item.path &&
|
|
28051
|
+
const remoteEl = remote === null ? null : react.default.createElement("div", { className: "edrv-svn-hintbar" + (remote.failed ? " edrv-svn-hintbar-warn" : "") }, react.default.createElement("span", { style: { flex: 1 } }, remote.failed ? "远端检查失败(网络不可达或超时;可用 ⟳ 或「检查远端」重试)" : remote.outdated.length ? "远端有 " + remote.outdated.length + " 个更新(against r" + (remote.againstRev ?? "?") + "),其中本地已改 " + remote.outdated.filter((item) => item.path && versionedPathSet.has(item.path)).length + " 个;更新前请先提交或还原" : "远端无更新(against r" + (remote.againstRev ?? "?") + ")"), react.default.createElement("button", {
|
|
25605
28052
|
className: "edrv-svn-act",
|
|
25606
28053
|
title: "关闭提示条",
|
|
25607
28054
|
onClick: () => setRemote(null)
|
|
25608
28055
|
}, "✕"));
|
|
28056
|
+
const pairCapEl = pairCapHint ? react.default.createElement("div", { className: "edrv-svn-hintbar" }, "改名配对候选超过 200 个,仅评估前 200 个(缺失文件优先)") : null;
|
|
28057
|
+
const menuEl = menu ? react.default.createElement(ContextMenu, {
|
|
28058
|
+
key: "edrv-svn-row-menu",
|
|
28059
|
+
x: menu.x,
|
|
28060
|
+
y: menu.y,
|
|
28061
|
+
entries: rowMenuEntriesOf(menu.entry),
|
|
28062
|
+
onClose: closeMenu
|
|
28063
|
+
}) : null;
|
|
28064
|
+
const aiPlanEl = aiPlan ? react.default.createElement(SvnAiPlanDialog, {
|
|
28065
|
+
key: "edrv-svn-ai-plan",
|
|
28066
|
+
plan: aiPlan.plan,
|
|
28067
|
+
meta: {
|
|
28068
|
+
entriesCount: aiPlan.entriesCount,
|
|
28069
|
+
diffIncluded: aiPlan.diffIncluded,
|
|
28070
|
+
dropped: aiPlan.dropped,
|
|
28071
|
+
model: aiPlan.model,
|
|
28072
|
+
source: aiPlan.source
|
|
28073
|
+
},
|
|
28074
|
+
run: aiRun,
|
|
28075
|
+
onExecute: onAiExecute,
|
|
28076
|
+
onPause: aiPause,
|
|
28077
|
+
onResume: aiResume,
|
|
28078
|
+
onCancelRest: aiCancelRest,
|
|
28079
|
+
onClose: closeAiPlan
|
|
28080
|
+
}) : null;
|
|
25609
28081
|
return react.default.createElement("div", { className: "edrv-side-panel" }, react.default.createElement("div", { className: "edrv-side-head" }, react.default.createElement("span", { className: "edrv-side-title" }, "SVN 变更"), react.default.createElement("span", {
|
|
25610
28082
|
className: "edrv-side-root",
|
|
25611
28083
|
title: ctx?.svn?.wcRoot || ""
|
|
25612
|
-
}, rootName), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("button", {
|
|
28084
|
+
}, rootName), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("span", { className: "edrv-svn-head-icons" }, react.default.createElement("button", {
|
|
25613
28085
|
className: "edrv-side-btn",
|
|
25614
28086
|
title: "检查远端更新(svn status -u;约数秒,离线自动降级)",
|
|
25615
28087
|
onClick: checkRemote
|
|
@@ -25620,7 +28092,15 @@ window.__ModuleLoader__.load({
|
|
|
25620
28092
|
refreshSvnChanges(sessionId, scope);
|
|
25621
28093
|
ctx?.refreshRecords?.();
|
|
25622
28094
|
}
|
|
25623
|
-
}, refreshIconEl())),
|
|
28095
|
+
}, refreshIconEl()))), aiBarEl({
|
|
28096
|
+
managed,
|
|
28097
|
+
busy,
|
|
28098
|
+
aiBusy,
|
|
28099
|
+
deepWaiting,
|
|
28100
|
+
hasRun: Boolean(aiRun),
|
|
28101
|
+
onQuick: runAiPlan,
|
|
28102
|
+
onDeep: runDeepPlan
|
|
28103
|
+
}), remoteEl, pairCapEl, ...toolbar, react.default.createElement("div", { className: "edrv-svn-list" }, svnListBody(listState, handlers)), menuEl, aiPlanEl);
|
|
25624
28104
|
}
|
|
25625
28105
|
//#endregion
|
|
25626
28106
|
//#region src/client/dap/variableTree.ts
|
|
@@ -26484,7 +28964,7 @@ window.__ModuleLoader__.load({
|
|
|
26484
28964
|
return {
|
|
26485
28965
|
id: "explorer",
|
|
26486
28966
|
title: "文件管理",
|
|
26487
|
-
icon:
|
|
28967
|
+
icon: IconFolderOpenOutline16,
|
|
26488
28968
|
order: 10,
|
|
26489
28969
|
badge: (ctx) => ctx.sum && ctx.sum.totalFiles > 0 ? ctx.sum.totalFiles : null,
|
|
26490
28970
|
render: (ctx) => react.default.createElement(FileExplorer, { ctx })
|
|
@@ -26499,7 +28979,7 @@ window.__ModuleLoader__.load({
|
|
|
26499
28979
|
return {
|
|
26500
28980
|
id: "search",
|
|
26501
28981
|
title: "搜索",
|
|
26502
|
-
icon:
|
|
28982
|
+
icon: IconSearchOutline16,
|
|
26503
28983
|
order: 15,
|
|
26504
28984
|
render: (ctx) => react.default.createElement(SearchPanel, { ctx })
|
|
26505
28985
|
};
|
|
@@ -26513,7 +28993,7 @@ window.__ModuleLoader__.load({
|
|
|
26513
28993
|
return {
|
|
26514
28994
|
id: "rules",
|
|
26515
28995
|
title: "规则",
|
|
26516
|
-
icon:
|
|
28996
|
+
icon: IconListPenOutline16,
|
|
26517
28997
|
order: 20,
|
|
26518
28998
|
render: (ctx) => react.default.createElement(RulesPanel, { ctx })
|
|
26519
28999
|
};
|
|
@@ -26529,7 +29009,7 @@ window.__ModuleLoader__.load({
|
|
|
26529
29009
|
return {
|
|
26530
29010
|
id: "svn",
|
|
26531
29011
|
title: "SVN 变更",
|
|
26532
|
-
icon: typeof
|
|
29012
|
+
icon: typeof IconBranchOutline16 === "function" ? IconBranchOutline16 : "⎇",
|
|
26533
29013
|
order: 25,
|
|
26534
29014
|
visible: (ctx) => Boolean(ctx?.svn?.managed && ctx?.svn?.svnCli),
|
|
26535
29015
|
badge: (ctx) => {
|
|
@@ -27873,7 +30353,7 @@ window.__ModuleLoader__.load({
|
|
|
27873
30353
|
return {
|
|
27874
30354
|
id: "outline",
|
|
27875
30355
|
title: "大纲",
|
|
27876
|
-
icon:
|
|
30356
|
+
icon: IconCodeOutline16,
|
|
27877
30357
|
order: 20,
|
|
27878
30358
|
render: (ctx) => react.default.createElement(OutlinePanel, { ctx })
|
|
27879
30359
|
};
|
|
@@ -28130,8 +30610,7 @@ window.__ModuleLoader__.load({
|
|
|
28130
30610
|
"remote",
|
|
28131
30611
|
"workspaces",
|
|
28132
30612
|
"sessions",
|
|
28133
|
-
"conversation"
|
|
28134
|
-
"settingsScope"
|
|
30613
|
+
"conversation"
|
|
28135
30614
|
];
|
|
28136
30615
|
/** 指令桥装配幂等标记(同一 document 重复 apply 只装配一次,避免重复键位监听)。 */
|
|
28137
30616
|
let commandsMounted = false;
|
|
@@ -28201,8 +30680,11 @@ window.__ModuleLoader__.load({
|
|
|
28201
30680
|
return subscribeScope(ctx, () => schedule(check, 0));
|
|
28202
30681
|
}, "vscode-mode: editor tab restore");
|
|
28203
30682
|
const originalOpenPath = workspaces?.openPath;
|
|
28204
|
-
const
|
|
28205
|
-
|
|
30683
|
+
const bridge = settingsBridge(ctx, {
|
|
30684
|
+
schedule,
|
|
30685
|
+
attempts: 15,
|
|
30686
|
+
intervalMs: 2e3
|
|
30687
|
+
});
|
|
28206
30688
|
let selected = autoValue("auto");
|
|
28207
30689
|
/** 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)是否已安装(compatSummary 展示用)。 */
|
|
28208
30690
|
let remoteOpenInstalled = false;
|
|
@@ -28222,8 +30704,8 @@ window.__ModuleLoader__.load({
|
|
|
28222
30704
|
const compatSummary = () => [
|
|
28223
30705
|
{
|
|
28224
30706
|
name: "设置桥",
|
|
28225
|
-
active:
|
|
28226
|
-
note:
|
|
30707
|
+
active: bridge.scope() !== void 0,
|
|
30708
|
+
note: bridge.scope() !== void 0 ? "使用 " + bridge.service() + " 桥" : "未绑定设置服务(fileOpenTool 持久化不可用,晚到重试窗口内自愈)"
|
|
28227
30709
|
},
|
|
28228
30710
|
{
|
|
28229
30711
|
name: "文件打开路由(workspaces.openPath)",
|
|
@@ -28316,32 +30798,40 @@ window.__ModuleLoader__.load({
|
|
|
28316
30798
|
claimDisposer = null;
|
|
28317
30799
|
}
|
|
28318
30800
|
};
|
|
28319
|
-
|
|
28320
|
-
|
|
28321
|
-
|
|
28322
|
-
|
|
28323
|
-
|
|
28324
|
-
|
|
28325
|
-
|
|
28326
|
-
|
|
28327
|
-
|
|
28328
|
-
|
|
28329
|
-
|
|
28330
|
-
|
|
28331
|
-
|
|
28332
|
-
|
|
28333
|
-
|
|
28334
|
-
|
|
28335
|
-
|
|
28336
|
-
|
|
28337
|
-
const
|
|
28338
|
-
|
|
28339
|
-
|
|
28340
|
-
|
|
28341
|
-
|
|
28342
|
-
|
|
28343
|
-
|
|
28344
|
-
|
|
30801
|
+
let settingsSyncMounted = false;
|
|
30802
|
+
const mountSettingsSyncs = () => {
|
|
30803
|
+
if (settingsSyncMounted) return;
|
|
30804
|
+
const scope = bridge.scope();
|
|
30805
|
+
if (!scope) return;
|
|
30806
|
+
settingsSyncMounted = true;
|
|
30807
|
+
ctx.effect(() => {
|
|
30808
|
+
const sync = () => {
|
|
30809
|
+
const snapshot = scope.getSnapshot();
|
|
30810
|
+
if (snapshot.status === "loading") return;
|
|
30811
|
+
selected = autoValue(snapshot.value?.fileOpenTool);
|
|
30812
|
+
syncFileClaim();
|
|
30813
|
+
window.dispatchEvent(new CustomEvent("edrv:file-open-tool-change", { detail: { value: selected } }));
|
|
30814
|
+
};
|
|
30815
|
+
sync();
|
|
30816
|
+
return scope.subscribe(sync);
|
|
30817
|
+
}, "vscode-mode: file opener setting sync");
|
|
30818
|
+
ctx.effect(() => {
|
|
30819
|
+
const sync = () => {
|
|
30820
|
+
const snapshot = scope.getSnapshot();
|
|
30821
|
+
if (snapshot.status === "loading") return;
|
|
30822
|
+
keybindingsApply(snapshot.value?.keybindings);
|
|
30823
|
+
const minW = sidebarMinApply(snapshot.value?.sidebarMinWidth);
|
|
30824
|
+
window.dispatchEvent(new CustomEvent("edrv:sidebar-min-width", { detail: { value: minW } }));
|
|
30825
|
+
const limit = editorLimitApply(snapshot.value?.maxOpenEditors);
|
|
30826
|
+
window.dispatchEvent(new CustomEvent("edrv:max-open-editors", { detail: { value: limit } }));
|
|
30827
|
+
setNativeCsv(snapshot.value?.nativeOpenExts);
|
|
30828
|
+
};
|
|
30829
|
+
sync();
|
|
30830
|
+
return scope.subscribe(sync);
|
|
30831
|
+
}, "vscode-mode: keybindings setting sync");
|
|
30832
|
+
};
|
|
30833
|
+
mountSettingsSyncs();
|
|
30834
|
+
if (!settingsSyncMounted) ctx.effect(() => bridge.whenReady(mountSettingsSyncs), "vscode-mode: settings bridge wait");
|
|
28345
30835
|
if (workspaces?.openPath) ctx.effect(() => installOpenPathRouter({
|
|
28346
30836
|
workspaces,
|
|
28347
30837
|
registry,
|
|
@@ -28440,7 +30930,18 @@ window.__ModuleLoader__.load({
|
|
|
28440
30930
|
return;
|
|
28441
30931
|
}
|
|
28442
30932
|
officialDisposer = ctx.effect(() => outcome, "vscode-mode: official sidebar editor tab");
|
|
28443
|
-
if (typeof official.service.openResource === "function")
|
|
30933
|
+
if (typeof official.service.openResource === "function") {
|
|
30934
|
+
ctx.effect(() => registry.register(officialSidebarOpener(official.service)), "vscode-mode: official sidebar opener");
|
|
30935
|
+
ctx.effect(() => {
|
|
30936
|
+
setNativeOpenHandler((path) => {
|
|
30937
|
+
const scope = readSessionScope(ctx);
|
|
30938
|
+
if (!scope.sessionId) return false;
|
|
30939
|
+
official.service.openResource?.(buildFileAddress(path, scope.sessionId));
|
|
30940
|
+
return typeof official.service.openResource === "function";
|
|
30941
|
+
});
|
|
30942
|
+
return () => setNativeOpenHandler(null);
|
|
30943
|
+
}, "vscode-mode: native open handler");
|
|
30944
|
+
}
|
|
28444
30945
|
syncFileClaim();
|
|
28445
30946
|
};
|
|
28446
30947
|
const applySideForm = () => {
|
|
@@ -28541,7 +31042,7 @@ window.__ModuleLoader__.load({
|
|
|
28541
31042
|
id: "vscode-mode",
|
|
28542
31043
|
order: 30,
|
|
28543
31044
|
label: "VSCodeMode"
|
|
28544
|
-
}, () => react.default.createElement(SettingsContext.Provider, { value:
|
|
31045
|
+
}, () => react.default.createElement(SettingsContext.Provider, { value: bridge.scope() }, react.default.createElement(McpSettings, {
|
|
28545
31046
|
openerRegistry: registry,
|
|
28546
31047
|
compatSummary
|
|
28547
31048
|
})));
|
|
@@ -28558,6 +31059,9 @@ window.__ModuleLoader__.load({
|
|
|
28558
31059
|
try {
|
|
28559
31060
|
disposeLsp();
|
|
28560
31061
|
} catch {}
|
|
31062
|
+
try {
|
|
31063
|
+
resetNativeOpen();
|
|
31064
|
+
} catch {}
|
|
28561
31065
|
}, "vscode-mode: monaco providers teardown");
|
|
28562
31066
|
}
|
|
28563
31067
|
//#endregion
|