dsh-vscode-mode 0.17.0 → 0.19.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 +8 -4
- package/lib/client.debug.js +934 -0
- package/lib/client.debug.js.map +1 -0
- package/lib/client.js +1425 -1724
- package/lib/client.js.map +1 -1
- package/lib/client.rules.js +793 -0
- package/lib/client.rules.js.map +1 -0
- package/lib/index.js +1078 -286
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/dsh-vscodemode-mcp/SKILL.md +24 -7
- package/src/client/index.ts +24 -0
- package/src/client/lazyPanel.ts +68 -0
- package/src/client/mcpSessionState.ts +108 -0
- package/src/client/monaco/lsp/providers.ts +32 -3
- package/src/client/sidebar/PanelBoundary.ts +57 -0
- package/src/client/sidebar/SidebarView.ts +58 -21
- package/src/client/sidebar/panels/FileExplorer.ts +80 -27
- package/src/client/sidebar/panels/SvnPanel.ts +24 -8
- package/src/client/sidebar/panels/index.ts +3 -4
- package/src/client/sidebar/panels/treeWindow.ts +106 -0
- package/src/client/sidebar/panels/useExplorerReveal.ts +5 -2
- package/src/client/sidebar/registry.ts +4 -1
- package/src/client/state/modelWarmer.ts +198 -0
- package/src/client/styles/editor.css +13 -1
- package/src/client/textPages.ts +135 -0
- package/src/client/ui/EditorPane.ts +16 -4
- package/src/client/ui/EditorView.ts +147 -29
- package/src/client/ui/LargeTextView.ts +94 -0
- package/src/client/ui/McpSessionStatus.ts +75 -0
- package/src/client/ui/McpSettings.ts +47 -13
- package/src/mcp.ts +1 -0
- package/src/mcpBridge.ts +163 -0
- package/src/mcpDiscover.ts +38 -0
- package/src/mcpPool.ts +245 -0
- package/src/mcpProject.ts +15 -12
- package/src/mcpRuntime.ts +299 -254
- package/src/rpc.ts +42 -8
- package/src/shared/mcp.ts +47 -0
- package/src/shared/rpc.ts +8 -2
- package/src/shared/textPages.ts +28 -0
- package/src/textPages.ts +149 -0
- package/src/treeIndex.ts +62 -37
package/lib/client.js
CHANGED
|
@@ -29,11 +29,11 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
let react = require("react");
|
|
30
30
|
react = __toESM(react, 1);
|
|
31
31
|
let react_dom = require("react-dom");
|
|
32
|
+
let react_dom_client = require("react-dom/client");
|
|
32
33
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
33
34
|
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
34
|
-
let react_dom_client = require("react-dom/client");
|
|
35
35
|
//#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
|
|
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 { user-select: none; -webkit-user-select: none; 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 { appearance: none; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 16px; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 4px; background: transparent; box-shadow: none; color: var(--dsw-alias-label-tertiary, #888); font-family: inherit; font-size: 11px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:focus-visible { outline: 1px solid var(--dsw-alias-state-info-primary, #4d6bfe); outline-offset: 1px; }\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/* 非焦点分组的静态路径条(GroupPathbar):可点击聚焦所属组,悬停给反馈 @author ddj 2026-10-16 */\n[data-edrv-view] .edrv-pathbar[data-edrv-pb-static] { cursor: pointer; }\n[data-edrv-view] .edrv-pathbar[data-edrv-pb-static]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-pb-update { display: inline-flex; align-items: center; gap: 4px; height: 20px; flex: none; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l1, #d9e0e4); border-radius: 4px; background: var(--dsw-alias-bg-layer-2, #f1f4f5); color: var(--dsw-alias-label-secondary, #485762); font-size: 11px; cursor: pointer; }\n[data-edrv-view] .edrv-pb-update:hover { background: var(--dsw-alias-interactive-bg-hover, #e7edf0); }\n[data-edrv-view] .edrv-pb-update:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 1px; }\n@media (max-width: 620px) { [data-edrv-view] .edrv-pb-update { width: 24px; padding: 0 4px; overflow: hidden; font-size: 0; } }\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/* 行内和原生 Monaco diff 共用主题色盘;不再借用普通 hover 的灰底。 */\n[data-edrv-view] .edrv-mn-add-line { background: var(--edrv-diff-add, #e6f4e8); }\n[data-edrv-view] .edrv-mn-gutter-add { background: var(--edrv-diff-add, #e6f4e8); color: var(--dsw-alias-state-success-primary, #0f9d58) !important; 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, [data-edrv-view] .edrv-del-margin { background: var(--edrv-diff-del, #fbe7e3); color: var(--edrv-diff-fg, #1e1e1e); }\n[data-edrv-view] .edrv-del-row { white-space: pre; height: var(--edrv-line-height, 20px); line-height: var(--edrv-line-height, 20px); }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-del-margin-row { position: relative; height: var(--edrv-line-height, 20px); line-height: var(--edrv-line-height, 20px); }\n[data-edrv-view] .edrv-del-number { position: absolute; left: var(--edrv-number-left); width: var(--edrv-number-width); color: var(--edrv-diff-number, #6c6c6c); text-align: right; }\n[data-edrv-view] .edrv-del-marker { position: absolute; left: var(--edrv-marker-left); width: var(--edrv-marker-width); color: var(--dsw-alias-state-error-primary, #d9534f); text-align: center; }\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/* 侧栏差异栏在编辑内容底部居中悬浮,按内容宽度收缩;窄栏只在工具条内横滚。@author ddj 2026年09月29号 */\n[data-edrv-view] .edrv-side-diff-dock[data-edrv-diff-dock-shell] { position: absolute; left: 50%; bottom: 28px; z-index: 25; width: max-content; max-width: calc(100% - 20px); max-height: min(300px, calc(100% - 38px)); margin: 0; transform: translateX(-50%); border-radius: 12px; background: var(--dsw-alias-bg-overlay, #fff); box-shadow: 0 6px 20px rgba(0,0,0,.18); overflow-x: hidden; overflow-y: auto; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-editor-row { padding-inline: 8px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-editor-content { gap: 5px; width: max-content; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-nav,\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-files,\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-actions { gap: 3px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-pill { padding-inline: 5px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-files .edrv-diffbar-file { min-width: 0; max-width: 72px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-details-toggle { width: 26px; min-width: 26px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-body { max-height: min(180px, 25vh); max-width: 100%; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diff-details { max-height: min(260px, 35vh); overflow-y: auto; }\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/* SVN overlays: official file icon remains visible, independent local/remote signs. ddj 2026-10-09 */\n[data-edrv-view] .edrv-tree-iconbox { position: relative; display: inline-flex; width: 20px; height: 20px; align-items: center; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-overlay { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-svn-local { position: absolute; right: -1px; bottom: -2px; display: flex; border-radius: 50%; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-svn-icon-clean { color: #16853b; }\n[data-edrv-view] .edrv-svn-icon-modified, [data-edrv-view] .edrv-svn-icon-deleted { color: #d12e35; }\n[data-edrv-view] .edrv-svn-icon-conflict { color: #c27300; }\n[data-edrv-view] .edrv-svn-icon-added { color: #2465c9; }\n[data-edrv-view] .edrv-svn-icon-unversioned, [data-edrv-view] .edrv-svn-icon-ignored, [data-edrv-view] .edrv-svn-icon-external { color: #75808c; }\n[data-edrv-view] .edrv-svn-remote { position: absolute; right: -3px; top: -4px; color: #2465c9; background: var(--dsw-alias-bg-layer-1, #fff); border-radius: 3px; font-size: 11px; font-weight: 900; line-height: 12px; }\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/* SVN 状态独立占位:换行不覆盖文件树,长错误在栏内滚动。@author ddj 2026-10-12 */\n[data-edrv-view] .edrv-tree-status { flex: 0 0 auto; min-width: 0; max-height: 96px; overflow: auto; padding: 4px 8px; box-sizing: border-box; line-height: 18px; white-space: normal; overflow-wrap: anywhere; 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/* .edrv-svn-ch-* 动作词表补全:replaced(替换)= 琥珀;侧栏状态分级(SVN_STATUS_TONE)不受影响 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svn-ch-replaced { color: var(--dsw-alias-state-warn-primary, #d97706); }\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: 10px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-svnlog-title { margin: 0; font-size: 16px; font-weight: 650; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n/* 头部目标路径 = 代码 chip(mono + 底色 + 全圆角;补丁/目录对比弹窗共用同视觉) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-target { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n/* 关闭按钮图标化(透明底,hover 显形;dialog 前缀提高特异性,压过底部按钮统一尺寸) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svnlog-head .edrv-svn-act { border-color: transparent; background: transparent; width: 24px; height: 24px; padding: 0; font-size: 12px; line-height: 1; }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svnlog-head .edrv-svn-act:hover { border-color: var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }\n/* 过滤栏:浅色表面条 + 统一 24px 控件 + 焦点边框变色 + 日期组靠右 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-filter { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-field { flex: 0 0 auto; height: 24px; box-sizing: border-box; padding: 0 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; height: 24px; box-sizing: border-box; padding: 0 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-field:focus, [data-edrv-view] .edrv-svnlog-search:focus, [data-edrv-view] .edrv-svnlog-date:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-dates { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }\n[data-edrv-view] .edrv-svnlog-date { height: 24px; box-sizing: border-box; padding: 0 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-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-check input[type=\"checkbox\"] { margin: 0; cursor: pointer; accent-color: var(--dsw-alias-brand-primary, #2563eb); }\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/* 上段修订列表(13-log-ui-polish 变更台账):\n 1px 发丝边框 + 底色面 + 左侧时间脊线(每行 ::before 画节点圆点、行盒左缘 ::after 连线段,\n 行行相接即整列脊线;选中行节点实心着色 + 2px 左强调条)。列宽由 cell 类与 nth-child 对齐(P0-12/D30)。 */\n[data-edrv-view] .edrv-svnlog-list { position: relative; flex: 0 0 auto; display: flex; flex-direction: column; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 0 4px 4px; background: var(--dsw-alias-bg-layer-1, #fff); }\n/* 列头 sticky:滚动常驻 + 实底防透;z-index 20 低于一切浮层(190 起),仅在列表滚动容器内生效 */\n[data-edrv-view] .edrv-svnlog-cols { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-tertiary, #9aa5b1); 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/* 行:position:relative 承载脊线节点(::before)与连线段(::after);选中 = 强调条 + 节点实心 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-item { position: relative; display: flex; align-items: center; gap: 6px; padding: 3px 6px 3px 16px; border-radius: 6px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-item::before { content: ''; position: absolute; left: 6px; top: 50%; transform: translateY(-50%); width: 7px; height: 7px; box-sizing: border-box; border-radius: 50%; border: 1.5px solid var(--dsw-alias-border-l2, #cbd2d9); background: var(--dsw-alias-bg-layer-1, #fff); z-index: 1; }\n[data-edrv-view] .edrv-svnlog-item::after { content: ''; position: absolute; left: 9px; top: 0; bottom: 0; width: 1px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-item:last-child::after { bottom: 50%; }\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)); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-item.edrv-svnlog-item-on::before { border-color: var(--dsw-alias-brand-primary, #2563eb); background: var(--dsw-alias-brand-primary, #2563eb); }\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); font-variant-numeric: tabular-nums; }\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: 14px; height: 14px; border-radius: 4px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; }\n/* 动作语义色(13-log-ui-polish 归位):A=success 绿 · D=error 红 · M=brand 蓝 · R=warn 琥珀 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-badge-modified { color: var(--dsw-alias-state-info-primary, #2f6fed); background: rgba(47,111,237,.14); }\n[data-edrv-view] .edrv-svnlog-badge-added { color: var(--dsw-alias-state-success-primary, #0f9d58); background: rgba(15,157,88,.14); }\n[data-edrv-view] .edrv-svnlog-badge-deleted { color: var(--dsw-alias-state-error-primary, #d9534f); background: rgba(217,83,79,.14); }\n[data-edrv-view] .edrv-svnlog-badge-replaced { color: var(--dsw-alias-state-warn-primary, #d97706); background: rgba(217,119,6,.16); }\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 公共外观(13-log-ui-polish:2px 粗框 → 1px 发丝线 + 面板底色) */\n[data-edrv-view] .edrv-svnlog-pane { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 8px 10px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, #fff); }\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/* 提交信息 meta 行:rev/日期 chip 化,author 常规字重(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-meta .edrv-svnlog-rev { padding: 1px 8px; border-radius: 999px; background: var(--dsw-alias-state-info-primary, #2f6fed); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 11px; font-weight: 700; }\n[data-edrv-view] .edrv-svnlog-meta .edrv-svnlog-date { height: auto; padding: 1px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-date { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 正文块:左侧品牌强调线 +1.7 行高(长提交信息可读性) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-msgpane pre.edrv-svnlog-body { margin: 8px 0 0; padding: 8px 10px; border-radius: 0 6px 6px 0; border-left: 2px solid var(--dsw-alias-state-info-primary, #2f6fed); background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11.5px; line-height: 1.7; white-space: pre-wrap; word-break: break-word; overflow: auto; flex: 1 1 auto; min-height: 0; }\n/* P0-14 外框尺寸手柄(13-log-ui-polish 增量3 八向:n/s/w/e 四边 + nw/ne/sw/se 四角);\n 热区避开内边距带(边8px/角14px 落在卡片22/24px padding 内不遮内容),角手柄后渲染盖过相邻边2px overlap。\n chevron 角标四向各自朝向,hover 显形。 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-grip { position: absolute; z-index: 2; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-grip-n { top: 0; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-s { bottom: 0; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-w { left: 0; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-e { right: 0; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-nw { left: 0; top: 0; width: 14px; height: 14px; cursor: nwse-resize; }\n[data-edrv-view] .edrv-svnlog-grip-ne { right: 0; top: 0; width: 14px; height: 14px; cursor: nesw-resize; }\n[data-edrv-view] .edrv-svnlog-grip-sw { left: 0; bottom: 0; width: 14px; height: 14px; cursor: nesw-resize; }\n[data-edrv-view] .edrv-svnlog-grip-se { right: 0; bottom: 0; width: 14px; height: 14px; cursor: nwse-resize; background: transparent; }\n[data-edrv-view] .edrv-svnlog-grip-nw::after,\n[data-edrv-view] .edrv-svnlog-grip-ne::after,\n[data-edrv-view] .edrv-svnlog-grip-sw::after,\n[data-edrv-view] .edrv-svnlog-grip-se::after { content: ''; position: absolute; width: 6px; height: 6px; opacity: .5; border-color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-grip-se::after { right: 3px; bottom: 3px; border-right: 1.5px solid; border-bottom: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-sw::after { left: 3px; bottom: 3px; border-left: 1.5px solid; border-bottom: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-ne::after { right: 3px; top: 3px; border-right: 1.5px solid; border-top: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-nw::after { left: 3px; top: 3px; border-left: 1.5px solid; border-top: 1.5px solid; }\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-grip:hover::after, [data-edrv-view] .edrv-svnlog-grip:active::after { opacity: 1; border-color: var(--dsw-alias-label-primary, #1f2933); }\n/* 行反馈微动效(仅 hover/选中背景过渡;用户要求减少动效时关闭) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-item { transition: background-color .12s ease, box-shadow .12s ease; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-svnlog-item { transition: none; }\n}\n/*13-log-ui-polish 增量:行容器可滚动(pane 仍 overflow:hidden 裁圆角),列头不被压缩 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-paths-head { display: flex; align-items: center; gap: 6px; flex: none; font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-tertiary, #9aa5b1); margin-bottom: 4px; padding-bottom: 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-hint { font-weight: 400; letter-spacing: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-paths { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-height: 0; overflow: auto; }\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-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); }\n/* 路径两段着色:目录段弱化 + 文件名加重(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-pdir { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-pbase { color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\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/* 底部:状态行与按钮组分层 + 组间发丝线(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-footwrap { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; padding-top: 6px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-btns { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnlog-btngroup { display: inline-flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnlog-btngroup + .edrv-svnlog-btngroup { margin-left: 6px; padding-left: 10px; border-left: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svn-act,\n[data-edrv-view] .edrv-svnlog-range .edrv-svn-act,\n[data-edrv-view] .edrv-svnlog-stats .edrv-svn-act { font-size: 11px; padding: 2px 9px; }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svn-act:focus-visible,\n[data-edrv-view] .edrv-svnlog-range .edrv-svn-act:focus-visible,\n[data-edrv-view] .edrv-svnlog-stats .edrv-svn-act:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2563eb); outline-offset: 1px; }\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 更新窗口:逐条状态与路径是主信息;跟随 DSH 主题令牌。 */\n[data-edrv-view] .edrv-update-dialog { display: flex; flex-direction: column; padding: 0; border-radius: 8px; overflow: hidden; background: var(--dsw-alias-bg-overlay, #fff); }\n[data-edrv-view] .edrv-update-head { display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 0 16px; border-bottom: 1px solid var(--dsw-alias-border-l1, #d9e0e4); }\n[data-edrv-view] .edrv-update-heading { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }\n[data-edrv-view] .edrv-update-heading h3 { margin: 0; flex: none; font-size: 15px; }\n[data-edrv-view] .edrv-update-target { min-width: 0; color: var(--dsw-alias-label-tertiary, #65737c); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }\n[data-edrv-view] .edrv-update-phase { flex: none; font-size: 12px; color: var(--dsw-alias-label-secondary, #485762); }\n[data-edrv-view] .edrv-update-close { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border: 0; border-radius: 4px; background: none; color: var(--dsw-alias-label-secondary, #485762); cursor: pointer; }\n[data-edrv-view] .edrv-update-close:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-update-close:not(:disabled):hover { background: var(--dsw-alias-interactive-bg-hover, #e7edf0); }\n[data-edrv-view] .edrv-update-table { display: flex; flex-direction: column; flex: 1; min-height: 0; margin: 12px 14px 0; border: 1px solid var(--dsw-alias-border-l1, #d9e0e4); border-radius: 5px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-update-cols, [data-edrv-view] .edrv-update-row { display: grid; grid-template-columns: 110px minmax(0, 1fr) 160px; align-items: center; }\n[data-edrv-view] .edrv-update-sort { width: 100%; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }\n[data-edrv-view] .edrv-update-sort:hover { color: var(--dsw-alias-brand-primary, #2869b4); }\n[data-edrv-view] .edrv-update-sort:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 2px; }\n[data-edrv-view] .edrv-update-jump { color: var(--dsw-alias-state-error-primary, #b93b43); }\n[data-edrv-view] .edrv-update-cols { flex: none; height: 30px; background: var(--dsw-alias-bg-layer-2, #f1f4f5); border-bottom: 1px solid var(--dsw-alias-border-l1, #d9e0e4); color: var(--dsw-alias-label-secondary, #485762); font-size: 11px; font-weight: 600; }\n[data-edrv-view] .edrv-update-cols > span, [data-edrv-view] .edrv-update-row > span { min-width: 0; padding: 0 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-update-cols > span + span, [data-edrv-view] .edrv-update-row > span + span { border-left: 1px solid var(--dsw-alias-border-l1, #d9e0e4); }\n[data-edrv-view] .edrv-update-list { position: relative; flex: 1; min-height: 0; overflow: auto; scrollbar-width: thin; }\n[data-edrv-view] .edrv-update-space { position: relative; }\n[data-edrv-view] .edrv-update-pos { position: absolute; left: 0; right: 0; }\n[data-edrv-view] .edrv-update-row { height: 100%; font-size: 11px; cursor: default; color: var(--dsw-alias-label-primary, #202932); }\n[data-edrv-view] .edrv-update-row:hover { background: var(--dsw-alias-interactive-bg-hover, #ecf3f9); }\n[data-edrv-view] .edrv-update-row.is-selected { background: var(--dsw-alias-interactive-bg-hover, #dceaf9); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #2869b4); }\n[data-edrv-view] .edrv-update-action { font-weight: 600; }\n[data-edrv-view] .edrv-update-action.tone-added { color: var(--dsw-alias-state-success-primary, #23704e); }\n[data-edrv-view] .edrv-update-action.tone-deleted, [data-edrv-view] .edrv-update-action.tone-conflict { color: var(--dsw-alias-state-error-primary, #b93b43); }\n[data-edrv-view] .edrv-update-action.tone-merged { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-action.tone-updated { color: var(--dsw-alias-state-info-primary, #2869b4); }\n[data-edrv-view] .edrv-update-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-update-type { color: var(--dsw-alias-label-tertiary, #65737c); }\n[data-edrv-view] .edrv-update-empty { position: absolute; top: 50%; left: 0; right: 0; transform: translateY(-50%); text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary, #65737c); pointer-events: none; }\n[data-edrv-view] .edrv-update-telemetry { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px; padding: 10px 15px; min-height: 43px; box-sizing: border-box; font-size: 11px; color: var(--dsw-alias-label-secondary, #485762); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-update-error { color: var(--dsw-alias-state-error-primary, #b93b43); overflow-wrap: anywhere; }\n[data-edrv-view] .edrv-update-warning { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-locked { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-foot .edrv-update-cleanup:not(:disabled) { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-foot { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--dsw-alias-border-l1, #d9e0e4); font-size: 11px; }\n[data-edrv-view] .edrv-update-counts { flex: 1; min-width: 0; color: var(--dsw-alias-label-secondary, #485762); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act { flex: none; min-width: 74px; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act:focus-visible, [data-edrv-view] .edrv-update-close:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 2px; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-update-raw { max-height: 120px; overflow: auto; padding: 0 14px 10px; font-size: 11px; }\n[data-edrv-view] .edrv-update-raw pre { white-space: pre-wrap; overflow-wrap: anywhere; margin: 5px 0; }\n@media (max-width: 620px) {\n [data-edrv-view] .edrv-update-head { gap: 6px; padding: 0 10px; }\n [data-edrv-view] .edrv-update-table { margin: 8px 8px 0; }\n [data-edrv-view] .edrv-update-cols, [data-edrv-view] .edrv-update-row { grid-template-columns: 76px minmax(0, 1fr); }\n [data-edrv-view] .edrv-update-cols > span:nth-child(3), [data-edrv-view] .edrv-update-row > span:nth-child(3) { display: none; }\n [data-edrv-view] .edrv-update-path { white-space: normal !important; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }\n [data-edrv-view] .edrv-update-foot { flex-wrap: wrap; padding: 8px; }\n [data-edrv-view] .edrv-update-counts { flex-basis: 100%; }\n}\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); }\n\n/* Workbench panes: existing theme, action-driven drop previews. ddj 2026年10月14号 */\n[data-edrv-view] .edrv-group, [data-edrv-view] .edrv-group-body,\n[data-edrv-view] .edrv-full-slot, [data-edrv-view] .edrv-full-view,\n[data-edrv-view] .edrv-passive-view { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-group { border: 0; }\n[data-edrv-view] .edrv-group .edrv-tabs { flex: 0 0 auto; touch-action: none; }\n[data-edrv-view] .edrv-group-tab-name { overflow: hidden; text-overflow: ellipsis; max-width: 180px; }\n[data-edrv-view] .edrv-workbench-tools { flex: 0 0 auto; }\n[data-edrv-view] .edrv-split { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-split-vertical { flex-direction: column; }\n[data-edrv-view] .edrv-split-child { display: flex; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-split-handle { flex: 0 0 5px; background: var(--dsw-alias-border-l1, #e0e6e8); cursor: col-resize; touch-action: none; }\n[data-edrv-view] .edrv-split-vertical > .edrv-split-handle { cursor: row-resize; }\n[data-edrv-view] .edrv-split-handle:hover, [data-edrv-view] .edrv-split-handle:focus-visible { background: var(--dsw-alias-state-info-primary, #4d6bfe); }\n[data-edrv-view].edrv-floating { position: fixed; display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-base, #fff); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: 0 8px 28px rgba(0,0,0,.22); }\n[data-edrv-view] .edrv-floating-title { display: flex; align-items: center; gap: 8px; flex: 0 0 32px; padding: 0 8px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); cursor: move; touch-action: none; user-select: none; }\n[data-edrv-view] .edrv-floating-title > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-floating-content { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; overflow: auto; box-sizing: border-box; }\n[data-edrv-view] .edrv-floating-content.edrv-svnlog-dialog { padding: 12px; overflow: auto; }\n[data-edrv-view] .edrv-floating-content.edrv-svn-patch-dialog,\n[data-edrv-view] .edrv-floating-content.edrv-svn-sum-dialog,\n[data-edrv-view] .edrv-floating-content.edrv-svnplan-dialog { padding: 12px; max-width: none; gap: 8px; }\n[data-edrv-view] .edrv-floating-content.edrv-svnlog-stats { max-height: none; }\n[data-edrv-view] .edrv-floating-content .edrv-svn-patch-pre,\n[data-edrv-view] .edrv-floating-content .edrv-svn-sum-list { flex: 1; min-height: 0; max-height: none; }\n[data-edrv-view] .edrv-floating-content .edrv-svnlog-filter,\n[data-edrv-view] .edrv-floating-content .edrv-svn-sum-form { flex-wrap: wrap; }\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-head,\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-filter,\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-foot { flex-shrink: 0; }\n[data-edrv-view] .edrv-floating-title > button { border: 0; background: transparent; color: inherit; cursor: pointer; }\n[data-edrv-view] .edrv-floating-grip { position: absolute; width: 16px; height: 16px; bottom: 0; right: 0; cursor: nwse-resize; touch-action: none; border-right: 3px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 3px solid var(--dsw-alias-border-l2, #cdd6da); }\n.edrv-drag-overlay { position: fixed; z-index: 150; pointer-events: none; box-sizing: border-box; border: 0; background: color-mix(in srgb, var(--dsw-alias-state-info-primary, #4d6bfe) 25%, transparent); }\n.edrv-drop-label, .edrv-drag-ghost { position: fixed; z-index: 151; pointer-events: none; padding: 4px 8px; background: var(--dsw-alias-bg-overlay, #fff); color: var(--dsw-alias-label-primary, #1f2933); border: 1px solid var(--dsw-alias-border-l2, #cdd6da); font-size: 12px; white-space: nowrap; }\n.edrv-drop-label { position: absolute; left: 4px; top: 4px; }\n[data-edrv-view] .edrv-tab:focus-visible { outline: 2px solid var(--dsw-alias-state-info-primary, #4d6bfe); outline-offset: -2px; }\n@media (prefers-reduced-motion: reduce) { .edrv-drag-overlay { transition: none; } }\n";
|
|
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 { user-select: none; -webkit-user-select: none; 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 { appearance: none; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 16px; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 4px; background: transparent; box-shadow: none; color: var(--dsw-alias-label-tertiary, #888); font-family: inherit; font-size: 11px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:focus-visible { outline: 1px solid var(--dsw-alias-state-info-primary, #4d6bfe); outline-offset: 1px; }\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/* 页签行 = 可横滚的页签区 + 行尾固定工具列(计划预览页签与磁盘页签同处这一行的滚动区)@author ddj 2026年10月20号 */\n[data-edrv-view] .edrv-tabs-row { display: flex; align-items: center; min-width: 0; flex: 0 0 auto; background: var(--dsw-alias-bg-layer-1, transparent); }\n/* 特异性需盖过下面 .edrv-group .edrv-tabs 的 flex: 0 0 auto,页签区才能吃掉行走宽、把工具列压到行尾 */\n[data-edrv-view] .edrv-group .edrv-tabs-row > .edrv-tabs { flex: 1 1 auto; min-width: 0; background: transparent; }\n[data-edrv-view] .edrv-tabs-tools { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; padding: 0 6px 0 2px; }\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/* 非焦点分组的静态路径条(GroupPathbar):可点击聚焦所属组,悬停给反馈 @author ddj 2026-10-16 */\n[data-edrv-view] .edrv-pathbar[data-edrv-pb-static] { cursor: pointer; }\n[data-edrv-view] .edrv-pathbar[data-edrv-pb-static]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-pb-update { display: inline-flex; align-items: center; gap: 4px; height: 20px; flex: none; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l1, #d9e0e4); border-radius: 4px; background: var(--dsw-alias-bg-layer-2, #f1f4f5); color: var(--dsw-alias-label-secondary, #485762); font-size: 11px; cursor: pointer; }\n[data-edrv-view] .edrv-pb-update:hover { background: var(--dsw-alias-interactive-bg-hover, #e7edf0); }\n[data-edrv-view] .edrv-pb-update:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 1px; }\n@media (max-width: 620px) { [data-edrv-view] .edrv-pb-update { width: 24px; padding: 0 4px; overflow: hidden; font-size: 0; } }\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/* 行内和原生 Monaco diff 共用主题色盘;不再借用普通 hover 的灰底。 */\n[data-edrv-view] .edrv-mn-add-line { background: var(--edrv-diff-add, #e6f4e8); }\n[data-edrv-view] .edrv-mn-gutter-add { background: var(--edrv-diff-add, #e6f4e8); color: var(--dsw-alias-state-success-primary, #0f9d58) !important; 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, [data-edrv-view] .edrv-del-margin { background: var(--edrv-diff-del, #fbe7e3); color: var(--edrv-diff-fg, #1e1e1e); }\n[data-edrv-view] .edrv-del-row { white-space: pre; height: var(--edrv-line-height, 20px); line-height: var(--edrv-line-height, 20px); }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-del-margin-row { position: relative; height: var(--edrv-line-height, 20px); line-height: var(--edrv-line-height, 20px); }\n[data-edrv-view] .edrv-del-number { position: absolute; left: var(--edrv-number-left); width: var(--edrv-number-width); color: var(--edrv-diff-number, #6c6c6c); text-align: right; }\n[data-edrv-view] .edrv-del-marker { position: absolute; left: var(--edrv-marker-left); width: var(--edrv-marker-width); color: var(--dsw-alias-state-error-primary, #d9534f); text-align: center; }\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/* 侧栏差异栏在编辑内容底部居中悬浮,按内容宽度收缩;窄栏只在工具条内横滚。@author ddj 2026年09月29号 */\n[data-edrv-view] .edrv-side-diff-dock[data-edrv-diff-dock-shell] { position: absolute; left: 50%; bottom: 28px; z-index: 25; width: max-content; max-width: calc(100% - 20px); max-height: min(300px, calc(100% - 38px)); margin: 0; transform: translateX(-50%); border-radius: 12px; background: var(--dsw-alias-bg-overlay, #fff); box-shadow: 0 6px 20px rgba(0,0,0,.18); overflow-x: hidden; overflow-y: auto; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-editor-row { padding-inline: 8px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-editor-content { gap: 5px; width: max-content; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-nav,\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-files,\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-actions { gap: 3px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-pill { padding-inline: 5px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-files .edrv-diffbar-file { min-width: 0; max-width: 72px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-details-toggle { width: 26px; min-width: 26px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-body { max-height: min(180px, 25vh); max-width: 100%; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diff-details { max-height: min(260px, 35vh); overflow-y: auto; }\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/* SVN overlays: official file icon remains visible, independent local/remote signs. ddj 2026-10-09 */\n[data-edrv-view] .edrv-tree-iconbox { position: relative; display: inline-flex; width: 20px; height: 20px; align-items: center; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-overlay { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-svn-local { position: absolute; right: -1px; bottom: -2px; display: flex; border-radius: 50%; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-svn-icon-clean { color: #16853b; }\n[data-edrv-view] .edrv-svn-icon-modified, [data-edrv-view] .edrv-svn-icon-deleted { color: #d12e35; }\n[data-edrv-view] .edrv-svn-icon-conflict { color: #c27300; }\n[data-edrv-view] .edrv-svn-icon-added { color: #2465c9; }\n[data-edrv-view] .edrv-svn-icon-unversioned, [data-edrv-view] .edrv-svn-icon-ignored, [data-edrv-view] .edrv-svn-icon-external { color: #75808c; }\n[data-edrv-view] .edrv-svn-remote { position: absolute; right: -3px; top: -4px; color: #2465c9; background: var(--dsw-alias-bg-layer-1, #fff); border-radius: 3px; font-size: 11px; font-weight: 900; line-height: 12px; }\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/* Virtual rows and placeholders share the same geometry as treeWindow.ts. @author ddj 2026年10月22号 */\n[data-edrv-view] .edrv-tree.edrv-tree-virtual { padding-top: 0; padding-bottom: 0; }\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-row,\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-loading { height: 24px; min-height: 24px; box-sizing: border-box; }\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-loading { line-height: 24px; }\n[data-edrv-view] .edrv-large-text { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-large-text-toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }\n[data-edrv-view] .edrv-large-text-content { margin: 0; flex: 1; min-height: 0; overflow: auto; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); user-select: text; }\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/* SVN 状态独立占位:换行不覆盖文件树,长错误在栏内滚动。@author ddj 2026-10-12 */\n[data-edrv-view] .edrv-tree-status { flex: 0 0 auto; min-width: 0; max-height: 96px; overflow: auto; padding: 4px 8px; box-sizing: border-box; line-height: 18px; white-space: normal; overflow-wrap: anywhere; 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/* .edrv-svn-ch-* 动作词表补全:replaced(替换)= 琥珀;侧栏状态分级(SVN_STATUS_TONE)不受影响 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svn-ch-replaced { color: var(--dsw-alias-state-warn-primary, #d97706); }\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: 10px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-svnlog-title { margin: 0; font-size: 16px; font-weight: 650; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n/* 头部目标路径 = 代码 chip(mono + 底色 + 全圆角;补丁/目录对比弹窗共用同视觉) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-target { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n/* 关闭按钮图标化(透明底,hover 显形;dialog 前缀提高特异性,压过底部按钮统一尺寸) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svnlog-head .edrv-svn-act { border-color: transparent; background: transparent; width: 24px; height: 24px; padding: 0; font-size: 12px; line-height: 1; }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svnlog-head .edrv-svn-act:hover { border-color: var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }\n/* 过滤栏:浅色表面条 + 统一 24px 控件 + 焦点边框变色 + 日期组靠右 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-filter { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-field { flex: 0 0 auto; height: 24px; box-sizing: border-box; padding: 0 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; height: 24px; box-sizing: border-box; padding: 0 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-field:focus, [data-edrv-view] .edrv-svnlog-search:focus, [data-edrv-view] .edrv-svnlog-date:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-dates { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }\n[data-edrv-view] .edrv-svnlog-date { height: 24px; box-sizing: border-box; padding: 0 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-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-check input[type=\"checkbox\"] { margin: 0; cursor: pointer; accent-color: var(--dsw-alias-brand-primary, #2563eb); }\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/* 上段修订列表(13-log-ui-polish 变更台账):\n 1px 发丝边框 + 底色面 + 左侧时间脊线(每行 ::before 画节点圆点、行盒左缘 ::after 连线段,\n 行行相接即整列脊线;选中行节点实心着色 + 2px 左强调条)。列宽由 cell 类与 nth-child 对齐(P0-12/D30)。 */\n[data-edrv-view] .edrv-svnlog-list { position: relative; flex: 0 0 auto; display: flex; flex-direction: column; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 0 4px 4px; background: var(--dsw-alias-bg-layer-1, #fff); }\n/* 列头 sticky:滚动常驻 + 实底防透;z-index 20 低于一切浮层(190 起),仅在列表滚动容器内生效 */\n[data-edrv-view] .edrv-svnlog-cols { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-tertiary, #9aa5b1); 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/* 行:position:relative 承载脊线节点(::before)与连线段(::after);选中 = 强调条 + 节点实心 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-item { position: relative; display: flex; align-items: center; gap: 6px; padding: 3px 6px 3px 16px; border-radius: 6px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-item::before { content: ''; position: absolute; left: 6px; top: 50%; transform: translateY(-50%); width: 7px; height: 7px; box-sizing: border-box; border-radius: 50%; border: 1.5px solid var(--dsw-alias-border-l2, #cbd2d9); background: var(--dsw-alias-bg-layer-1, #fff); z-index: 1; }\n[data-edrv-view] .edrv-svnlog-item::after { content: ''; position: absolute; left: 9px; top: 0; bottom: 0; width: 1px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-item:last-child::after { bottom: 50%; }\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)); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-item.edrv-svnlog-item-on::before { border-color: var(--dsw-alias-brand-primary, #2563eb); background: var(--dsw-alias-brand-primary, #2563eb); }\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); font-variant-numeric: tabular-nums; }\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: 14px; height: 14px; border-radius: 4px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; }\n/* 动作语义色(13-log-ui-polish 归位):A=success 绿 · D=error 红 · M=brand 蓝 · R=warn 琥珀 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-badge-modified { color: var(--dsw-alias-state-info-primary, #2f6fed); background: rgba(47,111,237,.14); }\n[data-edrv-view] .edrv-svnlog-badge-added { color: var(--dsw-alias-state-success-primary, #0f9d58); background: rgba(15,157,88,.14); }\n[data-edrv-view] .edrv-svnlog-badge-deleted { color: var(--dsw-alias-state-error-primary, #d9534f); background: rgba(217,83,79,.14); }\n[data-edrv-view] .edrv-svnlog-badge-replaced { color: var(--dsw-alias-state-warn-primary, #d97706); background: rgba(217,119,6,.16); }\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 公共外观(13-log-ui-polish:2px 粗框 → 1px 发丝线 + 面板底色) */\n[data-edrv-view] .edrv-svnlog-pane { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 8px 10px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, #fff); }\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/* 提交信息 meta 行:rev/日期 chip 化,author 常规字重(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-meta .edrv-svnlog-rev { padding: 1px 8px; border-radius: 999px; background: var(--dsw-alias-state-info-primary, #2f6fed); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 11px; font-weight: 700; }\n[data-edrv-view] .edrv-svnlog-meta .edrv-svnlog-date { height: auto; padding: 1px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-date { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 正文块:左侧品牌强调线 +1.7 行高(长提交信息可读性) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-msgpane pre.edrv-svnlog-body { margin: 8px 0 0; padding: 8px 10px; border-radius: 0 6px 6px 0; border-left: 2px solid var(--dsw-alias-state-info-primary, #2f6fed); background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11.5px; line-height: 1.7; white-space: pre-wrap; word-break: break-word; overflow: auto; flex: 1 1 auto; min-height: 0; }\n/* P0-14 外框尺寸手柄(13-log-ui-polish 增量3 八向:n/s/w/e 四边 + nw/ne/sw/se 四角);\n 热区避开内边距带(边8px/角14px 落在卡片22/24px padding 内不遮内容),角手柄后渲染盖过相邻边2px overlap。\n chevron 角标四向各自朝向,hover 显形。 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-grip { position: absolute; z-index: 2; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-grip-n { top: 0; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-s { bottom: 0; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-w { left: 0; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-e { right: 0; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-nw { left: 0; top: 0; width: 14px; height: 14px; cursor: nwse-resize; }\n[data-edrv-view] .edrv-svnlog-grip-ne { right: 0; top: 0; width: 14px; height: 14px; cursor: nesw-resize; }\n[data-edrv-view] .edrv-svnlog-grip-sw { left: 0; bottom: 0; width: 14px; height: 14px; cursor: nesw-resize; }\n[data-edrv-view] .edrv-svnlog-grip-se { right: 0; bottom: 0; width: 14px; height: 14px; cursor: nwse-resize; background: transparent; }\n[data-edrv-view] .edrv-svnlog-grip-nw::after,\n[data-edrv-view] .edrv-svnlog-grip-ne::after,\n[data-edrv-view] .edrv-svnlog-grip-sw::after,\n[data-edrv-view] .edrv-svnlog-grip-se::after { content: ''; position: absolute; width: 6px; height: 6px; opacity: .5; border-color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-grip-se::after { right: 3px; bottom: 3px; border-right: 1.5px solid; border-bottom: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-sw::after { left: 3px; bottom: 3px; border-left: 1.5px solid; border-bottom: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-ne::after { right: 3px; top: 3px; border-right: 1.5px solid; border-top: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-nw::after { left: 3px; top: 3px; border-left: 1.5px solid; border-top: 1.5px solid; }\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-grip:hover::after, [data-edrv-view] .edrv-svnlog-grip:active::after { opacity: 1; border-color: var(--dsw-alias-label-primary, #1f2933); }\n/* 行反馈微动效(仅 hover/选中背景过渡;用户要求减少动效时关闭) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-item { transition: background-color .12s ease, box-shadow .12s ease; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-svnlog-item { transition: none; }\n}\n/*13-log-ui-polish 增量:行容器可滚动(pane 仍 overflow:hidden 裁圆角),列头不被压缩 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-paths-head { display: flex; align-items: center; gap: 6px; flex: none; font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-tertiary, #9aa5b1); margin-bottom: 4px; padding-bottom: 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-hint { font-weight: 400; letter-spacing: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-paths { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-height: 0; overflow: auto; }\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-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); }\n/* 路径两段着色:目录段弱化 + 文件名加重(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-pdir { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-pbase { color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\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/* 底部:状态行与按钮组分层 + 组间发丝线(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-footwrap { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; padding-top: 6px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-btns { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnlog-btngroup { display: inline-flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnlog-btngroup + .edrv-svnlog-btngroup { margin-left: 6px; padding-left: 10px; border-left: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svn-act,\n[data-edrv-view] .edrv-svnlog-range .edrv-svn-act,\n[data-edrv-view] .edrv-svnlog-stats .edrv-svn-act { font-size: 11px; padding: 2px 9px; }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svn-act:focus-visible,\n[data-edrv-view] .edrv-svnlog-range .edrv-svn-act:focus-visible,\n[data-edrv-view] .edrv-svnlog-stats .edrv-svn-act:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2563eb); outline-offset: 1px; }\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 更新窗口:逐条状态与路径是主信息;跟随 DSH 主题令牌。 */\n[data-edrv-view] .edrv-update-dialog { display: flex; flex-direction: column; padding: 0; border-radius: 8px; overflow: hidden; background: var(--dsw-alias-bg-overlay, #fff); }\n[data-edrv-view] .edrv-update-head { display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 0 16px; border-bottom: 1px solid var(--dsw-alias-border-l1, #d9e0e4); }\n[data-edrv-view] .edrv-update-heading { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }\n[data-edrv-view] .edrv-update-heading h3 { margin: 0; flex: none; font-size: 15px; }\n[data-edrv-view] .edrv-update-target { min-width: 0; color: var(--dsw-alias-label-tertiary, #65737c); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }\n[data-edrv-view] .edrv-update-phase { flex: none; font-size: 12px; color: var(--dsw-alias-label-secondary, #485762); }\n[data-edrv-view] .edrv-update-close { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border: 0; border-radius: 4px; background: none; color: var(--dsw-alias-label-secondary, #485762); cursor: pointer; }\n[data-edrv-view] .edrv-update-close:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-update-close:not(:disabled):hover { background: var(--dsw-alias-interactive-bg-hover, #e7edf0); }\n[data-edrv-view] .edrv-update-table { display: flex; flex-direction: column; flex: 1; min-height: 0; margin: 12px 14px 0; border: 1px solid var(--dsw-alias-border-l1, #d9e0e4); border-radius: 5px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-update-cols, [data-edrv-view] .edrv-update-row { display: grid; grid-template-columns: 110px minmax(0, 1fr) 160px; align-items: center; }\n[data-edrv-view] .edrv-update-sort { width: 100%; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }\n[data-edrv-view] .edrv-update-sort:hover { color: var(--dsw-alias-brand-primary, #2869b4); }\n[data-edrv-view] .edrv-update-sort:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 2px; }\n[data-edrv-view] .edrv-update-jump { color: var(--dsw-alias-state-error-primary, #b93b43); }\n[data-edrv-view] .edrv-update-cols { flex: none; height: 30px; background: var(--dsw-alias-bg-layer-2, #f1f4f5); border-bottom: 1px solid var(--dsw-alias-border-l1, #d9e0e4); color: var(--dsw-alias-label-secondary, #485762); font-size: 11px; font-weight: 600; }\n[data-edrv-view] .edrv-update-cols > span, [data-edrv-view] .edrv-update-row > span { min-width: 0; padding: 0 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-update-cols > span + span, [data-edrv-view] .edrv-update-row > span + span { border-left: 1px solid var(--dsw-alias-border-l1, #d9e0e4); }\n[data-edrv-view] .edrv-update-list { position: relative; flex: 1; min-height: 0; overflow: auto; scrollbar-width: thin; }\n[data-edrv-view] .edrv-update-space { position: relative; }\n[data-edrv-view] .edrv-update-pos { position: absolute; left: 0; right: 0; }\n[data-edrv-view] .edrv-update-row { height: 100%; font-size: 11px; cursor: default; color: var(--dsw-alias-label-primary, #202932); }\n[data-edrv-view] .edrv-update-row:hover { background: var(--dsw-alias-interactive-bg-hover, #ecf3f9); }\n[data-edrv-view] .edrv-update-row.is-selected { background: var(--dsw-alias-interactive-bg-hover, #dceaf9); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #2869b4); }\n[data-edrv-view] .edrv-update-action { font-weight: 600; }\n[data-edrv-view] .edrv-update-action.tone-added { color: var(--dsw-alias-state-success-primary, #23704e); }\n[data-edrv-view] .edrv-update-action.tone-deleted, [data-edrv-view] .edrv-update-action.tone-conflict { color: var(--dsw-alias-state-error-primary, #b93b43); }\n[data-edrv-view] .edrv-update-action.tone-merged { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-action.tone-updated { color: var(--dsw-alias-state-info-primary, #2869b4); }\n[data-edrv-view] .edrv-update-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-update-type { color: var(--dsw-alias-label-tertiary, #65737c); }\n[data-edrv-view] .edrv-update-empty { position: absolute; top: 50%; left: 0; right: 0; transform: translateY(-50%); text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary, #65737c); pointer-events: none; }\n[data-edrv-view] .edrv-update-telemetry { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px; padding: 10px 15px; min-height: 43px; box-sizing: border-box; font-size: 11px; color: var(--dsw-alias-label-secondary, #485762); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-update-error { color: var(--dsw-alias-state-error-primary, #b93b43); overflow-wrap: anywhere; }\n[data-edrv-view] .edrv-update-warning { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-locked { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-foot .edrv-update-cleanup:not(:disabled) { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-foot { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--dsw-alias-border-l1, #d9e0e4); font-size: 11px; }\n[data-edrv-view] .edrv-update-counts { flex: 1; min-width: 0; color: var(--dsw-alias-label-secondary, #485762); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act { flex: none; min-width: 74px; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act:focus-visible, [data-edrv-view] .edrv-update-close:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 2px; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-update-raw { max-height: 120px; overflow: auto; padding: 0 14px 10px; font-size: 11px; }\n[data-edrv-view] .edrv-update-raw pre { white-space: pre-wrap; overflow-wrap: anywhere; margin: 5px 0; }\n@media (max-width: 620px) {\n [data-edrv-view] .edrv-update-head { gap: 6px; padding: 0 10px; }\n [data-edrv-view] .edrv-update-table { margin: 8px 8px 0; }\n [data-edrv-view] .edrv-update-cols, [data-edrv-view] .edrv-update-row { grid-template-columns: 76px minmax(0, 1fr); }\n [data-edrv-view] .edrv-update-cols > span:nth-child(3), [data-edrv-view] .edrv-update-row > span:nth-child(3) { display: none; }\n [data-edrv-view] .edrv-update-path { white-space: normal !important; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }\n [data-edrv-view] .edrv-update-foot { flex-wrap: wrap; padding: 8px; }\n [data-edrv-view] .edrv-update-counts { flex-basis: 100%; }\n}\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); }\n\n/* Workbench panes: existing theme, action-driven drop previews. ddj 2026年10月14号 */\n[data-edrv-view] .edrv-group, [data-edrv-view] .edrv-group-body,\n[data-edrv-view] .edrv-full-slot, [data-edrv-view] .edrv-full-view,\n[data-edrv-view] .edrv-passive-view { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-group { border: 0; }\n[data-edrv-view] .edrv-group .edrv-tabs { flex: 0 0 auto; touch-action: none; }\n[data-edrv-view] .edrv-group-tab-name { overflow: hidden; text-overflow: ellipsis; max-width: 180px; }\n[data-edrv-view] .edrv-split { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-split-vertical { flex-direction: column; }\n[data-edrv-view] .edrv-split-child { display: flex; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-split-handle { flex: 0 0 5px; background: var(--dsw-alias-border-l1, #e0e6e8); cursor: col-resize; touch-action: none; }\n[data-edrv-view] .edrv-split-vertical > .edrv-split-handle { cursor: row-resize; }\n[data-edrv-view] .edrv-split-handle:hover, [data-edrv-view] .edrv-split-handle:focus-visible { background: var(--dsw-alias-state-info-primary, #4d6bfe); }\n[data-edrv-view].edrv-floating { position: fixed; display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-base, #fff); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: 0 8px 28px rgba(0,0,0,.22); }\n[data-edrv-view] .edrv-floating-title { display: flex; align-items: center; gap: 8px; flex: 0 0 32px; padding: 0 8px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); cursor: move; touch-action: none; user-select: none; }\n[data-edrv-view] .edrv-floating-title > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-floating-content { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; overflow: auto; box-sizing: border-box; }\n[data-edrv-view] .edrv-floating-content.edrv-svnlog-dialog { padding: 12px; overflow: auto; }\n[data-edrv-view] .edrv-floating-content.edrv-svn-patch-dialog,\n[data-edrv-view] .edrv-floating-content.edrv-svn-sum-dialog,\n[data-edrv-view] .edrv-floating-content.edrv-svnplan-dialog { padding: 12px; max-width: none; gap: 8px; }\n[data-edrv-view] .edrv-floating-content.edrv-svnlog-stats { max-height: none; }\n[data-edrv-view] .edrv-floating-content .edrv-svn-patch-pre,\n[data-edrv-view] .edrv-floating-content .edrv-svn-sum-list { flex: 1; min-height: 0; max-height: none; }\n[data-edrv-view] .edrv-floating-content .edrv-svnlog-filter,\n[data-edrv-view] .edrv-floating-content .edrv-svn-sum-form { flex-wrap: wrap; }\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-head,\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-filter,\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-foot { flex-shrink: 0; }\n[data-edrv-view] .edrv-floating-title > button { border: 0; background: transparent; color: inherit; cursor: pointer; }\n[data-edrv-view] .edrv-floating-grip { position: absolute; width: 16px; height: 16px; bottom: 0; right: 0; cursor: nwse-resize; touch-action: none; border-right: 3px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 3px solid var(--dsw-alias-border-l2, #cdd6da); }\n.edrv-drag-overlay { position: fixed; z-index: 150; pointer-events: none; box-sizing: border-box; border: 0; background: color-mix(in srgb, var(--dsw-alias-state-info-primary, #4d6bfe) 25%, transparent); }\n.edrv-drop-label, .edrv-drag-ghost { position: fixed; z-index: 151; pointer-events: none; padding: 4px 8px; background: var(--dsw-alias-bg-overlay, #fff); color: var(--dsw-alias-label-primary, #1f2933); border: 1px solid var(--dsw-alias-border-l2, #cdd6da); font-size: 12px; white-space: nowrap; }\n.edrv-drop-label { position: absolute; left: 4px; top: 4px; }\n[data-edrv-view] .edrv-tab:focus-visible { outline: 2px solid var(--dsw-alias-state-info-primary, #4d6bfe); outline-offset: -2px; }\n@media (prefers-reduced-motion: reduce) { .edrv-drag-overlay { transition: none; } }\n";
|
|
37
37
|
const tagId$1 = "dsh-vscode-mode/editor.css";
|
|
38
38
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
39
39
|
const tag = document.createElement("style");
|
|
@@ -199,7 +199,7 @@ window.__ModuleLoader__.load({
|
|
|
199
199
|
* 作者 ddj 2026-08-28
|
|
200
200
|
*/
|
|
201
201
|
/** localStorage 键前缀(按工作区作用域隔离,无 cwd 会话回退会话键)。 */
|
|
202
|
-
const KEY_PREFIX$
|
|
202
|
+
const KEY_PREFIX$4 = CACHE_KEY.viewstate;
|
|
203
203
|
/**
|
|
204
204
|
* 判断对象是否为合法的 Monaco view state(保守形状校验,非深校验)。
|
|
205
205
|
* @author ddj 2026年08月28号
|
|
@@ -277,7 +277,7 @@ window.__ModuleLoader__.load({
|
|
|
277
277
|
*/
|
|
278
278
|
function viewStatesLoad(scope) {
|
|
279
279
|
try {
|
|
280
|
-
return parseViewStates(window.localStorage.getItem(KEY_PREFIX$
|
|
280
|
+
return parseViewStates(window.localStorage.getItem(KEY_PREFIX$4 + String(scope))) ?? {};
|
|
281
281
|
} catch (error) {
|
|
282
282
|
return {};
|
|
283
283
|
}
|
|
@@ -290,7 +290,7 @@ window.__ModuleLoader__.load({
|
|
|
290
290
|
*/
|
|
291
291
|
function viewStatesSave(scope, states) {
|
|
292
292
|
try {
|
|
293
|
-
window.localStorage.setItem(KEY_PREFIX$
|
|
293
|
+
window.localStorage.setItem(KEY_PREFIX$4 + String(scope), serializeViewStates(states));
|
|
294
294
|
} catch (error) {}
|
|
295
295
|
}
|
|
296
296
|
//#endregion
|
|
@@ -352,8 +352,10 @@ window.__ModuleLoader__.load({
|
|
|
352
352
|
/** @public @author ddj 2026年10月14号 @param props Group callbacks and file view. @returns Accessible tab strip and drop target. */
|
|
353
353
|
function EditorPane(props) {
|
|
354
354
|
const { group } = props;
|
|
355
|
-
|
|
355
|
+
const suppressActive = props.suppressActive === true;
|
|
356
|
+
/** @private @author ddj 2026年10月20号 @param tab File record. @returns Draggable tab with isolated close control. */
|
|
356
357
|
function renderTab(tab) {
|
|
358
|
+
const selected = !suppressActive && tab.path === group.active;
|
|
357
359
|
/** @private @author ddj 2026年10月14号 @param event Primary press. Start trusted file drag. */
|
|
358
360
|
function press(event) {
|
|
359
361
|
props.onDrag(tab.path, event);
|
|
@@ -389,8 +391,8 @@ window.__ModuleLoader__.load({
|
|
|
389
391
|
"data-edrv-tab-path": tab.path,
|
|
390
392
|
role: "tab",
|
|
391
393
|
tabIndex: 0,
|
|
392
|
-
"aria-selected":
|
|
393
|
-
className: "edrv-tab" + (
|
|
394
|
+
"aria-selected": selected,
|
|
395
|
+
className: "edrv-tab" + (selected ? " edrv-tab-active" : "") + (tab.pinned ? " edrv-tab-pinned" : ""),
|
|
394
396
|
title: tab.path + (tab.pinned ? "(已固定)" : ""),
|
|
395
397
|
onPointerDown: press,
|
|
396
398
|
onClick: select,
|
|
@@ -416,11 +418,12 @@ window.__ModuleLoader__.load({
|
|
|
416
418
|
className: "edrv-group" + (props.focused ? " edrv-group-focused" : ""),
|
|
417
419
|
"data-edrv-group": group.id,
|
|
418
420
|
onPointerDown: focusEmpty
|
|
419
|
-
}, react.default.createElement("div", {
|
|
421
|
+
}, react.default.createElement("div", { className: "edrv-tabs-row" }, react.default.createElement("div", {
|
|
420
422
|
className: "edrv-tabs",
|
|
421
423
|
role: "tablist",
|
|
422
|
-
"data-edrv-group-tabs": group.id
|
|
423
|
-
|
|
424
|
+
"data-edrv-group-tabs": group.id,
|
|
425
|
+
ref: props.tabsRef
|
|
426
|
+
}, group.tabs.map(renderTab), props.extras), props.tools), react.default.createElement("div", {
|
|
424
427
|
className: "edrv-group-body",
|
|
425
428
|
role: "tabpanel"
|
|
426
429
|
}, props.children));
|
|
@@ -1502,6 +1505,236 @@ window.__ModuleLoader__.load({
|
|
|
1502
1505
|
actions
|
|
1503
1506
|
}, props.children);
|
|
1504
1507
|
}
|
|
1508
|
+
const TEXT_VIEW_BYTES = 8388608;
|
|
1509
|
+
//#endregion
|
|
1510
|
+
//#region src/client/textPages.ts
|
|
1511
|
+
/**
|
|
1512
|
+
* Own one preview identity and one in-flight request; no polling or automatic continuation.
|
|
1513
|
+
* @public @author ddj 2026年10月22号
|
|
1514
|
+
* @param load Authorized RPC loader. @param changed Snapshot observer.
|
|
1515
|
+
* @returns Pager lifecycle and explicit load controls.
|
|
1516
|
+
*/
|
|
1517
|
+
function createTextPager(load, changed) {
|
|
1518
|
+
let generation = 0;
|
|
1519
|
+
let disposed = false;
|
|
1520
|
+
let target = null;
|
|
1521
|
+
let cursor;
|
|
1522
|
+
let version;
|
|
1523
|
+
let inFlight = false;
|
|
1524
|
+
let retainedBytes = 0;
|
|
1525
|
+
let state = emptyState();
|
|
1526
|
+
/** @private @author ddj 2026年10月22号 @returns Publish immutable snapshot. */
|
|
1527
|
+
function publish() {
|
|
1528
|
+
changed({ ...state });
|
|
1529
|
+
}
|
|
1530
|
+
/** @public @author ddj 2026年10月22号 @param args New file/session identity. @returns Reset state; old responses are ignored. */
|
|
1531
|
+
function reset(args) {
|
|
1532
|
+
generation++;
|
|
1533
|
+
target = {
|
|
1534
|
+
sessionId: args.sessionId,
|
|
1535
|
+
path: args.path
|
|
1536
|
+
};
|
|
1537
|
+
cursor = void 0;
|
|
1538
|
+
version = void 0;
|
|
1539
|
+
retainedBytes = 0;
|
|
1540
|
+
state = emptyState();
|
|
1541
|
+
state.loading = inFlight;
|
|
1542
|
+
if (!disposed) publish();
|
|
1543
|
+
}
|
|
1544
|
+
/** @public @author ddj 2026年10月22号 @returns Explicitly load one page, or no-op when busy/finished. */
|
|
1545
|
+
async function loadNext() {
|
|
1546
|
+
if (disposed || !target || inFlight || state.eof || state.capped) return;
|
|
1547
|
+
state.error = null;
|
|
1548
|
+
const requestGeneration = generation;
|
|
1549
|
+
const request = {
|
|
1550
|
+
...target,
|
|
1551
|
+
cursor
|
|
1552
|
+
};
|
|
1553
|
+
inFlight = true;
|
|
1554
|
+
state.loading = true;
|
|
1555
|
+
publish();
|
|
1556
|
+
try {
|
|
1557
|
+
const page = await load(request);
|
|
1558
|
+
if (disposed || requestGeneration !== generation) return;
|
|
1559
|
+
if (!page.ok) {
|
|
1560
|
+
state.error = page.error;
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
if (!validPage(page, state.offset, version)) {
|
|
1564
|
+
state.error = "Invalid or stale text page";
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
appendPage(page);
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
if (!disposed && requestGeneration === generation) state.error = String(error);
|
|
1570
|
+
} finally {
|
|
1571
|
+
inFlight = false;
|
|
1572
|
+
if (!disposed) {
|
|
1573
|
+
state.loading = false;
|
|
1574
|
+
publish();
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/** @private @author ddj 2026年10月22号 @param page Verified page. @returns Append only within byte/line limits. */
|
|
1579
|
+
function appendPage(page) {
|
|
1580
|
+
let text = page.text;
|
|
1581
|
+
const encoded = new TextEncoder().encode(text);
|
|
1582
|
+
const remaining = TEXT_VIEW_BYTES - retainedBytes;
|
|
1583
|
+
if (encoded.length > remaining) {
|
|
1584
|
+
let end = remaining;
|
|
1585
|
+
while (end > 0 && (encoded[end] & 192) === 128) end--;
|
|
1586
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(encoded.subarray(0, end));
|
|
1587
|
+
state.capped = true;
|
|
1588
|
+
}
|
|
1589
|
+
let lines = state.lines;
|
|
1590
|
+
for (let index = 0; index < text.length; index++) {
|
|
1591
|
+
if (text[index] !== "\n" && (text[index] !== "\r" || text[index + 1] === "\n")) continue;
|
|
1592
|
+
if (lines >= 5e4) {
|
|
1593
|
+
const end = text[index] === "\n" && text[index - 1] === "\r" ? index - 1 : index;
|
|
1594
|
+
text = text.slice(0, end);
|
|
1595
|
+
state.capped = true;
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
lines++;
|
|
1599
|
+
}
|
|
1600
|
+
retainedBytes += new TextEncoder().encode(text).length;
|
|
1601
|
+
state.text += text;
|
|
1602
|
+
state.lines = lines;
|
|
1603
|
+
state.offset = page.offset;
|
|
1604
|
+
state.size = page.size;
|
|
1605
|
+
state.eof = page.eof;
|
|
1606
|
+
state.partialLine = page.partialLine || state.capped;
|
|
1607
|
+
state.capped ||= page.offset >= 8388608 || retainedBytes >= 8388608 || lines >= 5e4;
|
|
1608
|
+
version = page.version;
|
|
1609
|
+
cursor = page.nextCursor ?? void 0;
|
|
1610
|
+
}
|
|
1611
|
+
/** @public @author ddj 2026年10月22号 @returns Invalidate identity while retaining the transport lock. */
|
|
1612
|
+
function invalidate() {
|
|
1613
|
+
generation++;
|
|
1614
|
+
target = null;
|
|
1615
|
+
state = emptyState();
|
|
1616
|
+
}
|
|
1617
|
+
/** @public @author ddj 2026年10月22号 @returns Invalidate pending responses and release preview content. */
|
|
1618
|
+
function dispose() {
|
|
1619
|
+
disposed = true;
|
|
1620
|
+
invalidate();
|
|
1621
|
+
}
|
|
1622
|
+
return {
|
|
1623
|
+
reset,
|
|
1624
|
+
loadNext,
|
|
1625
|
+
invalidate,
|
|
1626
|
+
dispose,
|
|
1627
|
+
getState: () => ({ ...state })
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
/** @private @author ddj 2026年10月22号 @returns Empty bounded preview state. */
|
|
1631
|
+
function emptyState() {
|
|
1632
|
+
return {
|
|
1633
|
+
text: "",
|
|
1634
|
+
loading: false,
|
|
1635
|
+
eof: false,
|
|
1636
|
+
capped: false,
|
|
1637
|
+
error: null,
|
|
1638
|
+
offset: 0,
|
|
1639
|
+
size: 0,
|
|
1640
|
+
lines: 1,
|
|
1641
|
+
partialLine: false
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Validate the RPC envelope before retaining any content.
|
|
1646
|
+
* @private @author ddj 2026年10月22号
|
|
1647
|
+
* @param page Server page. @param offset Previous byte position. @param version Bound version.
|
|
1648
|
+
* @returns Whether bounds and continuation invariants hold.
|
|
1649
|
+
*/
|
|
1650
|
+
function validPage(page, offset, version) {
|
|
1651
|
+
return typeof page.text === "string" && typeof page.version === "string" && (version === void 0 || version === page.version) && Number.isSafeInteger(page.offset) && Number.isSafeInteger(page.size) && page.size >= 0 && page.offset >= offset && page.offset <= page.size && (page.offset > offset || page.eof) && page.offset - offset <= 262144 && new TextEncoder().encode(page.text).length <= 262145 && page.encoding === "utf-8" && typeof page.partialLine === "boolean" && typeof page.eof === "boolean" && (page.eof ? page.nextCursor === null : typeof page.nextCursor === "string" && page.nextCursor.length > 0) && page.eof === (page.offset === page.size);
|
|
1652
|
+
}
|
|
1653
|
+
//#endregion
|
|
1654
|
+
//#region src/client/ui/LargeTextView.ts
|
|
1655
|
+
const Button$1 = _deepseek_ai_dsh_client_ui_primitives.Button;
|
|
1656
|
+
/**
|
|
1657
|
+
* Read-only paged text surface: never creates a Monaco model or exposes a save action.
|
|
1658
|
+
* @public @author ddj 2026年10月22号
|
|
1659
|
+
* @param props Exact session/file identity and authorized page loader.
|
|
1660
|
+
* @returns Bounded selectable text with explicit continuation and status.
|
|
1661
|
+
*/
|
|
1662
|
+
function LargeTextView(props) {
|
|
1663
|
+
const [snapshot, setSnapshot] = react.default.useState(null);
|
|
1664
|
+
const [identity, setIdentity] = react.default.useState("");
|
|
1665
|
+
const pagerRef = react.default.useRef(null);
|
|
1666
|
+
const loaderRef = react.default.useRef(props.loadPage);
|
|
1667
|
+
const mountedRef = react.default.useRef(false);
|
|
1668
|
+
const firstPending = react.default.useRef(false);
|
|
1669
|
+
loaderRef.current = props.loadPage;
|
|
1670
|
+
const currentIdentity = JSON.stringify([props.sessionId, props.path]);
|
|
1671
|
+
/** @private @author ddj 2026年10月22号 @param args Request identity. @returns Current injected loader response. */
|
|
1672
|
+
function loadPage(args) {
|
|
1673
|
+
return loaderRef.current(args);
|
|
1674
|
+
}
|
|
1675
|
+
/** @private @author ddj 2026年10月22号 @param state Latest pager snapshot. @returns Publish only while mounted. */
|
|
1676
|
+
function onSnapshot(state) {
|
|
1677
|
+
if (!mountedRef.current) return;
|
|
1678
|
+
setSnapshot(state);
|
|
1679
|
+
if (!state.loading && firstPending.current) queueMicrotask(loadFirst);
|
|
1680
|
+
}
|
|
1681
|
+
/** @private @author ddj 2026年10月22号 @description Start only the new identity's deferred first page, never automatic continuation. */
|
|
1682
|
+
function loadFirst() {
|
|
1683
|
+
const pager = pagerRef.current;
|
|
1684
|
+
if (!mountedRef.current || !firstPending.current || !pager || pager.getState().loading || typeof document !== "undefined" && document.hidden) return;
|
|
1685
|
+
firstPending.current = false;
|
|
1686
|
+
pager.loadNext();
|
|
1687
|
+
}
|
|
1688
|
+
/** @private @author ddj 2026年10月22号 @returns Bind identity without replacing the single transport lock. */
|
|
1689
|
+
function mountPager() {
|
|
1690
|
+
mountedRef.current = true;
|
|
1691
|
+
if (!pagerRef.current) pagerRef.current = createTextPager(loadPage, onSnapshot);
|
|
1692
|
+
const pager = pagerRef.current;
|
|
1693
|
+
setIdentity(currentIdentity);
|
|
1694
|
+
firstPending.current = true;
|
|
1695
|
+
pager.reset({
|
|
1696
|
+
sessionId: props.sessionId,
|
|
1697
|
+
path: props.path
|
|
1698
|
+
});
|
|
1699
|
+
loadFirst();
|
|
1700
|
+
if (typeof document !== "undefined") document.addEventListener("visibilitychange", loadFirst);
|
|
1701
|
+
/** @private @author ddj 2026年10月22号 @returns Release retained text and ignore pending responses. */
|
|
1702
|
+
function unmountPager() {
|
|
1703
|
+
mountedRef.current = false;
|
|
1704
|
+
firstPending.current = false;
|
|
1705
|
+
if (typeof document !== "undefined") document.removeEventListener("visibilitychange", loadFirst);
|
|
1706
|
+
pager.invalidate();
|
|
1707
|
+
}
|
|
1708
|
+
return unmountPager;
|
|
1709
|
+
}
|
|
1710
|
+
react.default.useEffect(mountPager, [props.sessionId, props.path]);
|
|
1711
|
+
/** @private @author ddj 2026年10月22号 @returns Load exactly one additional page on user intent. */
|
|
1712
|
+
function loadNext() {
|
|
1713
|
+
if (typeof document !== "undefined" && document.hidden) return;
|
|
1714
|
+
if (firstPending.current) {
|
|
1715
|
+
loadFirst();
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
pagerRef.current?.loadNext();
|
|
1719
|
+
}
|
|
1720
|
+
const state = identity === currentIdentity ? snapshot : null;
|
|
1721
|
+
let status = "正在加载只读文本…";
|
|
1722
|
+
if (state?.error) status = state.error;
|
|
1723
|
+
else if (state?.capped) status = "已达预览上限(8 MiB / 50,000 行),请使用外部编辑器查看完整文件";
|
|
1724
|
+
else if (state?.eof) status = "文件末尾 · 只读";
|
|
1725
|
+
else if (state) status = `已读取 ${state.offset.toLocaleString()} / ${state.size.toLocaleString()} 字节 · 只读`;
|
|
1726
|
+
if (state?.partialLine) status += " · 末行尚未完整";
|
|
1727
|
+
return react.default.createElement("section", {
|
|
1728
|
+
className: "edrv-large-text",
|
|
1729
|
+
"aria-label": "大文件只读预览"
|
|
1730
|
+
}, react.default.createElement("div", { className: "edrv-large-text-toolbar" }, react.default.createElement("span", { role: "status" }, status), react.default.createElement(Button$1, {
|
|
1731
|
+
onClick: loadNext,
|
|
1732
|
+
disabled: !state || state.loading || state.eof || state.capped
|
|
1733
|
+
}, state?.loading ? "加载中…" : state?.error ? "重试本段" : "加载下一段")), react.default.createElement("pre", {
|
|
1734
|
+
className: "edrv-large-text-content",
|
|
1735
|
+
tabIndex: 0
|
|
1736
|
+
}, state?.text ?? ""));
|
|
1737
|
+
}
|
|
1505
1738
|
//#endregion
|
|
1506
1739
|
//#region src/client/tabDrag.ts
|
|
1507
1740
|
/** Internal pointer drag: never converts external text into file paths. @author ddj 2026年10月14号 */
|
|
@@ -8430,7 +8663,7 @@ window.__ModuleLoader__.load({
|
|
|
8430
8663
|
//#endregion
|
|
8431
8664
|
//#region src/client/dap/breakpoints.ts
|
|
8432
8665
|
/** localStorage key 前缀。 */
|
|
8433
|
-
const KEY_PREFIX$
|
|
8666
|
+
const KEY_PREFIX$3 = "edrv.dap.bp.";
|
|
8434
8667
|
/**
|
|
8435
8668
|
* 读断点表(缺失/损坏返回空表)。
|
|
8436
8669
|
* @author ddj 2026年09月29号
|
|
@@ -8438,7 +8671,7 @@ window.__ModuleLoader__.load({
|
|
|
8438
8671
|
*/
|
|
8439
8672
|
function loadBpMap(scope) {
|
|
8440
8673
|
try {
|
|
8441
|
-
const raw = localStorage.getItem(KEY_PREFIX$
|
|
8674
|
+
const raw = localStorage.getItem(KEY_PREFIX$3 + scope);
|
|
8442
8675
|
if (!raw) return {};
|
|
8443
8676
|
const data = JSON.parse(raw);
|
|
8444
8677
|
if (!data || typeof data !== "object" || Array.isArray(data)) return {};
|
|
@@ -8474,8 +8707,8 @@ window.__ModuleLoader__.load({
|
|
|
8474
8707
|
*/
|
|
8475
8708
|
function saveBpMap(scope, map) {
|
|
8476
8709
|
try {
|
|
8477
|
-
if (!Object.keys(map).length) localStorage.removeItem(KEY_PREFIX$
|
|
8478
|
-
else localStorage.setItem(KEY_PREFIX$
|
|
8710
|
+
if (!Object.keys(map).length) localStorage.removeItem(KEY_PREFIX$3 + scope);
|
|
8711
|
+
else localStorage.setItem(KEY_PREFIX$3 + scope, JSON.stringify(map));
|
|
8479
8712
|
} catch {}
|
|
8480
8713
|
}
|
|
8481
8714
|
/**
|
|
@@ -8582,23 +8815,6 @@ window.__ModuleLoader__.load({
|
|
|
8582
8815
|
[path]: list
|
|
8583
8816
|
};
|
|
8584
8817
|
}
|
|
8585
|
-
/**
|
|
8586
|
-
* 推导断点行的跳转目标(调试面板点击断点项 → 打开文件并定位到断点行)。
|
|
8587
|
-
* 路径缺失或行号非 ≥1 的有限数时返回 null(提前返回,不发起无效导航)。
|
|
8588
|
-
* @author ddj 2026年09月21号
|
|
8589
|
-
* @param row 面板行视图(文件路径 + 断点条目)
|
|
8590
|
-
* @returns 跳转目标;入参不合法时返回 null
|
|
8591
|
-
*/
|
|
8592
|
-
function bpJumpTargetOf(row) {
|
|
8593
|
-
const path = typeof row?.path === "string" ? row.path : "";
|
|
8594
|
-
const line = row?.entry?.line;
|
|
8595
|
-
if (!path || typeof line !== "number" || !Number.isFinite(line) || line < 1) return null;
|
|
8596
|
-
return {
|
|
8597
|
-
path,
|
|
8598
|
-
line: Math.floor(line),
|
|
8599
|
-
column: 1
|
|
8600
|
-
};
|
|
8601
|
-
}
|
|
8602
8818
|
//#endregion
|
|
8603
8819
|
//#region src/client/dap/pidPick.ts
|
|
8604
8820
|
/**
|
|
@@ -10910,6 +11126,82 @@ window.__ModuleLoader__.load({
|
|
|
10910
11126
|
}, "✕ 关闭")), react.default.createElement("div", { className: "edrv-launch-body" }, body));
|
|
10911
11127
|
}
|
|
10912
11128
|
//#endregion
|
|
11129
|
+
//#region src/client/sidebar/PanelBoundary.ts
|
|
11130
|
+
/** Panel-local callback and React subtree isolation, not a security sandbox. @author ddj 2026年10月22号 */
|
|
11131
|
+
const panelIds = /* @__PURE__ */ new WeakMap();
|
|
11132
|
+
let nextPanelId = 0;
|
|
11133
|
+
/** @private @author ddj 2026年10月22号 @param props Subtree ownership. @returns Stable identity that changes on registration, scope or session replacement. */
|
|
11134
|
+
function ownerKey(props) {
|
|
11135
|
+
if (!panelIds.has(props.panel)) panelIds.set(props.panel, ++nextPanelId);
|
|
11136
|
+
return JSON.stringify([
|
|
11137
|
+
panelIds.get(props.panel),
|
|
11138
|
+
props.scope,
|
|
11139
|
+
props.sessionId
|
|
11140
|
+
]);
|
|
11141
|
+
}
|
|
11142
|
+
/** @public @author ddj 2026年10月22号 @param panel Registered definition. @param ctx Current scope. @returns Hidden marker or isolated visible/badge result; failed guards stay reachable for retry. */
|
|
11143
|
+
function inspectPanel(panel, ctx) {
|
|
11144
|
+
try {
|
|
11145
|
+
if (panel.visible && panel.visible(ctx) !== true) return null;
|
|
11146
|
+
return {
|
|
11147
|
+
panel,
|
|
11148
|
+
badge: panel.badge?.(ctx) ?? null
|
|
11149
|
+
};
|
|
11150
|
+
} catch (error) {
|
|
11151
|
+
return {
|
|
11152
|
+
panel,
|
|
11153
|
+
badge: null,
|
|
11154
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
11155
|
+
};
|
|
11156
|
+
}
|
|
11157
|
+
}
|
|
11158
|
+
/** @public @author ddj 2026年10月22号 @description Reset a failed subtree when registration or scope ownership changes. */
|
|
11159
|
+
var PanelBoundary = class extends react.default.Component {
|
|
11160
|
+
state = {
|
|
11161
|
+
panel: this.props.panel,
|
|
11162
|
+
scope: this.props.scope,
|
|
11163
|
+
sessionId: this.props.sessionId,
|
|
11164
|
+
error: null
|
|
11165
|
+
};
|
|
11166
|
+
/** @public @author ddj 2026年10月22号 @param props Current ownership. @param state Previous ownership/error. @returns Reset only when the owner changes. */
|
|
11167
|
+
static getDerivedStateFromProps(props, state) {
|
|
11168
|
+
if (state.panel === props.panel && state.scope === props.scope && state.sessionId === props.sessionId) return null;
|
|
11169
|
+
return {
|
|
11170
|
+
panel: props.panel,
|
|
11171
|
+
scope: props.scope,
|
|
11172
|
+
sessionId: props.sessionId,
|
|
11173
|
+
error: null
|
|
11174
|
+
};
|
|
11175
|
+
}
|
|
11176
|
+
/** @public @author ddj 2026年10月22号 @param error React render/lifecycle error. @returns Local fallback state. */
|
|
11177
|
+
static getDerivedStateFromError(error) {
|
|
11178
|
+
return { error };
|
|
11179
|
+
}
|
|
11180
|
+
/** @private @author ddj 2026年10月22号 @description Remount only this failed subtree and reevaluate its callbacks. */
|
|
11181
|
+
retry = () => {
|
|
11182
|
+
this.setState({ error: null });
|
|
11183
|
+
};
|
|
11184
|
+
/** @public @author ddj 2026年10月22号 @returns Original subtree or panel-local retry UI. */
|
|
11185
|
+
render() {
|
|
11186
|
+
if (!this.state.error) return react.default.createElement(react.default.Fragment, { key: ownerKey(this.props) }, this.props.children);
|
|
11187
|
+
if (this.props.compact) return react.default.createElement("span", {
|
|
11188
|
+
role: "img",
|
|
11189
|
+
title: "图标加载失败",
|
|
11190
|
+
"aria-label": "图标加载失败"
|
|
11191
|
+
}, "!");
|
|
11192
|
+
return react.default.createElement("div", {
|
|
11193
|
+
className: "edrv-empty",
|
|
11194
|
+
role: "alert"
|
|
11195
|
+
}, this.props.panel.title + ":" + String(this.state.error.message), react.default.createElement("button", { onClick: this.retry }, "重试"));
|
|
11196
|
+
}
|
|
11197
|
+
};
|
|
11198
|
+
/** @public @author ddj 2026年10月22号 @param props Isolated panel invocation below the boundary. @returns Panel subtree. @throws Extension callback error for the nearest boundary. */
|
|
11199
|
+
function PanelContent(props) {
|
|
11200
|
+
const checked = inspectPanel(props.panel, props.ctx);
|
|
11201
|
+
if (checked?.error) throw checked.error;
|
|
11202
|
+
return checked ? props.panel.render(props.ctx) : null;
|
|
11203
|
+
}
|
|
11204
|
+
//#endregion
|
|
10913
11205
|
//#region src/client/sidebar/SidebarView.ts
|
|
10914
11206
|
/**
|
|
10915
11207
|
* dsh-vscode-mode client — 侧边栏容器(活动栏常显 + 可隐藏面板区 + 拖拽调宽)。
|
|
@@ -10952,7 +11244,8 @@ window.__ModuleLoader__.load({
|
|
|
10952
11244
|
*/
|
|
10953
11245
|
function SidebarView(props) {
|
|
10954
11246
|
const ctx = props.ctx;
|
|
10955
|
-
const
|
|
11247
|
+
const checked = (props.registry?.list?.() ?? []).map((panel) => inspectPanel(panel, ctx)).filter(Boolean);
|
|
11248
|
+
const panels = checked.map((entry) => entry.panel);
|
|
10956
11249
|
const activePanel = props.activePanel;
|
|
10957
11250
|
const onActive = props.onActive;
|
|
10958
11251
|
const visible = props.visible !== false;
|
|
@@ -10962,26 +11255,71 @@ window.__ModuleLoader__.load({
|
|
|
10962
11255
|
const onHide = props.onHide;
|
|
10963
11256
|
const wMin = Math.max(120, Math.min(W_MAX, Math.round(Number(props.minWidth) || 300)));
|
|
10964
11257
|
const active = panels.find((p) => p.id === activePanel) || panels[0];
|
|
11258
|
+
const dragCleanup = react.default.useRef(null);
|
|
11259
|
+
/** @private @author ddj 2026年10月22号 @description End the active drag; safe on repeated teardown. */
|
|
11260
|
+
function stopDrag() {
|
|
11261
|
+
dragCleanup.current?.();
|
|
11262
|
+
}
|
|
11263
|
+
const seamOpening = react.default.useRef(false);
|
|
11264
|
+
const lastVisible = react.default.useRef(visible);
|
|
11265
|
+
/** @private @author ddj 2026年10月22号 @description Always release listeners on unmount, including a pending seam expansion. */
|
|
11266
|
+
function dragEffect() {
|
|
11267
|
+
return stopDrag;
|
|
11268
|
+
}
|
|
11269
|
+
react.default.useEffect(dragEffect, []);
|
|
11270
|
+
/** @private @author ddj 2026年10月22号 @description Preserve only the seam's own expansion; external visibility changes cancel ownership. */
|
|
11271
|
+
function visibilityEffect() {
|
|
11272
|
+
if (lastVisible.current !== visible) {
|
|
11273
|
+
if (!(visible && seamOpening.current)) stopDrag();
|
|
11274
|
+
seamOpening.current = false;
|
|
11275
|
+
lastVisible.current = visible;
|
|
11276
|
+
}
|
|
11277
|
+
}
|
|
11278
|
+
react.default.useEffect(visibilityEffect, [visible]);
|
|
11279
|
+
/** @private @author ddj 2026年10月22号 @param pointerId Owning pointer. @param move Pointer movement callback. @description Replace the previous drag and own every exit path. */
|
|
11280
|
+
function bindDrag(pointerId, move) {
|
|
11281
|
+
stopDrag();
|
|
11282
|
+
/** @author ddj 2026年10月22号 @param event Pointer event. @description Ignore another pointer's movement. */
|
|
11283
|
+
function onMove(event) {
|
|
11284
|
+
if (event.pointerId === pointerId) move(event);
|
|
11285
|
+
}
|
|
11286
|
+
/** @author ddj 2026年10月22号 @param event Pointer completion. @description Only the owning pointer ends the drag. */
|
|
11287
|
+
function onEnd(event) {
|
|
11288
|
+
if (event.pointerId === pointerId) cleanup();
|
|
11289
|
+
}
|
|
11290
|
+
/** @author ddj 2026年10月22号 @description Remove all drag listeners and release ownership. */
|
|
11291
|
+
function cleanup() {
|
|
11292
|
+
window.removeEventListener("pointermove", onMove);
|
|
11293
|
+
window.removeEventListener("pointerup", onEnd);
|
|
11294
|
+
window.removeEventListener("pointercancel", onEnd);
|
|
11295
|
+
window.removeEventListener("blur", cleanup);
|
|
11296
|
+
if (dragCleanup.current === cleanup) {
|
|
11297
|
+
dragCleanup.current = null;
|
|
11298
|
+
seamOpening.current = false;
|
|
11299
|
+
}
|
|
11300
|
+
}
|
|
11301
|
+
dragCleanup.current = cleanup;
|
|
11302
|
+
window.addEventListener("pointermove", onMove);
|
|
11303
|
+
window.addEventListener("pointerup", onEnd);
|
|
11304
|
+
window.addEventListener("pointercancel", onEnd);
|
|
11305
|
+
window.addEventListener("blur", cleanup);
|
|
11306
|
+
}
|
|
11307
|
+
/** @private @author ddj 2026年10月22号 @param e Starting pointer. @description Resize the visible panel until completion or collapse. */
|
|
10965
11308
|
const onPointerDown = (e) => {
|
|
10966
11309
|
e.preventDefault();
|
|
10967
11310
|
const startX = e.clientX;
|
|
10968
11311
|
const startW = width;
|
|
10969
|
-
|
|
10970
|
-
window.removeEventListener("pointermove", onMove);
|
|
10971
|
-
window.removeEventListener("pointerup", onUp);
|
|
10972
|
-
};
|
|
10973
|
-
const onUp = () => cleanup();
|
|
11312
|
+
/** @author ddj 2026年10月22号 @param ev Owning pointer movement. @description Apply this drag's width calculation. */
|
|
10974
11313
|
const onMove = (ev) => {
|
|
10975
11314
|
const raw = startW + (ev.clientX - startX);
|
|
10976
11315
|
if (raw < wMin) {
|
|
10977
|
-
|
|
11316
|
+
stopDrag();
|
|
10978
11317
|
onHide();
|
|
10979
11318
|
return;
|
|
10980
11319
|
}
|
|
10981
11320
|
onWidth(Math.min(W_MAX, raw));
|
|
10982
11321
|
};
|
|
10983
|
-
|
|
10984
|
-
window.addEventListener("pointerup", onUp);
|
|
11322
|
+
bindDrag(e.pointerId, onMove);
|
|
10985
11323
|
};
|
|
10986
11324
|
/**
|
|
10987
11325
|
* 隐藏态接缝拖拽:向右拖过阈值把面板区拉出来(以记忆宽度展开),继续拖动实时调宽。
|
|
@@ -10993,23 +11331,19 @@ window.__ModuleLoader__.load({
|
|
|
10993
11331
|
const baseW = Math.max(wMin, Math.min(W_MAX, Math.round(Number(width) || wMin)));
|
|
10994
11332
|
let expanded = false;
|
|
10995
11333
|
let expandStartX = 0;
|
|
10996
|
-
|
|
10997
|
-
window.removeEventListener("pointermove", onMove);
|
|
10998
|
-
window.removeEventListener("pointerup", onUp);
|
|
10999
|
-
};
|
|
11000
|
-
const onUp = () => cleanup();
|
|
11334
|
+
/** @author ddj 2026年10月22号 @param ev Owning pointer movement. @description Apply this drag's width calculation. */
|
|
11001
11335
|
const onMove = (ev) => {
|
|
11002
11336
|
if (!expanded) {
|
|
11003
11337
|
if (ev.clientX - startX < PULL_OUT_THRESHOLD) return;
|
|
11004
11338
|
expanded = true;
|
|
11005
11339
|
expandStartX = ev.clientX;
|
|
11340
|
+
seamOpening.current = true;
|
|
11006
11341
|
onShow();
|
|
11007
11342
|
return;
|
|
11008
11343
|
}
|
|
11009
11344
|
onWidth(Math.max(wMin, Math.min(W_MAX, baseW + (ev.clientX - expandStartX))));
|
|
11010
11345
|
};
|
|
11011
|
-
|
|
11012
|
-
window.addEventListener("pointerup", onUp);
|
|
11346
|
+
bindDrag(e.pointerId, onMove);
|
|
11013
11347
|
};
|
|
11014
11348
|
const rail = react.default.createElement("div", { className: "edrv-rail" }, react.default.createElement("div", { style: {
|
|
11015
11349
|
flex: 1,
|
|
@@ -11017,7 +11351,7 @@ window.__ModuleLoader__.load({
|
|
|
11017
11351
|
flexDirection: "column",
|
|
11018
11352
|
gap: 2
|
|
11019
11353
|
} }, panels.map((p) => {
|
|
11020
|
-
const count =
|
|
11354
|
+
const count = checked.find((entry) => entry.panel === p)?.badge;
|
|
11021
11355
|
return react.default.createElement("button", {
|
|
11022
11356
|
key: p.id,
|
|
11023
11357
|
className: "edrv-rail-btn" + (visible && active && p.id === active.id ? " edrv-rail-on" : ""),
|
|
@@ -11026,7 +11360,12 @@ window.__ModuleLoader__.load({
|
|
|
11026
11360
|
onActive(p.id);
|
|
11027
11361
|
if (!visible && typeof onShow === "function") onShow();
|
|
11028
11362
|
}
|
|
11029
|
-
}, react.default.createElement("span", { className: "edrv-rail-icon" },
|
|
11363
|
+
}, react.default.createElement("span", { className: "edrv-rail-icon" }, react.default.createElement(PanelBoundary, {
|
|
11364
|
+
panel: p,
|
|
11365
|
+
scope: ctx?.scope,
|
|
11366
|
+
sessionId: ctx?.sessionId,
|
|
11367
|
+
compact: true
|
|
11368
|
+
}, railIconEl(p.icon))), count > 0 ? react.default.createElement("span", { className: "edrv-rail-badge" }, String(count)) : null);
|
|
11030
11369
|
})), react.default.createElement("button", {
|
|
11031
11370
|
className: "edrv-rail-btn",
|
|
11032
11371
|
title: (visible ? "隐藏面板区" : "展开面板区") + " (Ctrl+B)",
|
|
@@ -11038,7 +11377,14 @@ window.__ModuleLoader__.load({
|
|
|
11038
11377
|
width: visible ? width : 49,
|
|
11039
11378
|
maxWidth: "100%"
|
|
11040
11379
|
}
|
|
11041
|
-
}, rail, visible ? react.default.createElement("div", { className: "edrv-side-body" }, active ?
|
|
11380
|
+
}, rail, visible ? react.default.createElement("div", { className: "edrv-side-body" }, active ? react.default.createElement(PanelBoundary, {
|
|
11381
|
+
panel: active,
|
|
11382
|
+
scope: ctx?.scope,
|
|
11383
|
+
sessionId: ctx?.sessionId
|
|
11384
|
+
}, react.default.createElement(PanelContent, {
|
|
11385
|
+
panel: active,
|
|
11386
|
+
ctx
|
|
11387
|
+
})) : null) : null, visible ? react.default.createElement("div", {
|
|
11042
11388
|
className: "edrv-side-resize",
|
|
11043
11389
|
onPointerDown
|
|
11044
11390
|
}) : react.default.createElement("div", {
|
|
@@ -12216,6 +12562,137 @@ window.__ModuleLoader__.load({
|
|
|
12216
12562
|
disposeIfFree(evicted);
|
|
12217
12563
|
}
|
|
12218
12564
|
}
|
|
12565
|
+
/** 预热失败后该路径的冷却窗口,避免每次 hover 都重试同一失败目标。 */
|
|
12566
|
+
const WARM_COOLDOWN_MS = 1e4;
|
|
12567
|
+
/** 当前安装的预热实现(EditorView 挂载时注入,未挂载则为 null)。 */
|
|
12568
|
+
let impl = null;
|
|
12569
|
+
/** path → 进行中的预热(单飞:连续 hover 同一目标只读一次盘)。 */
|
|
12570
|
+
const inFlight$1 = /* @__PURE__ */ new Map();
|
|
12571
|
+
/** path → 冷却截止时间戳(毫秒)。 */
|
|
12572
|
+
const cooled = /* @__PURE__ */ new Map();
|
|
12573
|
+
/**
|
|
12574
|
+
* 清空进行中/冷却状态(安装或卸载实现时复位,避免跨实现串味)。
|
|
12575
|
+
* @author ddj 2026年10月20号
|
|
12576
|
+
*/
|
|
12577
|
+
function resetWarmerState() {
|
|
12578
|
+
inFlight$1.clear();
|
|
12579
|
+
cooled.clear();
|
|
12580
|
+
}
|
|
12581
|
+
/**
|
|
12582
|
+
* 该路径是否处于失败冷却窗口内(过期的顺手清掉)。
|
|
12583
|
+
* @author ddj 2026年10月20号
|
|
12584
|
+
* @param path 工作区相对路径
|
|
12585
|
+
* @returns 仍在冷却中返回 true
|
|
12586
|
+
*/
|
|
12587
|
+
function isCooling(path) {
|
|
12588
|
+
const until = cooled.get(path);
|
|
12589
|
+
if (until === void 0) return false;
|
|
12590
|
+
if (until > Date.now()) return true;
|
|
12591
|
+
cooled.delete(path);
|
|
12592
|
+
return false;
|
|
12593
|
+
}
|
|
12594
|
+
/**
|
|
12595
|
+
* 记录一次失败冷却,并把冷却表裁剪到上限以内。
|
|
12596
|
+
* @author ddj 2026年10月20号
|
|
12597
|
+
* @param path 工作区相对路径
|
|
12598
|
+
*/
|
|
12599
|
+
function coolDown(path) {
|
|
12600
|
+
cooled.delete(path);
|
|
12601
|
+
cooled.set(path, Date.now() + WARM_COOLDOWN_MS);
|
|
12602
|
+
while (cooled.size > 64) {
|
|
12603
|
+
const oldest = cooled.keys().next();
|
|
12604
|
+
if (oldest.done) break;
|
|
12605
|
+
cooled.delete(oldest.value);
|
|
12606
|
+
}
|
|
12607
|
+
}
|
|
12608
|
+
/**
|
|
12609
|
+
* 结算一次预热:清 inFlight(仅当仍是自己那条)、按结果维护冷却。
|
|
12610
|
+
* @author ddj 2026年10月20号
|
|
12611
|
+
* @param path 工作区相对路径
|
|
12612
|
+
* @param run 该次预热的 promise(用于识别是否已被更新的一次替换)
|
|
12613
|
+
* @param ok 实现返回的成功与否
|
|
12614
|
+
*/
|
|
12615
|
+
function settleWarm(path, run, ok) {
|
|
12616
|
+
if (inFlight$1.get(path) === run) inFlight$1.delete(path);
|
|
12617
|
+
if (ok) {
|
|
12618
|
+
cooled.delete(path);
|
|
12619
|
+
return;
|
|
12620
|
+
}
|
|
12621
|
+
coolDown(path);
|
|
12622
|
+
}
|
|
12623
|
+
/**
|
|
12624
|
+
* 启动一次底层预热:登记 inFlight、把实现异常折叠成 false(绝不向调用方抛)。
|
|
12625
|
+
* @author ddj 2026年10月20号
|
|
12626
|
+
* @param path 工作区相对路径
|
|
12627
|
+
* @param fn 预热实现
|
|
12628
|
+
* @returns 永不 reject 的布尔 promise(已登记到 inFlight)
|
|
12629
|
+
*/
|
|
12630
|
+
function startWarm(path, fn) {
|
|
12631
|
+
const run = Promise.resolve().then(() => fn(path)).then((ok) => ok === true, () => false);
|
|
12632
|
+
inFlight$1.set(path, run);
|
|
12633
|
+
run.then((ok) => settleWarm(path, run, ok));
|
|
12634
|
+
return run;
|
|
12635
|
+
}
|
|
12636
|
+
/**
|
|
12637
|
+
* 给底层预热挂一个等待上限:超时只是本次调用放行,底层继续跑(仍可能建成 model
|
|
12638
|
+
* 惠及下一次 hover),因此超时既不取消 promise,也不提前摘掉 inFlight。
|
|
12639
|
+
* @author ddj 2026年10月20号
|
|
12640
|
+
* @param path 工作区相对路径
|
|
12641
|
+
* @param run 底层预热 promise(永不 reject)
|
|
12642
|
+
* @returns 最多 WARM_TIMEOUT_MS 内结算的布尔结果
|
|
12643
|
+
*/
|
|
12644
|
+
function withTimeout(path, run) {
|
|
12645
|
+
return new Promise((resolve) => {
|
|
12646
|
+
let settled = false;
|
|
12647
|
+
let timer = null;
|
|
12648
|
+
const finish = (ok) => {
|
|
12649
|
+
if (settled) return;
|
|
12650
|
+
settled = true;
|
|
12651
|
+
if (timer) clearTimeout(timer);
|
|
12652
|
+
resolve(ok);
|
|
12653
|
+
};
|
|
12654
|
+
timer = setTimeout(() => {
|
|
12655
|
+
coolDown(path);
|
|
12656
|
+
finish(false);
|
|
12657
|
+
}, 400);
|
|
12658
|
+
run.then(finish, () => finish(false));
|
|
12659
|
+
});
|
|
12660
|
+
}
|
|
12661
|
+
/**
|
|
12662
|
+
* 安装预热实现(EditorView 挂载时调用;传 null 视同卸载)。
|
|
12663
|
+
* @author ddj 2026年10月20号
|
|
12664
|
+
* @param fn 预热实现,或 null 表示卸载
|
|
12665
|
+
*/
|
|
12666
|
+
function setModelWarmer(fn) {
|
|
12667
|
+
impl = typeof fn === "function" ? fn : null;
|
|
12668
|
+
if (!impl) resetWarmerState();
|
|
12669
|
+
}
|
|
12670
|
+
/**
|
|
12671
|
+
* 卸载预热实现:仅当传入的仍是当前实现才清(防卸载竞态误清新挂载的实现)。
|
|
12672
|
+
* @author ddj 2026年10月20号
|
|
12673
|
+
* @param fn 当初安装的实现
|
|
12674
|
+
*/
|
|
12675
|
+
function clearModelWarmer(fn) {
|
|
12676
|
+
if (impl !== fn) return;
|
|
12677
|
+
impl = null;
|
|
12678
|
+
resetWarmerState();
|
|
12679
|
+
}
|
|
12680
|
+
/**
|
|
12681
|
+
* 确保目标路径的 Monaco model 存在(供 hover 定义预览的 createModelReference 命中)。
|
|
12682
|
+
*
|
|
12683
|
+
* 降级语义:未安装实现(编辑器页签未挂载)、路径为空、处于失败冷却、实现失败或超时,
|
|
12684
|
+
* 一律返回 false 且不抛异常——此时行为等价于修复前(预览不显示,但不再产生未捕获异常)。
|
|
12685
|
+
* @author ddj 2026年10月20号
|
|
12686
|
+
* @param path 工作区相对路径(或绝对路径,由实现决定如何解析)
|
|
12687
|
+
* @returns 预热成功返回 true;被跳过/失败/超时返回 false
|
|
12688
|
+
*/
|
|
12689
|
+
function warmModelFor(path) {
|
|
12690
|
+
if (!path || !impl) return Promise.resolve(false);
|
|
12691
|
+
const running = inFlight$1.get(path);
|
|
12692
|
+
if (running) return withTimeout(path, running);
|
|
12693
|
+
if (isCooling(path)) return Promise.resolve(false);
|
|
12694
|
+
return withTimeout(path, startWarm(path, impl));
|
|
12695
|
+
}
|
|
12219
12696
|
//#endregion
|
|
12220
12697
|
//#region src/client/navHighlight.ts
|
|
12221
12698
|
/**
|
|
@@ -13841,7 +14318,8 @@ window.__ModuleLoader__.load({
|
|
|
13841
14318
|
* 交互层:edrv.goToDefinition(F12)/ edrv.findReferences(Shift+F12 / 右键)均走 Monaco 原生命令;
|
|
13842
14319
|
* Ctrl+点击使用处交给原生;Lua 表枚举字段声明处改为打开引用 Peek,
|
|
13843
14320
|
* 数据经 host 的 LSP RPC 提供(registerEditorOpener 接管 edrv:// 点击跳转)。
|
|
13844
|
-
*
|
|
14321
|
+
* 定义结果返回前预热目标 model,避免 hover 定义链接预览抛 "Model not found"。
|
|
14322
|
+
* 作者 ddj 2026-08-27 / 2026-09-11 / 2026-10-20
|
|
13845
14323
|
*/
|
|
13846
14324
|
const LSP_LANGS = ["lua", "csharp"];
|
|
13847
14325
|
/**
|
|
@@ -13916,6 +14394,28 @@ window.__ModuleLoader__.load({
|
|
|
13916
14394
|
endColumn
|
|
13917
14395
|
};
|
|
13918
14396
|
}
|
|
14397
|
+
/**
|
|
14398
|
+
* 预热定义目标文件的 Monaco model(供 Ctrl+hover 定义链接预览使用)。
|
|
14399
|
+
*
|
|
14400
|
+
* 为什么需要:Monaco 的 GotoDefinitionAtPositionEditorContribution 在渲染定义链接预览时,
|
|
14401
|
+
* 对目标 uri 调 `createModelReference` 后**只挂 then、没有 catch**;本插件的定义目标是
|
|
14402
|
+
* 自造的 `edrv://` uri,目标文件若从未打开过就没有已注册 model,`createModelReference`
|
|
14403
|
+
* 会 reject("Model not found") 变成未捕获的 promise 异常(弹到 Console)。
|
|
14404
|
+
* 本 vendored Monaco 也没有 `registerTextModelContentProvider` 可惰性喂内容,故只能提前建模型。
|
|
14405
|
+
*
|
|
14406
|
+
* 只预热单一定义:多定义分支(`locations.length > 1`)Monaco 走列表、不调
|
|
14407
|
+
* `createModelReference`,没必要读盘。预热纯属增强,任何异常都在此吞掉,
|
|
14408
|
+
* 不得让跳转查询失败(F12 与 Ctrl+点击不依赖模型是否已建)。
|
|
14409
|
+
* @author ddj 2026年10月20号
|
|
14410
|
+
* @param locations findDefinition 的客户端结果(含 edrv uri)
|
|
14411
|
+
*/
|
|
14412
|
+
async function prewarmDefinitionTarget(locations) {
|
|
14413
|
+
if (!Array.isArray(locations) || locations.length !== 1) return;
|
|
14414
|
+
try {
|
|
14415
|
+
const target = lspUriToAbs(locations[0]?.uri);
|
|
14416
|
+
if (target) await warmModelFor(target);
|
|
14417
|
+
} catch (error) {}
|
|
14418
|
+
}
|
|
13919
14419
|
/** 注册全部 Monaco LSP provider 与文档跟踪(幂等)。 */
|
|
13920
14420
|
function registerLspProviders(monaco) {
|
|
13921
14421
|
if (registered$3) return;
|
|
@@ -13964,10 +14464,12 @@ window.__ModuleLoader__.load({
|
|
|
13964
14464
|
if (!path) return false;
|
|
13965
14465
|
openAt(path, navTargetOf(selectionOrPosition));
|
|
13966
14466
|
return true;
|
|
13967
|
-
} }), monaco.languages.registerDefinitionProvider(LSP_LANGS, { provideDefinition: (model, position, token) => {
|
|
14467
|
+
} }), monaco.languages.registerDefinitionProvider(LSP_LANGS, { provideDefinition: async (model, position, token) => {
|
|
13968
14468
|
const path = pathOfModel(model);
|
|
13969
14469
|
if (!path) return [];
|
|
13970
|
-
|
|
14470
|
+
const locations = await findDefinition(path, model.getValue(), position);
|
|
14471
|
+
await prewarmDefinitionTarget(locations);
|
|
14472
|
+
return locations;
|
|
13971
14473
|
} }), monaco.languages.registerReferenceProvider(LSP_LANGS, { provideReferences: (model, position, context, token) => {
|
|
13972
14474
|
const path = pathOfModel(model);
|
|
13973
14475
|
if (!path) return [];
|
|
@@ -22187,7 +22689,7 @@ window.__ModuleLoader__.load({
|
|
|
22187
22689
|
}
|
|
22188
22690
|
//#endregion
|
|
22189
22691
|
//#region src/client/dap/toolbarDrag.ts
|
|
22190
|
-
const KEY_PREFIX$
|
|
22692
|
+
const KEY_PREFIX$2 = "edrv.dap.toolbar.";
|
|
22191
22693
|
/** 将工具条位置限制在编辑区内。 */
|
|
22192
22694
|
function clampToolbarPosition(point, bounds) {
|
|
22193
22695
|
const margin = bounds.margin ?? 6;
|
|
@@ -22202,7 +22704,7 @@ window.__ModuleLoader__.load({
|
|
|
22202
22704
|
function loadToolbarPosition(scope) {
|
|
22203
22705
|
if (!scope) return null;
|
|
22204
22706
|
try {
|
|
22205
|
-
const raw = localStorage.getItem(KEY_PREFIX$
|
|
22707
|
+
const raw = localStorage.getItem(KEY_PREFIX$2 + scope);
|
|
22206
22708
|
if (!raw) return null;
|
|
22207
22709
|
const value = JSON.parse(raw);
|
|
22208
22710
|
if (!value || !Number.isFinite(value.left) || !Number.isFinite(value.top)) return null;
|
|
@@ -22218,7 +22720,7 @@ window.__ModuleLoader__.load({
|
|
|
22218
22720
|
function saveToolbarPosition(scope, point) {
|
|
22219
22721
|
if (!scope) return;
|
|
22220
22722
|
try {
|
|
22221
|
-
localStorage.setItem(KEY_PREFIX$
|
|
22723
|
+
localStorage.setItem(KEY_PREFIX$2 + scope, JSON.stringify(point));
|
|
22222
22724
|
} catch {}
|
|
22223
22725
|
}
|
|
22224
22726
|
//#endregion
|
|
@@ -22410,6 +22912,7 @@ window.__ModuleLoader__.load({
|
|
|
22410
22912
|
const planActiveRef = react.default.useRef(null);
|
|
22411
22913
|
planActiveRef.current = planActive;
|
|
22412
22914
|
const planHostRef = react.default.useRef(null);
|
|
22915
|
+
const planReturnRef = react.default.useRef(null);
|
|
22413
22916
|
react.default.useEffect(() => {
|
|
22414
22917
|
setPlanTabs([]);
|
|
22415
22918
|
setPlanActive(null);
|
|
@@ -22424,6 +22927,7 @@ window.__ModuleLoader__.load({
|
|
|
22424
22927
|
const [layoutScope, setLayoutScope] = react.default.useState(null);
|
|
22425
22928
|
const benchRef = react.default.useRef(workbench);
|
|
22426
22929
|
benchRef.current = workbench;
|
|
22930
|
+
const rowOwner = workbench.floats.some((item) => item.group === workbench.focused) ? MAIN_GROUP : workbench.focused;
|
|
22427
22931
|
const [dragPreview, setDragPreview] = react.default.useState(null);
|
|
22428
22932
|
const dragRef = react.default.useRef(null);
|
|
22429
22933
|
const benchOps = react.default.useRef(null);
|
|
@@ -22466,6 +22970,15 @@ window.__ModuleLoader__.load({
|
|
|
22466
22970
|
const aiStatusTimer = react.default.useRef(null);
|
|
22467
22971
|
const [error, setError] = react.default.useState(null);
|
|
22468
22972
|
const [loadError, setLoadError] = react.default.useState(null);
|
|
22973
|
+
const [largeCandidate, setLargeCandidate] = react.default.useState(null);
|
|
22974
|
+
const [pagedFile, setPagedFile] = react.default.useState(null);
|
|
22975
|
+
const [pageChanged, setPageChanged] = react.default.useState(null);
|
|
22976
|
+
const pageIdentity = JSON.stringify([
|
|
22977
|
+
scope,
|
|
22978
|
+
sessionId,
|
|
22979
|
+
active
|
|
22980
|
+
]);
|
|
22981
|
+
const paging = pagedFile === pageIdentity;
|
|
22469
22982
|
const [loadStage, setLoadStage] = react.default.useState({
|
|
22470
22983
|
progress: 0,
|
|
22471
22984
|
message: "准备加载编辑器…"
|
|
@@ -23178,6 +23691,9 @@ window.__ModuleLoader__.load({
|
|
|
23178
23691
|
status: "loading"
|
|
23179
23692
|
});
|
|
23180
23693
|
setLoadError(null);
|
|
23694
|
+
setLargeCandidate(null);
|
|
23695
|
+
setPagedFile(null);
|
|
23696
|
+
setPageChanged(null);
|
|
23181
23697
|
/**
|
|
23182
23698
|
* 记下磁盘版本基线;markRead=true 表示本次真的从磁盘读了内容
|
|
23183
23699
|
* (已读版本台账随之失效,下一轮轮询按新版本重新比对)。
|
|
@@ -23234,6 +23750,11 @@ window.__ModuleLoader__.load({
|
|
|
23234
23750
|
}));
|
|
23235
23751
|
setStatus("已加载");
|
|
23236
23752
|
} else {
|
|
23753
|
+
if (res?.code === "file-too-large") setLargeCandidate(JSON.stringify([
|
|
23754
|
+
scope,
|
|
23755
|
+
sid,
|
|
23756
|
+
path
|
|
23757
|
+
]));
|
|
23237
23758
|
const base = res?.error ? String(res.error) : "读取失败";
|
|
23238
23759
|
dapTrace("read-failed", {
|
|
23239
23760
|
path,
|
|
@@ -23414,7 +23935,7 @@ window.__ModuleLoader__.load({
|
|
|
23414
23935
|
* @author ddj 2026年09月15号
|
|
23415
23936
|
* @param change 轮询判定出的外部变更
|
|
23416
23937
|
*/
|
|
23417
|
-
|
|
23938
|
+
function onDiskChange(change) {
|
|
23418
23939
|
if (!change || !change.path) return;
|
|
23419
23940
|
const eventId = emitFileChanged(change.path, {
|
|
23420
23941
|
scope,
|
|
@@ -23443,6 +23964,18 @@ window.__ModuleLoader__.load({
|
|
|
23443
23964
|
}).catch(() => {});
|
|
23444
23965
|
return;
|
|
23445
23966
|
}
|
|
23967
|
+
if (change.kind === "modified" && (paging || pageChanged === pageIdentity) && change.path === activeRef.current) {
|
|
23968
|
+
loadContent(change.path, sessionId, true);
|
|
23969
|
+
setPageChanged(pageIdentity);
|
|
23970
|
+
setStatus("文件已变化,请重新读取");
|
|
23971
|
+
emitRefresh({
|
|
23972
|
+
scope,
|
|
23973
|
+
paths: [change.path],
|
|
23974
|
+
kind: "text",
|
|
23975
|
+
eventId
|
|
23976
|
+
});
|
|
23977
|
+
return;
|
|
23978
|
+
}
|
|
23446
23979
|
if (change.kind === "modified") {
|
|
23447
23980
|
loadContent(change.path, sessionId, true);
|
|
23448
23981
|
clearSync(scope, change.path);
|
|
@@ -23463,7 +23996,7 @@ window.__ModuleLoader__.load({
|
|
|
23463
23996
|
at: Date.now()
|
|
23464
23997
|
});
|
|
23465
23998
|
setStatus(kind === "deleted" ? "文件已被外部删除" : "外部已修改(未保存的编辑保留)");
|
|
23466
|
-
}
|
|
23999
|
+
}
|
|
23467
24000
|
diskChangeRef.current = onDiskChange;
|
|
23468
24001
|
/**
|
|
23469
24002
|
* 版本变化时读磁盘并与缓冲比对:内容相同 → 只推进基线(不做无意义重载);
|
|
@@ -23472,7 +24005,8 @@ window.__ModuleLoader__.load({
|
|
|
23472
24005
|
* @param path 文件路径
|
|
23473
24006
|
* @returns 比对结果;模型不可用/读失败 → null(按「需要重载」处理)
|
|
23474
24007
|
*/
|
|
23475
|
-
|
|
24008
|
+
function readDiskCompare(path) {
|
|
24009
|
+
if (path === activeRef.current && (paging || pageChanged === pageIdentity)) return Promise.resolve({ equal: false });
|
|
23476
24010
|
const model = modelsRef.current?.get(path);
|
|
23477
24011
|
if (!model || typeof model.getValue !== "function") return Promise.resolve(null);
|
|
23478
24012
|
return rpc("edrv.read", {
|
|
@@ -23482,7 +24016,7 @@ window.__ModuleLoader__.load({
|
|
|
23482
24016
|
if (!res || !res.ok) return null;
|
|
23483
24017
|
return { equal: res.content === model.getValue() };
|
|
23484
24018
|
}).catch(() => null);
|
|
23485
|
-
}
|
|
24019
|
+
}
|
|
23486
24020
|
useFileWatch({
|
|
23487
24021
|
sessionId,
|
|
23488
24022
|
scope,
|
|
@@ -24094,8 +24628,14 @@ window.__ModuleLoader__.load({
|
|
|
24094
24628
|
const right = left + el.offsetWidth;
|
|
24095
24629
|
if (left < host.scrollLeft) host.scrollLeft = left;
|
|
24096
24630
|
else if (right > host.scrollLeft + host.clientWidth) host.scrollLeft = right - host.clientWidth;
|
|
24097
|
-
}, [
|
|
24098
|
-
|
|
24631
|
+
}, [
|
|
24632
|
+
active,
|
|
24633
|
+
planActive,
|
|
24634
|
+
planTabs.length,
|
|
24635
|
+
tabs.length,
|
|
24636
|
+
rowOwner
|
|
24637
|
+
]);
|
|
24638
|
+
react.default.useEffect(() => attachHorizontalWheel(tabsHostRef.current), [rowOwner]);
|
|
24099
24639
|
react.default.useEffect(() => {
|
|
24100
24640
|
const onPointer = (e) => {
|
|
24101
24641
|
if (e?.button !== 3 && e?.button !== 4) return;
|
|
@@ -24295,6 +24835,41 @@ window.__ModuleLoader__.load({
|
|
|
24295
24835
|
}
|
|
24296
24836
|
return model;
|
|
24297
24837
|
};
|
|
24838
|
+
const warmRef = react.default.useRef(null);
|
|
24839
|
+
/**
|
|
24840
|
+
* 让目标文件的 Monaco model 存在(预热入口,供 Ctrl+hover 定义链接预览)。
|
|
24841
|
+
*
|
|
24842
|
+
* 为什么需要:Monaco 在渲染定义链接的源码预览时对目标 uri 调 `createModelReference`
|
|
24843
|
+
* 后只挂 then、没有 catch;本插件的定义目标是自造 `edrv://` uri,目标文件若从未打开过
|
|
24844
|
+
* 就没有已注册 model,会 reject("Model not found") 成为未捕获异常(见 state/modelWarmer.ts)。
|
|
24845
|
+
* 已缓存的路径直接返回成功:模型已在 Monaco 注册表里,无需再读盘。
|
|
24846
|
+
* 预热出的 model 走 modelsRef 常规 LRU(40 条、未附着超限被 dispose);正式打开该文件时
|
|
24847
|
+
* 由 loadContent 的 cached-model 分支 + checkCached 后台校验接管,不会显示陈旧内容。
|
|
24848
|
+
* @private
|
|
24849
|
+
* @author ddj 2026年10月20号
|
|
24850
|
+
* @param path 工作区相对路径
|
|
24851
|
+
* @returns 模型是否已存在/成功建出(失败一律 false,绝不抛)
|
|
24852
|
+
*/
|
|
24853
|
+
warmRef.current = (path) => {
|
|
24854
|
+
if (!path) return Promise.resolve(false);
|
|
24855
|
+
if (modelsRef.current.get(path)) return Promise.resolve(true);
|
|
24856
|
+
if (isImagePath(path) || isPdfPath(path)) return Promise.resolve(false);
|
|
24857
|
+
const sid = sessionId;
|
|
24858
|
+
if (!sid) return Promise.resolve(false);
|
|
24859
|
+
return rpc("edrv.read", {
|
|
24860
|
+
sessionId: sid,
|
|
24861
|
+
path
|
|
24862
|
+
}).then((res) => {
|
|
24863
|
+
if (!res?.ok || typeof res.content !== "string") return false;
|
|
24864
|
+
getModel(path, res.content);
|
|
24865
|
+
return true;
|
|
24866
|
+
}).catch(() => false);
|
|
24867
|
+
};
|
|
24868
|
+
react.default.useEffect(() => {
|
|
24869
|
+
const warmImpl = (path) => warmRef.current?.(path) ?? Promise.resolve(false);
|
|
24870
|
+
setModelWarmer(warmImpl);
|
|
24871
|
+
return () => clearModelWarmer(warmImpl);
|
|
24872
|
+
}, []);
|
|
24298
24873
|
/**
|
|
24299
24874
|
* 提交待执行的防抖保存(**真正执行保存**,不是取消)。
|
|
24300
24875
|
* 语义与缺陷背景见 saveDebounce.ts:`schedule` 的返回值是只 clearTimeout 的 disposer,
|
|
@@ -26006,6 +26581,7 @@ window.__ModuleLoader__.load({
|
|
|
26006
26581
|
if (!document) return;
|
|
26007
26582
|
flushSave();
|
|
26008
26583
|
saveViewState(active);
|
|
26584
|
+
if (!planActiveRef.current) planReturnRef.current = active;
|
|
26009
26585
|
setDiskActive(null);
|
|
26010
26586
|
setPlanTabs((prev) => [...prev.filter((item) => item.address !== document.address), document].slice(-32));
|
|
26011
26587
|
setPlanActive(document.address);
|
|
@@ -26016,7 +26592,10 @@ window.__ModuleLoader__.load({
|
|
|
26016
26592
|
function closePlan(address, event) {
|
|
26017
26593
|
event?.stopPropagation();
|
|
26018
26594
|
setPlanTabs((prev) => prev.filter((item) => item.address !== address));
|
|
26019
|
-
if (planActiveRef.current
|
|
26595
|
+
if (planActiveRef.current !== address) return;
|
|
26596
|
+
const previous = planReturnRef.current;
|
|
26597
|
+
planReturnRef.current = null;
|
|
26598
|
+
setActive(tabsRef.current.some((tab) => tab.path === previous) ? previous : tabsRef.current[0]?.path ?? null);
|
|
26020
26599
|
}
|
|
26021
26600
|
/** @private @author ddj 2026年10月08号 @param request Claimed disk or virtual open. Flushes saves without replacing dirty models. */
|
|
26022
26601
|
function openReceiptFile(request) {
|
|
@@ -26147,7 +26726,8 @@ window.__ModuleLoader__.load({
|
|
|
26147
26726
|
window.removeEventListener("edrv.command.debugStop", onStop);
|
|
26148
26727
|
};
|
|
26149
26728
|
}, []);
|
|
26150
|
-
|
|
26729
|
+
/** @private @author ddj 2026年10月22号 @description Probe a typed path; let oversized files enter the existing read-only paging flow. @returns Probe completion. */
|
|
26730
|
+
function openPath() {
|
|
26151
26731
|
const p = (pathDraft || "").trim();
|
|
26152
26732
|
if (!p) return;
|
|
26153
26733
|
if (isImagePath(p) || isPdfPath(p)) {
|
|
@@ -26159,11 +26739,11 @@ window.__ModuleLoader__.load({
|
|
|
26159
26739
|
return;
|
|
26160
26740
|
}
|
|
26161
26741
|
setStatus("打开中…");
|
|
26162
|
-
rpc("edrv.read", {
|
|
26742
|
+
return rpc("edrv.read", {
|
|
26163
26743
|
sessionId,
|
|
26164
26744
|
path: p
|
|
26165
26745
|
}).then((res) => {
|
|
26166
|
-
if (res && res.ok) {
|
|
26746
|
+
if (res && (res.ok || res.code === "file-too-large")) {
|
|
26167
26747
|
openFile(p, false);
|
|
26168
26748
|
setOpenInput(false);
|
|
26169
26749
|
setPathDraft("");
|
|
@@ -26177,7 +26757,7 @@ window.__ModuleLoader__.load({
|
|
|
26177
26757
|
setStatus("打开失败");
|
|
26178
26758
|
setError("打开异常:" + String(e));
|
|
26179
26759
|
});
|
|
26180
|
-
}
|
|
26760
|
+
}
|
|
26181
26761
|
/** @private @author ddj 2026年10月14号 @param group Group id. @param path Optional selection. Route global actions only to this group. */
|
|
26182
26762
|
function selectGroup(group, path) {
|
|
26183
26763
|
saveViewState();
|
|
@@ -26359,9 +26939,21 @@ window.__ModuleLoader__.load({
|
|
|
26359
26939
|
host.removeEventListener("contextmenu", onCtx);
|
|
26360
26940
|
};
|
|
26361
26941
|
}
|
|
26942
|
+
/** @private @author ddj 2026年10月22号 @param args Read-only page request. @returns Current Host capability result. */
|
|
26943
|
+
function loadTextPage(args) {
|
|
26944
|
+
return rpc("edrv.textPage", args);
|
|
26945
|
+
}
|
|
26946
|
+
/** @private @author ddj 2026年10月22号 @description Never replace a dirty or saving document with partial text. */
|
|
26947
|
+
function openTextPages() {
|
|
26948
|
+
if (largeCandidate !== pageIdentity || dirtyRef.current[active] || savingRef.current.has(active)) return;
|
|
26949
|
+
flushSave();
|
|
26950
|
+
if (dirtyRef.current[active] || savingRef.current.has(active)) return;
|
|
26951
|
+
setPageChanged(null);
|
|
26952
|
+
setPagedFile(pageIdentity);
|
|
26953
|
+
}
|
|
26362
26954
|
/** @private @author ddj 2026年10月16号 @param host Passive body. @param path Canonical file. @param groupId Owning group id(find 快照按组隔离). @returns View-only cleanup, models remain workbench-owned. */
|
|
26363
26955
|
function mountPassive(host, path, groupId) {
|
|
26364
|
-
let disposed = false, keeper = null, subscription = null, observer = null;
|
|
26956
|
+
let disposed = false, keeper = null, subscription = null, observer = null, pageRoot = null;
|
|
26365
26957
|
const focus = () => {
|
|
26366
26958
|
const owner = ownerOf(benchRef.current, path);
|
|
26367
26959
|
if (owner) selectGroup(owner.id, path);
|
|
@@ -26388,6 +26980,27 @@ window.__ModuleLoader__.load({
|
|
|
26388
26980
|
host.textContent = "";
|
|
26389
26981
|
if (!result?.ok) {
|
|
26390
26982
|
host.textContent = result?.error || "文件加载失败";
|
|
26983
|
+
if (result?.code === "file-too-large" && !binary) {
|
|
26984
|
+
const button = document.createElement("button");
|
|
26985
|
+
button.textContent = "只读分段查看";
|
|
26986
|
+
/** @author ddj 2026年10月22号 @param event Pointer event. @description Do not focus and unmount the passive group before click. */
|
|
26987
|
+
button.onpointerdown = (event) => event.stopPropagation();
|
|
26988
|
+
/** @author ddj 2026年10月22号 @description Keep partial text out of passive models, autosave and editing commands. */
|
|
26989
|
+
button.onclick = () => {
|
|
26990
|
+
if (disposed || dirtyRef.current[path] || savingRef.current.has(path)) return;
|
|
26991
|
+
flushSave();
|
|
26992
|
+
if (dirtyRef.current[path] || savingRef.current.has(path)) return;
|
|
26993
|
+
host.textContent = "";
|
|
26994
|
+
host.removeEventListener("pointerdown", focus);
|
|
26995
|
+
pageRoot = (0, react_dom_client.createRoot)(host);
|
|
26996
|
+
pageRoot.render(react.default.createElement(LargeTextView, {
|
|
26997
|
+
sessionId,
|
|
26998
|
+
path,
|
|
26999
|
+
loadPage: loadTextPage
|
|
27000
|
+
}));
|
|
27001
|
+
};
|
|
27002
|
+
host.appendChild(button);
|
|
27003
|
+
}
|
|
26391
27004
|
return;
|
|
26392
27005
|
}
|
|
26393
27006
|
if (binary) {
|
|
@@ -26438,6 +27051,7 @@ window.__ModuleLoader__.load({
|
|
|
26438
27051
|
});
|
|
26439
27052
|
return () => {
|
|
26440
27053
|
disposed = true;
|
|
27054
|
+
pageRoot?.unmount();
|
|
26441
27055
|
host.removeEventListener("pointerdown", focus);
|
|
26442
27056
|
observer?.disconnect();
|
|
26443
27057
|
subscription?.dispose();
|
|
@@ -26472,15 +27086,21 @@ window.__ModuleLoader__.load({
|
|
|
26472
27086
|
}));
|
|
26473
27087
|
}
|
|
26474
27088
|
/** @private @author ddj 2026年10月14号 @param id Leaf id. @returns Tabs and either focused full-feature editor or independent passive view. */
|
|
26475
|
-
function renderGroup(id) {
|
|
27089
|
+
function renderGroup(id, floating) {
|
|
26476
27090
|
const group = workbench.groups.find((item) => item.id === id);
|
|
26477
27091
|
if (!group) return null;
|
|
26478
27092
|
const focused = id === workbench.focused;
|
|
27093
|
+
const chrome = !floating && id === rowOwner;
|
|
27094
|
+
const hideDiskActive = Boolean(planActive) && (chrome || focused);
|
|
26479
27095
|
return react.default.createElement(EditorPane, {
|
|
26480
27096
|
key: id,
|
|
26481
27097
|
group,
|
|
26482
27098
|
focused,
|
|
26483
27099
|
dirty: dirtyMap,
|
|
27100
|
+
suppressActive: hideDiskActive,
|
|
27101
|
+
tabsRef: chrome ? tabsHostRef : void 0,
|
|
27102
|
+
extras: chrome ? planTabExtras : void 0,
|
|
27103
|
+
tools: chrome ? rowTools : void 0,
|
|
26484
27104
|
onSelect: selectGroup,
|
|
26485
27105
|
onClose: closeTab,
|
|
26486
27106
|
onMenu: paneMenu,
|
|
@@ -26493,7 +27113,11 @@ window.__ModuleLoader__.load({
|
|
|
26493
27113
|
path: group.active,
|
|
26494
27114
|
onFocus: () => selectGroup(id)
|
|
26495
27115
|
}), react.default.createElement(PassiveEditor, {
|
|
26496
|
-
key:
|
|
27116
|
+
key: JSON.stringify([
|
|
27117
|
+
scope,
|
|
27118
|
+
sessionId,
|
|
27119
|
+
group.active
|
|
27120
|
+
]),
|
|
26497
27121
|
path: group.active,
|
|
26498
27122
|
groupId: id,
|
|
26499
27123
|
mount: passiveMount
|
|
@@ -26510,27 +27134,17 @@ window.__ModuleLoader__.load({
|
|
|
26510
27134
|
onRatio: (ratio) => changeRatio(node, ratio)
|
|
26511
27135
|
});
|
|
26512
27136
|
}
|
|
26513
|
-
const
|
|
26514
|
-
className: "edrv-tabs",
|
|
26515
|
-
ref: tabsHostRef,
|
|
26516
|
-
style: {
|
|
26517
|
-
flex: "1 1 auto",
|
|
26518
|
-
minWidth: 0
|
|
26519
|
-
}
|
|
26520
|
-
}, planTabs.map((plan) => react.default.createElement("div", {
|
|
27137
|
+
const planTabExtras = planTabs.length ? planTabs.map((plan) => react.default.createElement("div", {
|
|
26521
27138
|
key: plan.address,
|
|
26522
|
-
className: "edrv-tab" + (plan.address === planActive ? " edrv-tab-active" : ""),
|
|
27139
|
+
className: "edrv-tab edrv-tab-plan" + (plan.address === planActive ? " edrv-tab-active" : ""),
|
|
26523
27140
|
title: plan.title + "(计划预览 · 只读)",
|
|
26524
27141
|
onClick: () => openPlanDoc(plan)
|
|
26525
|
-
}, react.default.createElement("span", {
|
|
26526
|
-
overflow: "hidden",
|
|
26527
|
-
textOverflow: "ellipsis",
|
|
26528
|
-
maxWidth: 180
|
|
26529
|
-
} }, plan.title), react.default.createElement("span", {
|
|
27142
|
+
}, react.default.createElement("span", { className: "edrv-group-tab-name" }, plan.title), react.default.createElement("span", {
|
|
26530
27143
|
className: "edrv-tab-x",
|
|
26531
27144
|
title: "关闭",
|
|
26532
27145
|
onClick: (event) => closePlan(plan.address, event)
|
|
26533
|
-
}, "×")))
|
|
27146
|
+
}, "×"))) : null;
|
|
27147
|
+
const openControl = openInput ? react.default.createElement("input", {
|
|
26534
27148
|
className: "edrv-path-input",
|
|
26535
27149
|
autoFocus: true,
|
|
26536
27150
|
placeholder: "输入工作区相对/绝对路径,回车打开",
|
|
@@ -26544,7 +27158,7 @@ window.__ModuleLoader__.load({
|
|
|
26544
27158
|
className: "edrv-tab-add",
|
|
26545
27159
|
title: "打开文件(输入路径)",
|
|
26546
27160
|
onClick: () => setOpenInput(true)
|
|
26547
|
-
}, "+")
|
|
27161
|
+
}, "+");
|
|
26548
27162
|
const activePlan = planTabs.find((plan) => plan.address === planActive);
|
|
26549
27163
|
const pathBar = activePlan ? react.default.createElement("div", {
|
|
26550
27164
|
className: "edrv-pathbar",
|
|
@@ -26623,12 +27237,7 @@ window.__ModuleLoader__.load({
|
|
|
26623
27237
|
const navBackTarget = navRef.current.peekBack();
|
|
26624
27238
|
const navForwardTarget = navRef.current.peekForward();
|
|
26625
27239
|
const navNameOf = (entry) => entry && entry.path ? String(entry.path).split(/[\\/]/).pop() : "";
|
|
26626
|
-
const
|
|
26627
|
-
display: "flex",
|
|
26628
|
-
alignItems: "center",
|
|
26629
|
-
background: "var(--dsw-alias-bg-layer-1,transparent)",
|
|
26630
|
-
flexShrink: 0
|
|
26631
|
-
} }, tabsEl, sum.totalFiles > 0 ? react.default.createElement("button", {
|
|
27240
|
+
const rowTools = react.default.createElement("div", { className: "edrv-tabs-tools" }, openControl, sum.totalFiles > 0 ? react.default.createElement("button", {
|
|
26632
27241
|
className: "edrv-diffchip",
|
|
26633
27242
|
title: "打开差异总览/归档",
|
|
26634
27243
|
onClick: () => setLauncherOpen((o) => !o)
|
|
@@ -26734,7 +27343,20 @@ window.__ModuleLoader__.load({
|
|
|
26734
27343
|
ref: planHostRef,
|
|
26735
27344
|
"data-plan-address": activePlan.address
|
|
26736
27345
|
}, react.default.createElement("div", { className: "edrv-mdview-stage" }, react.default.createElement("div", { className: "edrv-mdview-doc" }, render)));
|
|
26737
|
-
} else if (
|
|
27346
|
+
} else if (pageChanged === pageIdentity && largeCandidate === pageIdentity) body = react.default.createElement("div", {
|
|
27347
|
+
className: "edrv-empty",
|
|
27348
|
+
role: "status"
|
|
27349
|
+
}, react.default.createElement("div", null, "文件已变化,请重新读取"), react.default.createElement("button", {
|
|
27350
|
+
className: "edrv-pill edrv-pill-ghost",
|
|
27351
|
+
onClick: openTextPages
|
|
27352
|
+
}, "重新读取首段"));
|
|
27353
|
+
else if (paging) body = react.default.createElement(LargeTextView, {
|
|
27354
|
+
key: pageIdentity,
|
|
27355
|
+
sessionId,
|
|
27356
|
+
path: active,
|
|
27357
|
+
loadPage: loadTextPage
|
|
27358
|
+
});
|
|
27359
|
+
else if (!monaco && !monacoErr) body = loadingBody(loadStage.message, loadStage.progress);
|
|
26738
27360
|
else if (monacoErr) body = react.default.createElement("div", { className: "edrv-empty" }, react.default.createElement("div", null, "Monaco 编辑器加载失败:" + String(monacoErr)), react.default.createElement("div", { style: { fontSize: 11 } }, "请确认插件包 assets/vendor/monaco 完整(/edrv/vendor 路由可达)"), react.default.createElement("button", {
|
|
26739
27361
|
className: "edrv-pill edrv-pill-ghost",
|
|
26740
27362
|
onClick: () => {
|
|
@@ -26766,7 +27388,7 @@ window.__ModuleLoader__.load({
|
|
|
26766
27388
|
loadContent(active, sessionId);
|
|
26767
27389
|
}
|
|
26768
27390
|
}, "重试"));
|
|
26769
|
-
else if (content === null && loadError) body = loadingBody("文件加载失败:" + loadError, 0, () => {
|
|
27391
|
+
else if (content === null && loadError) body = react.default.createElement(react.default.Fragment, null, loadingBody("文件加载失败:" + loadError, 0, () => {
|
|
26770
27392
|
setLoadError(null);
|
|
26771
27393
|
setContent(null);
|
|
26772
27394
|
setContentPath(null);
|
|
@@ -26775,7 +27397,10 @@ window.__ModuleLoader__.load({
|
|
|
26775
27397
|
message: "重新读取文件…"
|
|
26776
27398
|
});
|
|
26777
27399
|
loadContent(active, sessionId);
|
|
26778
|
-
})
|
|
27400
|
+
}), largeCandidate === pageIdentity && react.default.createElement("button", {
|
|
27401
|
+
className: "edrv-pill edrv-pill-ghost",
|
|
27402
|
+
onClick: openTextPages
|
|
27403
|
+
}, "只读分段查看"));
|
|
26779
27404
|
else if (isImageActive && imageSrc) body = imagePanel();
|
|
26780
27405
|
else if (isImageActive) body = loadingBody(loadStage.message || "读取图片…", loadStage.progress);
|
|
26781
27406
|
else if (isPdfActive && pdfBytes) body = react.default.createElement("div", {
|
|
@@ -27119,7 +27744,7 @@ window.__ModuleLoader__.load({
|
|
|
27119
27744
|
flexDirection: "column",
|
|
27120
27745
|
overflow: "hidden"
|
|
27121
27746
|
}
|
|
27122
|
-
},
|
|
27747
|
+
}, renderTree(workbench.tree), fullView, statusBar, workbench.floats.map((item, layer) => react.default.createElement(FloatingEditor, {
|
|
27123
27748
|
key: item.group,
|
|
27124
27749
|
owner: String(scope),
|
|
27125
27750
|
group: item.group,
|
|
@@ -27129,7 +27754,7 @@ window.__ModuleLoader__.load({
|
|
|
27129
27754
|
onBounds: floatBounds,
|
|
27130
27755
|
onFocus: selectGroup,
|
|
27131
27756
|
onReturn: returnPane
|
|
27132
|
-
}, renderGroup(item.group))), dragPreview ? (0, react_dom.createPortal)(react.default.createElement(react.default.Fragment, null, dragPreview.rect ? react.default.createElement("div", {
|
|
27757
|
+
}, renderGroup(item.group, true))), dragPreview ? (0, react_dom.createPortal)(react.default.createElement(react.default.Fragment, null, dragPreview.rect ? react.default.createElement("div", {
|
|
27133
27758
|
className: "edrv-drag-overlay",
|
|
27134
27759
|
style: dragPreview.rect
|
|
27135
27760
|
}, react.default.createElement("span", { className: "edrv-drop-label" }, dragPreview.label)) : null, react.default.createElement("div", {
|
|
@@ -27744,6 +28369,24 @@ window.__ModuleLoader__.load({
|
|
|
27744
28369
|
return config;
|
|
27745
28370
|
}
|
|
27746
28371
|
//#endregion
|
|
28372
|
+
//#region src/shared/mcp.ts
|
|
28373
|
+
/** @public @author ddj 2026年10月20号 @param input Project-only policy. @returns Validated defaults. @throws Invalid explicit policy. */
|
|
28374
|
+
function parseMcpPolicy(input) {
|
|
28375
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) throw new Error("vscodeMode 必须是对象");
|
|
28376
|
+
const policy = input ?? {};
|
|
28377
|
+
const startup = policy.startup ?? "on-demand";
|
|
28378
|
+
const sharing = policy.sharing ?? "agent";
|
|
28379
|
+
const startupTimeoutMs = policy.startupTimeoutMs ?? 6e4;
|
|
28380
|
+
if (!["on-demand", "background"].includes(startup)) throw new Error("不支持的 MCP startup");
|
|
28381
|
+
if (!["agent", "workspace"].includes(sharing)) throw new Error("不支持的 MCP sharing");
|
|
28382
|
+
if (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs < 1e3 || startupTimeoutMs > 3e5) throw new Error("MCP startupTimeoutMs 必须为 1000–300000 的整数");
|
|
28383
|
+
return {
|
|
28384
|
+
startup,
|
|
28385
|
+
sharing,
|
|
28386
|
+
startupTimeoutMs
|
|
28387
|
+
};
|
|
28388
|
+
}
|
|
28389
|
+
//#endregion
|
|
27747
28390
|
//#region src/client/mcpState.ts
|
|
27748
28391
|
/** Owns MCP read lifetimes and one synchronous mutation lock for the entire management page. */
|
|
27749
28392
|
var McpState = class {
|
|
@@ -28712,7 +29355,7 @@ window.__ModuleLoader__.load({
|
|
|
28712
29355
|
if (hasStoredConfig(config[status.languageId])) return true;
|
|
28713
29356
|
return Array.isArray(status.missingEnv) && status.missingEnv.length > 0;
|
|
28714
29357
|
}
|
|
28715
|
-
const PHASE_LABEL
|
|
29358
|
+
const PHASE_LABEL = {
|
|
28716
29359
|
idle: "未启动",
|
|
28717
29360
|
starting: "启动中",
|
|
28718
29361
|
ready: "就绪",
|
|
@@ -28766,7 +29409,7 @@ window.__ModuleLoader__.load({
|
|
|
28766
29409
|
className: "vsm-switch " + (config?.enabled !== false ? "on" : ""),
|
|
28767
29410
|
onClick: () => onToggle(lang.id, config?.enabled !== false ? false : true),
|
|
28768
29411
|
"aria-label": "启用/禁用"
|
|
28769
|
-
}, config?.enabled !== false ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, PHASE_LABEL
|
|
29412
|
+
}, config?.enabled !== false ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, PHASE_LABEL[phase] ?? phase, " · ", SOURCE_LABEL[status?.source] ?? status?.source, status?.providerName ? " · " + status.providerName : "", status?.version ? " · v" + status.version : "", phase === "ready" && status?.root ? " · " + String(status.root).split(/[\\/]/).pop() : ""), status?.reason ? react.default.createElement("div", { className: "vsm-mcp-error" }, status.reason) : null, Array.isArray(status?.candidates) && status.candidates.length > 1 ? react.default.createElement("div", { className: "vsm-lsp-hint" }, "候选(" + status.candidates.length + "):" + status.candidates.map((c) => c.name + (c.version ? " v" + c.version : "") + (c.chosen ? "(当前)" : "")).join(" · ")) : null, Array.isArray(status?.missingEnv) && status.missingEnv.length ? react.default.createElement("div", { className: "vsm-lsp-hint" }, "缺少运行环境(一键安装后自动生效):", status.missingEnv.map((req) => react.default.createElement("div", {
|
|
28770
29413
|
key: req.id,
|
|
28771
29414
|
className: "vsm-mcp-actions"
|
|
28772
29415
|
}, react.default.createElement("span", null, req.label, req.detail ? "(" + req.detail + ")" : ""), req.installable ? react.default.createElement("button", {
|
|
@@ -29911,10 +30554,13 @@ window.__ModuleLoader__.load({
|
|
|
29911
30554
|
args: "",
|
|
29912
30555
|
cwd: "",
|
|
29913
30556
|
url: "",
|
|
29914
|
-
headers: ""
|
|
30557
|
+
headers: "",
|
|
30558
|
+
startup: "on-demand",
|
|
30559
|
+
sharing: "agent",
|
|
30560
|
+
startupTimeoutMs: 6e4
|
|
29915
30561
|
};
|
|
29916
30562
|
/** @private @author ddj 2026年10月08号 @param props Server snapshot, page-wide busy state and actions. @returns Lifecycle-evidence card; enabled does not imply online. */
|
|
29917
|
-
function ServerCard({ server, busy, onRefresh, onToggle, onRemove }) {
|
|
30563
|
+
function ServerCard({ server, busy, onRefresh, onToggle, onRemove, onConnect, onPolicy }) {
|
|
29918
30564
|
return react.default.createElement("article", { className: "vsm-mcp-card" }, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot " + statusTone(server) }), server.serverName), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
|
|
29919
30565
|
disabled: !!busy,
|
|
29920
30566
|
onClick: () => onRefresh(server.id),
|
|
@@ -29931,12 +30577,46 @@ window.__ModuleLoader__.load({
|
|
|
29931
30577
|
title: "配置启用不代表连接在线",
|
|
29932
30578
|
"aria-label": server.enabled ? "禁用配置(不代表在线)" : "启用配置(不代表在线)",
|
|
29933
30579
|
"aria-pressed": !!server.enabled
|
|
29934
|
-
}, server.enabled ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, statusOf(server), " · ", server.toolCount, " 个工具 · ", server.transport, server.instanceCount !== void 0 ? " · " + server.instanceCount + "
|
|
30580
|
+
}, server.enabled ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, statusOf(server), " · ", server.toolCount, " 个工具 · ", server.transport, server.instanceCount !== void 0 ? " · " + server.instanceCount + " 个连接实例" : "", server.sessionCount !== void 0 ? " · " + server.sessionCount + " 个会话使用" : ""), server.policy && react.default.createElement(ProjectPolicy, {
|
|
30581
|
+
server,
|
|
30582
|
+
busy,
|
|
30583
|
+
onConnect,
|
|
30584
|
+
onPolicy
|
|
30585
|
+
}), server.error && react.default.createElement("div", { className: "vsm-mcp-error" }, server.error), react.default.createElement("div", { className: "vsm-mcp-tools" }, server.tools.map((tool) => react.default.createElement("span", {
|
|
29935
30586
|
key: tool.name,
|
|
29936
30587
|
className: "vsm-mcp-chip",
|
|
29937
30588
|
title: tool.description || tool.name
|
|
29938
30589
|
}, tool.name))));
|
|
29939
30590
|
}
|
|
30591
|
+
/** @private @author ddj 2026年10月20号 @param props Project policy and locked actions. @returns Explicit strategy controls and sharing risk notice. */
|
|
30592
|
+
function ProjectPolicy({ server, busy, onConnect, onPolicy }) {
|
|
30593
|
+
/** @author ddj 2026年10月20号 @param event Strategy selection. Saves only an explicit user change. */
|
|
30594
|
+
const startup = (event) => onPolicy?.(server, {
|
|
30595
|
+
...server.policy,
|
|
30596
|
+
startup: event.target.value
|
|
30597
|
+
});
|
|
30598
|
+
/** @author ddj 2026年10月20号 @param event Sharing selection. Requires acknowledgement before state sharing. */
|
|
30599
|
+
const sharing = (event) => {
|
|
30600
|
+
const mode = event.target.value;
|
|
30601
|
+
if (mode === "workspace" && !window.confirm("同一工作区的会话将共享此 MCP 的连接和内部状态;工具串行不等于业务状态隔离。确认服务适合共享?")) return;
|
|
30602
|
+
onPolicy?.(server, {
|
|
30603
|
+
...server.policy,
|
|
30604
|
+
sharing: mode
|
|
30605
|
+
});
|
|
30606
|
+
};
|
|
30607
|
+
return react.default.createElement("div", { className: "vsm-mcp-meta" }, react.default.createElement("label", null, "启动 ", react.default.createElement("select", {
|
|
30608
|
+
disabled: !!busy,
|
|
30609
|
+
value: server.policy.startup,
|
|
30610
|
+
onChange: startup
|
|
30611
|
+
}, react.default.createElement("option", { value: "on-demand" }, "按需启动"), react.default.createElement("option", { value: "background" }, "后台预热"))), react.default.createElement("label", null, " 连接 ", react.default.createElement("select", {
|
|
30612
|
+
disabled: !!busy,
|
|
30613
|
+
value: server.policy.sharing,
|
|
30614
|
+
onChange: sharing
|
|
30615
|
+
}, react.default.createElement("option", { value: "agent" }, "逐会话隔离"), react.default.createElement("option", { value: "workspace" }, "工作区共享(共享状态)"))), react.default.createElement("button", {
|
|
30616
|
+
disabled: !!busy || !server.enabled,
|
|
30617
|
+
onClick: onConnect
|
|
30618
|
+
}, "连接当前活跃会话"), react.default.createElement("small", null, "默认按需启动,不阻塞新对话;首次使用仍需等待。共享仅显式启用。"));
|
|
30619
|
+
}
|
|
29940
30620
|
/** 按项目标题或路径过滤,大小写不敏感。 */
|
|
29941
30621
|
function filterProjects(projects, query) {
|
|
29942
30622
|
const needle = String(query ?? "").trim().toLowerCase();
|
|
@@ -30010,7 +30690,7 @@ window.__ModuleLoader__.load({
|
|
|
30010
30690
|
}, react.default.createElement("span", null, project.title), react.default.createElement("small", null, project.workspacePath, " · ", project.servers.length, " 个 MCP"))) : react.default.createElement("div", { className: "vsm-project-picker-empty" }, "没有匹配的项目"))));
|
|
30011
30691
|
}
|
|
30012
30692
|
/** @private @author ddj 2026年10月08号 @param props Project view, shared busy lock and actions. @returns Project cards with actions disabled for any active mutation. */
|
|
30013
|
-
function ProjectGroup({ project, busy, onAdd, onRefresh, onToggle, onRemove }) {
|
|
30693
|
+
function ProjectGroup({ project, busy, onAdd, onRefresh, onToggle, onRemove, onConnect, onPolicy }) {
|
|
30014
30694
|
const head = react.default.createElement("div", { className: "vsm-project-head" }, react.default.createElement("div", { className: "vsm-project-title" }, react.default.createElement("span", { className: "vsm-project-icon" }, "▣"), react.default.createElement("div", null, react.default.createElement("span", { className: "vsm-project-name" }, project.title), react.default.createElement("span", { className: "vsm-project-path" }, project.workspacePath))), react.default.createElement("button", {
|
|
30015
30695
|
className: "vsm-primary vsm-small",
|
|
30016
30696
|
disabled: !!busy,
|
|
@@ -30024,6 +30704,8 @@ window.__ModuleLoader__.load({
|
|
|
30024
30704
|
key: server.serverName,
|
|
30025
30705
|
server,
|
|
30026
30706
|
busy,
|
|
30707
|
+
onConnect: () => onConnect(project, server),
|
|
30708
|
+
onPolicy: (server, policy) => onPolicy(project, server, policy),
|
|
30027
30709
|
onRefresh: (id) => onRefresh(project, server, id),
|
|
30028
30710
|
onToggle: () => onToggle(project, server),
|
|
30029
30711
|
onRemove: (id) => onRemove(project, server, id)
|
|
@@ -30468,6 +31150,7 @@ window.__ModuleLoader__.load({
|
|
|
30468
31150
|
save: () => saveGlobal(closeGlobalForm),
|
|
30469
31151
|
close: closeGlobalForm
|
|
30470
31152
|
}) : projectForm ? react.default.createElement(McpForm, {
|
|
31153
|
+
project: true,
|
|
30471
31154
|
title: "添加项目 MCP · " + projectForm.title,
|
|
30472
31155
|
draft,
|
|
30473
31156
|
busy,
|
|
@@ -30509,7 +31192,9 @@ window.__ModuleLoader__.load({
|
|
|
30509
31192
|
onAdd: add,
|
|
30510
31193
|
onRefresh: refreshProject,
|
|
30511
31194
|
onToggle: toggleProject,
|
|
30512
|
-
onRemove: removeProject
|
|
31195
|
+
onRemove: removeProject,
|
|
31196
|
+
onConnect: props.connectProject,
|
|
31197
|
+
onPolicy: props.policyProject
|
|
30513
31198
|
}) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目"));
|
|
30514
31199
|
}
|
|
30515
31200
|
/** @private @author ddj 2026年10月08号 @returns Mounted controller reference and its published view. */
|
|
@@ -30570,7 +31255,7 @@ window.__ModuleLoader__.load({
|
|
|
30570
31255
|
return state?.mutate(path + ":" + name, request, commit);
|
|
30571
31256
|
}
|
|
30572
31257
|
/** @private @author ddj 2026年10月08号 @param ref Mounted controller reference. @returns Global/project card actions sharing a synchronous lock. */
|
|
30573
|
-
function cardActions(ref) {
|
|
31258
|
+
function cardActions(ref, getSession) {
|
|
30574
31259
|
/** @author ddj 2026年10月08号 @param id Global identity. @returns Locked refresh. */
|
|
30575
31260
|
const refreshGlobal = (id) => globalAction(ref.current, id, "mcp.refresh", { id });
|
|
30576
31261
|
/** @author ddj 2026年10月08号 @param server Current configuration. @returns Locked enable change. */
|
|
@@ -30586,13 +31271,29 @@ window.__ModuleLoader__.load({
|
|
|
30586
31271
|
const toggleProject = (project, server) => projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectToggle", { enabled: !server.enabled });
|
|
30587
31272
|
/** @author ddj 2026年10月08号 @param project Project. @param server Configuration. @returns Confirmed locked deletion. */
|
|
30588
31273
|
const removeProject = (project, server) => !ref.current?.view.busy && window.confirm("确认删除此项目的 MCP「" + server.serverName + "」?") && projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectRemove");
|
|
31274
|
+
/** @author ddj 2026年10月20号 @param project Owner workspace. @param server Enabled service. @returns Explicit connection for an already-active current session. */
|
|
31275
|
+
const connectProject = (project, server) => {
|
|
31276
|
+
const sessionId = getSession?.();
|
|
31277
|
+
if (!sessionId) return ref.current?.mutate("connect", async () => ({
|
|
31278
|
+
ok: false,
|
|
31279
|
+
error: "请先打开此项目的活跃会话"
|
|
31280
|
+
}), () => void 0);
|
|
31281
|
+
return projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectConnect", { sessionId });
|
|
31282
|
+
};
|
|
31283
|
+
/** @author ddj 2026年10月20号 @param project Owner. @param server Masked editable config. @param policy Explicit user strategy. @returns Serialized strategy persistence. */
|
|
31284
|
+
const policyProject = (project, server, policy) => projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectSave", {
|
|
31285
|
+
config: server.config,
|
|
31286
|
+
policy
|
|
31287
|
+
});
|
|
30589
31288
|
return {
|
|
30590
31289
|
refreshGlobal,
|
|
30591
31290
|
toggleGlobal,
|
|
30592
31291
|
removeGlobal,
|
|
30593
31292
|
refreshProject,
|
|
30594
31293
|
toggleProject,
|
|
30595
|
-
removeProject
|
|
31294
|
+
removeProject,
|
|
31295
|
+
connectProject,
|
|
31296
|
+
policyProject
|
|
30596
31297
|
};
|
|
30597
31298
|
}
|
|
30598
31299
|
/** @private @author ddj 2026年10月08号 @param state Controller. @param draft Editable fields. @param setDraft Draft setter. @param path Optional project target. @param close Success-only close callback. @returns Locked save, validation included. */
|
|
@@ -30603,7 +31304,12 @@ window.__ModuleLoader__.load({
|
|
|
30603
31304
|
return path === null ? rpc("mcp.save", { config }) : rpc("mcp.projectSave", {
|
|
30604
31305
|
workspacePath: path,
|
|
30605
31306
|
serverName: config.serverName,
|
|
30606
|
-
config
|
|
31307
|
+
config,
|
|
31308
|
+
policy: parseMcpPolicy({
|
|
31309
|
+
startup: draft.startup,
|
|
31310
|
+
sharing: draft.sharing,
|
|
31311
|
+
startupTimeoutMs: Number(draft.startupTimeoutMs)
|
|
31312
|
+
})
|
|
30607
31313
|
});
|
|
30608
31314
|
};
|
|
30609
31315
|
/** @author ddj 2026年10月08号 @param result Save result. @description Update current view and only then reset/close the form. */
|
|
@@ -30636,7 +31342,7 @@ window.__ModuleLoader__.load({
|
|
|
30636
31342
|
return cleanup;
|
|
30637
31343
|
};
|
|
30638
31344
|
react.default.useEffect(selectTab, [tab]);
|
|
30639
|
-
const { refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject } = cardActions(ref);
|
|
31345
|
+
const { refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject, connectProject, policyProject } = cardActions(ref, props?.getMcpSession);
|
|
30640
31346
|
/** @author ddj 2026年10月08号 @param close Success-only close callback. @returns Global save completion. */
|
|
30641
31347
|
const saveGlobal = (close) => saveDraft(ref.current, draft, setDraft, null, close);
|
|
30642
31348
|
/** @author ddj 2026年10月08号 @param path Target project. @param close Success-only close callback. @returns Project save completion. */
|
|
@@ -30667,6 +31373,8 @@ window.__ModuleLoader__.load({
|
|
|
30667
31373
|
resetDraft,
|
|
30668
31374
|
saveGlobal,
|
|
30669
31375
|
saveProject,
|
|
31376
|
+
connectProject,
|
|
31377
|
+
policyProject,
|
|
30670
31378
|
refreshGlobal,
|
|
30671
31379
|
toggleGlobal,
|
|
30672
31380
|
removeGlobal,
|
|
@@ -30699,7 +31407,7 @@ window.__ModuleLoader__.load({
|
|
|
30699
31407
|
}, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body);
|
|
30700
31408
|
}
|
|
30701
31409
|
/** @private @author ddj 2026年10月08号 @param props Draft, shared busy lock, edit/save/close callbacks. @returns Lossless argument/header form; failed save stays open. */
|
|
30702
|
-
function McpForm({ title, draft, busy, edit, save, close }) {
|
|
31410
|
+
function McpForm({ title, draft, busy, edit, save, close, project }) {
|
|
30703
31411
|
/** @author ddj 2026年10月08号 @param key Draft field. @returns Short input change callback. */
|
|
30704
31412
|
const change = (key) => (event) => edit(key, event.target.value);
|
|
30705
31413
|
/** @author ddj 2026年10月08号 @param key Draft field. @param placeholder Input hint. @returns Disabled-while-busy input. */
|
|
@@ -30720,7 +31428,15 @@ window.__ModuleLoader__.load({
|
|
|
30720
31428
|
disabled: !!busy,
|
|
30721
31429
|
value: draft.transport,
|
|
30722
31430
|
onChange: change("transport")
|
|
30723
|
-
}, react.default.createElement("option", { value: "stdio" }, "stdio"), react.default.createElement("option", { value: "streamable-http" }, "streamable-http"))), fields, react.default.createElement("
|
|
31431
|
+
}, react.default.createElement("option", { value: "stdio" }, "stdio"), react.default.createElement("option", { value: "streamable-http" }, "streamable-http"))), fields, project && react.default.createElement("label", null, "启动策略", react.default.createElement("select", {
|
|
31432
|
+
disabled: !!busy,
|
|
31433
|
+
value: draft.startup,
|
|
31434
|
+
onChange: change("startup")
|
|
31435
|
+
}, react.default.createElement("option", { value: "on-demand" }, "按需启动(默认)"), react.default.createElement("option", { value: "background" }, "后台预热"))), project && react.default.createElement("label", null, "连接模式", react.default.createElement("select", {
|
|
31436
|
+
disabled: !!busy,
|
|
31437
|
+
value: draft.sharing,
|
|
31438
|
+
onChange: change("sharing")
|
|
31439
|
+
}, react.default.createElement("option", { value: "agent" }, "逐会话隔离(默认)"), react.default.createElement("option", { value: "workspace" }, "工作区共享(共享服务内部状态)"))), project && react.default.createElement("label", null, "初始化预算(毫秒,1000–300000)", input("startupTimeoutMs", "60000")), project && react.default.createElement("small", null, "新对话不等待连接。共享必须确认服务适合共享状态;不为 CodeGraph 自动启用。"), react.default.createElement("div", { className: "vsm-mcp-dialog-actions" }, react.default.createElement("button", {
|
|
30724
31440
|
disabled: !!busy,
|
|
30725
31441
|
onClick: close
|
|
30726
31442
|
}, "取消"), react.default.createElement("button", {
|
|
@@ -30730,6 +31446,218 @@ window.__ModuleLoader__.load({
|
|
|
30730
31446
|
}, "保存配置"))));
|
|
30731
31447
|
}
|
|
30732
31448
|
//#endregion
|
|
31449
|
+
//#region src/client/mcpSessionState.ts
|
|
31450
|
+
/** Shared, read-only sidebar MCP polling. @author ddj 2026年10月20号 */
|
|
31451
|
+
const unavailable = { stale: true };
|
|
31452
|
+
const inactive = { stale: false };
|
|
31453
|
+
/** One poller for all mounted row/hover consumers; never retains or activates sessions. */
|
|
31454
|
+
var McpSessionState = class {
|
|
31455
|
+
fetchStatus;
|
|
31456
|
+
page;
|
|
31457
|
+
views = /* @__PURE__ */ new Map();
|
|
31458
|
+
listeners = /* @__PURE__ */ new Map();
|
|
31459
|
+
timer;
|
|
31460
|
+
request;
|
|
31461
|
+
epoch = 0;
|
|
31462
|
+
stale = true;
|
|
31463
|
+
disposed = false;
|
|
31464
|
+
/** @public @author ddj 2026年10月20号 @param fetchStatus Authenticated read-only query. @param page Visibility boundary, omitted for server tests. */
|
|
31465
|
+
constructor(fetchStatus, page) {
|
|
31466
|
+
this.fetchStatus = fetchStatus;
|
|
31467
|
+
this.page = page;
|
|
31468
|
+
}
|
|
31469
|
+
/** @public @author ddj 2026年10月20号 @param sessionId Row identity. @returns Stable view until the next observation. */
|
|
31470
|
+
get(sessionId) {
|
|
31471
|
+
return this.views.get(sessionId) ?? (this.stale ? unavailable : inactive);
|
|
31472
|
+
}
|
|
31473
|
+
/** @public @author ddj 2026年10月20号 @param sessionId Row identity. @param listener React invalidation. @returns Idempotent unsubscribe; the last consumer stops polling. */
|
|
31474
|
+
subscribe(sessionId, listener) {
|
|
31475
|
+
if (this.disposed) return () => void 0;
|
|
31476
|
+
const first = this.listeners.size === 0;
|
|
31477
|
+
const listeners = this.listeners.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
31478
|
+
listeners.add(listener);
|
|
31479
|
+
this.listeners.set(sessionId, listeners);
|
|
31480
|
+
if (first) {
|
|
31481
|
+
this.page?.addEventListener("visibilitychange", this.visibility);
|
|
31482
|
+
this.poll();
|
|
31483
|
+
}
|
|
31484
|
+
return () => {
|
|
31485
|
+
listeners.delete(listener);
|
|
31486
|
+
if (listeners.size === 0) this.listeners.delete(sessionId);
|
|
31487
|
+
if (this.listeners.size === 0) this.pause();
|
|
31488
|
+
};
|
|
31489
|
+
}
|
|
31490
|
+
/** @private @author ddj 2026年10月20号 Stops pending work when hidden; resuming never accepts an old response. */
|
|
31491
|
+
visibility = () => {
|
|
31492
|
+
if (this.page?.hidden) this.invalidate();
|
|
31493
|
+
else this.poll();
|
|
31494
|
+
};
|
|
31495
|
+
/** @private @author ddj 2026年10月20号 Invalidates responses without allowing overlapping transport requests. */
|
|
31496
|
+
invalidate() {
|
|
31497
|
+
this.epoch += 1;
|
|
31498
|
+
clearTimeout(this.timer);
|
|
31499
|
+
this.timer = void 0;
|
|
31500
|
+
this.request?.abort();
|
|
31501
|
+
this.publish(void 0);
|
|
31502
|
+
}
|
|
31503
|
+
/** @private @author ddj 2026年10月20号 Cleans listener lifetime as well as timers. */
|
|
31504
|
+
pause() {
|
|
31505
|
+
this.page?.removeEventListener("visibilitychange", this.visibility);
|
|
31506
|
+
this.invalidate();
|
|
31507
|
+
}
|
|
31508
|
+
/** @public @author ddj 2026年10月20号 Permanently disposes HMR-owned polling. */
|
|
31509
|
+
dispose() {
|
|
31510
|
+
this.disposed = true;
|
|
31511
|
+
this.pause();
|
|
31512
|
+
this.listeners.clear();
|
|
31513
|
+
this.views.clear();
|
|
31514
|
+
}
|
|
31515
|
+
/** @private @author ddj 2026年10月20号 @param snapshot Fresh ledger, or undefined to mark all previous observations stale. */
|
|
31516
|
+
publish(snapshot) {
|
|
31517
|
+
this.stale = snapshot === void 0;
|
|
31518
|
+
if (snapshot) this.views = new Map(snapshot.sessions.map((status) => [status.sessionId, {
|
|
31519
|
+
status,
|
|
31520
|
+
stale: false
|
|
31521
|
+
}]));
|
|
31522
|
+
else for (const [id, view] of this.views) this.views.set(id, {
|
|
31523
|
+
...view,
|
|
31524
|
+
stale: true
|
|
31525
|
+
});
|
|
31526
|
+
for (const listeners of this.listeners.values()) for (const listener of listeners) listener();
|
|
31527
|
+
}
|
|
31528
|
+
/** @private @author ddj 2026年10月20号 Queries once, with a 10-second deadline and a 2.5-second gap after settlement. */
|
|
31529
|
+
async poll() {
|
|
31530
|
+
if (this.disposed || this.request || !this.listeners.size || this.page?.hidden) return;
|
|
31531
|
+
const request = new AbortController();
|
|
31532
|
+
const epoch = this.epoch;
|
|
31533
|
+
this.request = request;
|
|
31534
|
+
/** @author ddj 2026年10月20号 Marks overdue responses unusable even if a transport ignores cancellation. */
|
|
31535
|
+
const expire = () => {
|
|
31536
|
+
request.abort();
|
|
31537
|
+
if (epoch === this.epoch) this.publish(void 0);
|
|
31538
|
+
};
|
|
31539
|
+
const deadline = setTimeout(expire, 1e4);
|
|
31540
|
+
try {
|
|
31541
|
+
const snapshot = await this.fetchStatus(request.signal);
|
|
31542
|
+
if (epoch === this.epoch && !request.signal.aborted) this.publish(snapshot);
|
|
31543
|
+
} catch {
|
|
31544
|
+
if (epoch === this.epoch) this.publish(void 0);
|
|
31545
|
+
} finally {
|
|
31546
|
+
clearTimeout(deadline);
|
|
31547
|
+
this.request = void 0;
|
|
31548
|
+
if (!this.disposed && this.listeners.size && !this.page?.hidden) this.timer = setTimeout(() => {
|
|
31549
|
+
this.poll();
|
|
31550
|
+
}, 2500);
|
|
31551
|
+
}
|
|
31552
|
+
}
|
|
31553
|
+
};
|
|
31554
|
+
/** @public @author ddj 2026年10月20号 @returns Browser-owned singleton per plugin apply; legacy Hosts fail safely as stale. */
|
|
31555
|
+
function mcpSessionState() {
|
|
31556
|
+
/** @author ddj 2026年10月20号 @param signal Fetch lifetime. @returns Current in-memory host ledger. @throws RPC errors. */
|
|
31557
|
+
const fetchStatus = async (signal) => {
|
|
31558
|
+
const result = await rpc("mcp.sessionSnapshot", {}, signal);
|
|
31559
|
+
if (!result.ok) throw new Error("MCP 状态暂不可用");
|
|
31560
|
+
return result;
|
|
31561
|
+
};
|
|
31562
|
+
return new McpSessionState(fetchStatus, document);
|
|
31563
|
+
}
|
|
31564
|
+
//#endregion
|
|
31565
|
+
//#region src/client/ui/McpSessionStatus.ts
|
|
31566
|
+
/** Official session-row slots; no DOM patching or session activation. @author ddj 2026年10月20号 */
|
|
31567
|
+
const labels = {
|
|
31568
|
+
idle: "待使用,尚未启动",
|
|
31569
|
+
preparing: "读取项目配置",
|
|
31570
|
+
queued: "等待 MCP 启动名额",
|
|
31571
|
+
loading: "加载 MCP 插件模块",
|
|
31572
|
+
starting: "等待 MCP 插件启动",
|
|
31573
|
+
stopping: "释放旧 MCP 实例",
|
|
31574
|
+
ready: "初始化完成,连接健康未验证",
|
|
31575
|
+
error: "初始化异常",
|
|
31576
|
+
none: "无需加载项目 MCP"
|
|
31577
|
+
};
|
|
31578
|
+
const busy = /* @__PURE__ */ new Set([
|
|
31579
|
+
"preparing",
|
|
31580
|
+
"queued",
|
|
31581
|
+
"loading",
|
|
31582
|
+
"starting",
|
|
31583
|
+
"stopping"
|
|
31584
|
+
]);
|
|
31585
|
+
/** @private @author ddj 2026年10月20号 @param props Root row identity and shared poller. @returns Stable per-session observation. */
|
|
31586
|
+
function useMcpView({ sessionId, store }) {
|
|
31587
|
+
const subscribe = (0, react.useCallback)((notify) => store.subscribe(sessionId, notify), [store, sessionId]);
|
|
31588
|
+
const snapshot = (0, react.useCallback)(() => store.get(sessionId), [store, sessionId]);
|
|
31589
|
+
return (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot);
|
|
31590
|
+
}
|
|
31591
|
+
/** @public @author ddj 2026年10月20号 @param view Current observation. @returns Accessible row label; steady states deliberately do not occupy the leading slot. */
|
|
31592
|
+
function mcpRowLabel(view) {
|
|
31593
|
+
if (view.stale) return "项目 MCP 状态暂不可用(上次状态已过期)";
|
|
31594
|
+
if (!view.status || !busy.has(view.status.phase) && view.status.phase !== "error") return void 0;
|
|
31595
|
+
return "项目 MCP:" + labels[view.status.phase];
|
|
31596
|
+
}
|
|
31597
|
+
/** @public @author ddj 2026年10月20号 @param props Official row sessionId and poller. @returns 16px loading/error marker; official running/approval states retain precedence. */
|
|
31598
|
+
function McpSessionIcon(props) {
|
|
31599
|
+
const view = useMcpView(props);
|
|
31600
|
+
const label = mcpRowLabel(view);
|
|
31601
|
+
if (!label) return null;
|
|
31602
|
+
const warning = view.stale || view.status?.phase === "error";
|
|
31603
|
+
return react.default.createElement("span", {
|
|
31604
|
+
role: "img",
|
|
31605
|
+
"aria-label": label,
|
|
31606
|
+
title: label,
|
|
31607
|
+
style: {
|
|
31608
|
+
display: "inline-flex",
|
|
31609
|
+
width: 16,
|
|
31610
|
+
height: 16,
|
|
31611
|
+
alignItems: "center",
|
|
31612
|
+
justifyContent: "center",
|
|
31613
|
+
color: warning ? "var(--color-warning, #b7791f)" : "var(--color-text-secondary, currentColor)",
|
|
31614
|
+
fontSize: 13
|
|
31615
|
+
}
|
|
31616
|
+
}, warning ? "⚠" : "◌");
|
|
31617
|
+
}
|
|
31618
|
+
/** @private @author ddj 2026年10月20号 @param start Stage start. @param end Stage finish or local clock. @returns Human-readable elapsed time, without claiming a timeout. */
|
|
31619
|
+
function elapsed(start, end) {
|
|
31620
|
+
return Math.max(0, (end - start) / 1e3).toFixed(1) + " 秒";
|
|
31621
|
+
}
|
|
31622
|
+
/** @public @author ddj 2026年10月20号 @param props Official hover sessionId and poller. @returns Project-only diagnostic detail, never transport credentials. */
|
|
31623
|
+
function McpSessionDetail(props) {
|
|
31624
|
+
const view = useMcpView(props);
|
|
31625
|
+
const [now, setNow] = (0, react.useState)(Date.now);
|
|
31626
|
+
const status = view.status;
|
|
31627
|
+
(0, react.useEffect)(() => {
|
|
31628
|
+
if (!status || view.stale || !busy.has(status.phase)) return;
|
|
31629
|
+
const tick = setInterval(() => setNow(Date.now()), 1e3);
|
|
31630
|
+
return () => clearInterval(tick);
|
|
31631
|
+
}, [status, view.stale]);
|
|
31632
|
+
const children = [react.default.createElement("strong", { key: "title" }, "项目 MCP")];
|
|
31633
|
+
if (view.stale) children.push(react.default.createElement("div", {
|
|
31634
|
+
key: "stale",
|
|
31635
|
+
role: "status"
|
|
31636
|
+
}, "状态暂不可用;以下如有记录,为上次观测结果"));
|
|
31637
|
+
if (!status) children.push(react.default.createElement("div", { key: "empty" }, view.stale ? "等待状态查询" : "当前无活跃 MCP 实例"));
|
|
31638
|
+
else {
|
|
31639
|
+
const end = status.finishedAt ?? now;
|
|
31640
|
+
const completed = status.servers.filter((server) => server.phase === "ready").length;
|
|
31641
|
+
const slow = !view.stale && busy.has(status.phase) && now - status.phaseAt > 1e4;
|
|
31642
|
+
children.push(react.default.createElement("div", { key: "phase" }, labels[status.phase], slow ? "(耗时较长,不代表失败)" : ""));
|
|
31643
|
+
children.push(react.default.createElement("div", { key: "time" }, `${completed}/${status.servers.length} 已初始化 · 总耗时 ${elapsed(status.startedAt, end)} · 当前阶段 ${elapsed(status.phaseAt, end)}`));
|
|
31644
|
+
if (status.configMs !== void 0) children.push(react.default.createElement("div", { key: "config" }, `配置准备 ${elapsed(0, status.configMs)}`));
|
|
31645
|
+
if (status.error) children.push(react.default.createElement("div", { key: "error" }, status.error));
|
|
31646
|
+
for (const server of status.servers) children.push(react.default.createElement("div", { key: "server:" + server.serverName }, `${server.serverName}:${labels[server.phase]}${server.reused ? " · 复用已有连接" : ""}${server.phase === "idle" ? "" : " · " + elapsed(server.startedAt, server.finishedAt ?? now)}`, server.queuedMs === void 0 ? "" : " · 排队 " + elapsed(0, server.queuedMs), server.connectMs === void 0 ? "" : " · 初始化 " + elapsed(0, server.connectMs), server.firstCallMs === void 0 ? "" : " · 首次工具执行 " + elapsed(0, server.firstCallMs), server.error ? " · " + server.error : ""));
|
|
31647
|
+
}
|
|
31648
|
+
children.push(react.default.createElement("small", { key: "scope" }, "仅 VSCodeMode 管理的项目 MCP;不含全局或 preset MCP。"));
|
|
31649
|
+
return react.default.createElement("section", {
|
|
31650
|
+
"aria-label": "项目 MCP 状态",
|
|
31651
|
+
style: {
|
|
31652
|
+
display: "grid",
|
|
31653
|
+
gap: 4,
|
|
31654
|
+
maxWidth: 320,
|
|
31655
|
+
overflowWrap: "anywhere",
|
|
31656
|
+
fontSize: 12
|
|
31657
|
+
}
|
|
31658
|
+
}, children);
|
|
31659
|
+
}
|
|
31660
|
+
//#endregion
|
|
30733
31661
|
//#region src/client/ui/PluginDetailSection.ts
|
|
30734
31662
|
/**
|
|
30735
31663
|
* dsh-vscode-mode client — 官方插件管理器详情页的只读状态段。
|
|
@@ -31815,8 +32743,11 @@ window.__ModuleLoader__.load({
|
|
|
31815
32743
|
function createSidebarPanelRegistry() {
|
|
31816
32744
|
const entries = /* @__PURE__ */ new Map();
|
|
31817
32745
|
const listeners = /* @__PURE__ */ new Set();
|
|
32746
|
+
/** @private @author ddj 2026年10月22号 @description Isolate subscribers and snapshot notification membership against reentrant changes. */
|
|
31818
32747
|
const notify = () => {
|
|
31819
|
-
for (const listener of listeners)
|
|
32748
|
+
for (const listener of [...listeners]) try {
|
|
32749
|
+
listener();
|
|
32750
|
+
} catch {}
|
|
31820
32751
|
};
|
|
31821
32752
|
const list = () => [...entries.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
31822
32753
|
return {
|
|
@@ -31932,7 +32863,7 @@ window.__ModuleLoader__.load({
|
|
|
31932
32863
|
* 作者 ddj 2026-08-28
|
|
31933
32864
|
*/
|
|
31934
32865
|
/** localStorage 键前缀(按工作区作用域隔离,无 cwd 会话回退会话键)。 */
|
|
31935
|
-
const KEY_PREFIX$
|
|
32866
|
+
const KEY_PREFIX$1 = CACHE_KEY.expanded;
|
|
31936
32867
|
/**
|
|
31937
32868
|
* 归一化展开路径:去空白、反斜杠→斜杠、去首尾斜杠,`..` 段拒绝。
|
|
31938
32869
|
* @author ddj 2026年08月28号
|
|
@@ -32010,7 +32941,7 @@ window.__ModuleLoader__.load({
|
|
|
32010
32941
|
*/
|
|
32011
32942
|
function explorerLoad(scope) {
|
|
32012
32943
|
try {
|
|
32013
|
-
return parseExplorer(window.localStorage.getItem(KEY_PREFIX$
|
|
32944
|
+
return parseExplorer(window.localStorage.getItem(KEY_PREFIX$1 + String(scope)));
|
|
32014
32945
|
} catch (error) {
|
|
32015
32946
|
return null;
|
|
32016
32947
|
}
|
|
@@ -32023,12 +32954,12 @@ window.__ModuleLoader__.load({
|
|
|
32023
32954
|
*/
|
|
32024
32955
|
function explorerSave(scope, data) {
|
|
32025
32956
|
try {
|
|
32026
|
-
window.localStorage.setItem(KEY_PREFIX$
|
|
32957
|
+
window.localStorage.setItem(KEY_PREFIX$1 + String(scope), serializeExplorer(data));
|
|
32027
32958
|
} catch (error) {}
|
|
32028
32959
|
}
|
|
32029
32960
|
//#endregion
|
|
32030
32961
|
//#region src/client/state/explorerEntriesCache.ts
|
|
32031
|
-
const KEY_PREFIX
|
|
32962
|
+
const KEY_PREFIX = CACHE_KEY.entries;
|
|
32032
32963
|
/** 裁剪到上限(目录按 ts 逐出最旧;单目录条目截断)。 */
|
|
32033
32964
|
function entriesTrim(data, maxDirs = 300, maxEntries = 500) {
|
|
32034
32965
|
const dirs = {};
|
|
@@ -32102,7 +33033,7 @@ window.__ModuleLoader__.load({
|
|
|
32102
33033
|
/** localStorage 读取(不可用 → null)。 */
|
|
32103
33034
|
function readRaw(scope) {
|
|
32104
33035
|
try {
|
|
32105
|
-
return window.localStorage.getItem(KEY_PREFIX
|
|
33036
|
+
return window.localStorage.getItem(KEY_PREFIX + scope);
|
|
32106
33037
|
} catch (error) {
|
|
32107
33038
|
return null;
|
|
32108
33039
|
}
|
|
@@ -32110,7 +33041,7 @@ window.__ModuleLoader__.load({
|
|
|
32110
33041
|
/** localStorage 写入(配额/隐私模式失败静默)。 */
|
|
32111
33042
|
function writeRaw(scope, text) {
|
|
32112
33043
|
try {
|
|
32113
|
-
window.localStorage.setItem(KEY_PREFIX
|
|
33044
|
+
window.localStorage.setItem(KEY_PREFIX + scope, text);
|
|
32114
33045
|
} catch (error) {}
|
|
32115
33046
|
}
|
|
32116
33047
|
/** 取(或建)作用域缓存镜像。 */
|
|
@@ -32730,12 +33661,12 @@ window.__ModuleLoader__.load({
|
|
|
32730
33661
|
setTarget(next);
|
|
32731
33662
|
if (next) current.prepare(next.path);
|
|
32732
33663
|
}
|
|
32733
|
-
/** @private @author ddj 2026年10月
|
|
33664
|
+
/** @private @author ddj 2026年10月22号 Attempts virtual data-index location or the original mounted-row fallback after commit/resize; consumes only a successful current request. */
|
|
32734
33665
|
function tryReveal() {
|
|
32735
33666
|
const next = pending.current;
|
|
32736
33667
|
const current = latest.current;
|
|
32737
33668
|
if (!next || next.scope !== current.scope || next.active !== current.activePath) return;
|
|
32738
|
-
if (!revealTreeRow(current.treeRef.current, next.path)) return;
|
|
33669
|
+
if (!(current.locate ? current.locate(next.path) : revealTreeRow(current.treeRef.current, next.path))) return;
|
|
32739
33670
|
pending.current = null;
|
|
32740
33671
|
setRevealPath(next.path);
|
|
32741
33672
|
clearHighlight();
|
|
@@ -32778,6 +33709,106 @@ window.__ModuleLoader__.load({
|
|
|
32778
33709
|
requestReveal
|
|
32779
33710
|
};
|
|
32780
33711
|
}
|
|
33712
|
+
/**
|
|
33713
|
+
* @public @author ddj 2026年10月22号
|
|
33714
|
+
* @description Preserve the first visible path when rows above it expand or collapse.
|
|
33715
|
+
* @param previous Previous complete model.
|
|
33716
|
+
* @param rows New complete model.
|
|
33717
|
+
* @param top Previous scroll offset.
|
|
33718
|
+
* @returns Anchored offset, or original offset for subsequent clamping if path disappeared.
|
|
33719
|
+
*/
|
|
33720
|
+
function treeAnchorTop(previous, rows, top) {
|
|
33721
|
+
const anchor = previous[Math.floor(Math.max(0, top) / 24)]?.key;
|
|
33722
|
+
if (!anchor) return top;
|
|
33723
|
+
const nextIndex = rows.findIndex((row) => row.key === anchor);
|
|
33724
|
+
if (nextIndex < 0) return top;
|
|
33725
|
+
return nextIndex * 24 + top % 24;
|
|
33726
|
+
}
|
|
33727
|
+
/**
|
|
33728
|
+
* @public @author ddj 2026年10月22号
|
|
33729
|
+
* @description Flatten all expanded entries in their supplied order, including metadata files.
|
|
33730
|
+
* @param entriesOf Directory snapshot lookup; null means unavailable.
|
|
33731
|
+
* @param expanded Current directory expansion flags.
|
|
33732
|
+
* @param loading Current loading flags.
|
|
33733
|
+
* @returns Complete row model, independent of viewport mounting.
|
|
33734
|
+
*/
|
|
33735
|
+
function flattenTree(entriesOf, expanded, loading) {
|
|
33736
|
+
const rows = [];
|
|
33737
|
+
const pending = [{
|
|
33738
|
+
rel: "",
|
|
33739
|
+
depth: 0
|
|
33740
|
+
}];
|
|
33741
|
+
while (pending.length) {
|
|
33742
|
+
const current = pending.pop();
|
|
33743
|
+
const entry = current.entry;
|
|
33744
|
+
if (entry) {
|
|
33745
|
+
const isDir = entry.type === "directory";
|
|
33746
|
+
const isOpen = expanded[entry.path] === true;
|
|
33747
|
+
rows.push({
|
|
33748
|
+
key: entry.path,
|
|
33749
|
+
e: entry,
|
|
33750
|
+
depth: current.depth,
|
|
33751
|
+
isDir,
|
|
33752
|
+
isOpen
|
|
33753
|
+
});
|
|
33754
|
+
if (isDir && isOpen) pending.push({
|
|
33755
|
+
rel: entry.path,
|
|
33756
|
+
depth: current.depth + 1
|
|
33757
|
+
});
|
|
33758
|
+
continue;
|
|
33759
|
+
}
|
|
33760
|
+
const entries = entriesOf(current.rel);
|
|
33761
|
+
if (!entries) {
|
|
33762
|
+
rows.push({
|
|
33763
|
+
key: current.rel + ":loading",
|
|
33764
|
+
depth: current.depth,
|
|
33765
|
+
text: loading[current.rel] ? "加载中…" : "无法加载"
|
|
33766
|
+
});
|
|
33767
|
+
continue;
|
|
33768
|
+
}
|
|
33769
|
+
for (let index = entries.length - 1; index >= 0; index--) pending.push({
|
|
33770
|
+
rel: current.rel,
|
|
33771
|
+
depth: current.depth,
|
|
33772
|
+
entry: entries[index]
|
|
33773
|
+
});
|
|
33774
|
+
}
|
|
33775
|
+
return rows;
|
|
33776
|
+
}
|
|
33777
|
+
/**
|
|
33778
|
+
* @public @author ddj 2026年10月22号
|
|
33779
|
+
* @description Clamp scroll state before reusing the existing fixed-height range calculation.
|
|
33780
|
+
* @param count Complete row count.
|
|
33781
|
+
* @param viewport Latest scroll position and measured viewport height.
|
|
33782
|
+
* @returns Clamped scroll offset and overscanned exclusive row range.
|
|
33783
|
+
*/
|
|
33784
|
+
function treeWindow(count, viewport) {
|
|
33785
|
+
const height = Math.max(0, viewport.height);
|
|
33786
|
+
const top = Math.min(Math.max(0, viewport.top), Math.max(0, count * 24 - height));
|
|
33787
|
+
return {
|
|
33788
|
+
top,
|
|
33789
|
+
...updateRange(count, top, height, 24)
|
|
33790
|
+
};
|
|
33791
|
+
}
|
|
33792
|
+
/**
|
|
33793
|
+
* @public @author ddj 2026年10月22号
|
|
33794
|
+
* @description Locate an unmounted row using model indices, without querying row DOM.
|
|
33795
|
+
* @param rows Complete flattened model.
|
|
33796
|
+
* @param path Requested relative file path.
|
|
33797
|
+
* @param viewport Current viewport geometry.
|
|
33798
|
+
* @returns New scroll offset, or null while ancestors/viewport are unavailable.
|
|
33799
|
+
*/
|
|
33800
|
+
function treeRevealTop(rows, path, viewport) {
|
|
33801
|
+
if (viewport.height <= 0) return null;
|
|
33802
|
+
const index = rows.findIndex((row) => row.e?.path === path);
|
|
33803
|
+
if (index < 0) return null;
|
|
33804
|
+
const current = treeWindow(rows.length, viewport).top;
|
|
33805
|
+
const rowTop = index * 24;
|
|
33806
|
+
if (rowTop >= current && rowTop + 24 <= current + viewport.height) return current;
|
|
33807
|
+
return treeWindow(rows.length, {
|
|
33808
|
+
top: rowTop - (viewport.height - 24) / 2,
|
|
33809
|
+
height: viewport.height
|
|
33810
|
+
}).top;
|
|
33811
|
+
}
|
|
32781
33812
|
//#endregion
|
|
32782
33813
|
//#region src/client/sidebar/panels/FileExplorer.ts
|
|
32783
33814
|
/**
|
|
@@ -32791,7 +33822,6 @@ window.__ModuleLoader__.load({
|
|
|
32791
33822
|
* 远端更新箭头由显式只读检查提供,与本地状态并存,不执行更新。
|
|
32792
33823
|
* 作者 ddj 2026-08-26 / 2026-08-27 / 2026-08-31 / 2026-09-10 / 2026-09-16 / 2026-10-09
|
|
32793
33824
|
*/
|
|
32794
|
-
const DIR_CAP = 4e3;
|
|
32795
33825
|
const SAVE_DEBOUNCE_MS = 300;
|
|
32796
33826
|
const FOLLOW_INTERVAL_MS = 1e4;
|
|
32797
33827
|
const PREFETCH_MAX = 4;
|
|
@@ -32890,6 +33920,11 @@ window.__ModuleLoader__.load({
|
|
|
32890
33920
|
const loadDirRef = react.default.useRef(null);
|
|
32891
33921
|
const refreshRef = react.default.useRef(null);
|
|
32892
33922
|
const treeRef = react.default.useRef(null);
|
|
33923
|
+
const [viewport, setViewport] = react.default.useState({
|
|
33924
|
+
scope,
|
|
33925
|
+
top: 0,
|
|
33926
|
+
height: 0
|
|
33927
|
+
});
|
|
32893
33928
|
const reloadDirRef = react.default.useRef(null);
|
|
32894
33929
|
const changedTimerRef = react.default.useRef(null);
|
|
32895
33930
|
const changedRelRef = react.default.useRef(/* @__PURE__ */ new Set());
|
|
@@ -33096,12 +34131,6 @@ window.__ModuleLoader__.load({
|
|
|
33096
34131
|
window.addEventListener("edrv:refresh", onRefresh);
|
|
33097
34132
|
return () => window.removeEventListener("edrv:refresh", onRefresh);
|
|
33098
34133
|
}, []);
|
|
33099
|
-
const { revealPath } = useExplorerReveal({
|
|
33100
|
-
scope,
|
|
33101
|
-
activePath,
|
|
33102
|
-
treeRef,
|
|
33103
|
-
prepare: prepareReveal
|
|
33104
|
-
});
|
|
33105
34134
|
const activeTreePath = treeRevealPath(activePath);
|
|
33106
34135
|
react.default.useEffect(() => {
|
|
33107
34136
|
if (!scope) return;
|
|
@@ -33113,39 +34142,9 @@ window.__ModuleLoader__.load({
|
|
|
33113
34142
|
}, FOLLOW_INTERVAL_MS);
|
|
33114
34143
|
return () => clearInterval(timer);
|
|
33115
34144
|
}, [scope]);
|
|
33116
|
-
/** @private @author ddj 2026年10月
|
|
34145
|
+
/** @private @author ddj 2026年10月22号 @returns Complete expanded model; icon-only updates do not flatten again. */
|
|
33117
34146
|
function flatten() {
|
|
33118
|
-
|
|
33119
|
-
/** @private @author ddj 2026年10月10号 @param rel Directory. @param depth Indent level. Appends only expanded rows and placeholders. */
|
|
33120
|
-
function visit(rel, depth) {
|
|
33121
|
-
const entries = dirs[rel] ?? entriesCacheGet(scope, rel);
|
|
33122
|
-
if (!entries) {
|
|
33123
|
-
result.push({
|
|
33124
|
-
key: rel + ":loading",
|
|
33125
|
-
depth,
|
|
33126
|
-
text: loading[rel] ? "加载中…" : "无法加载"
|
|
33127
|
-
});
|
|
33128
|
-
return;
|
|
33129
|
-
}
|
|
33130
|
-
for (const e of entries) {
|
|
33131
|
-
const isDir = e.type === "directory", isOpen = expanded[e.path] === true;
|
|
33132
|
-
result.push({
|
|
33133
|
-
key: e.path,
|
|
33134
|
-
e,
|
|
33135
|
-
depth,
|
|
33136
|
-
isDir,
|
|
33137
|
-
isOpen
|
|
33138
|
-
});
|
|
33139
|
-
if (isDir && isOpen) visit(e.path, depth + 1);
|
|
33140
|
-
}
|
|
33141
|
-
if (entries.length >= DIR_CAP) result.push({
|
|
33142
|
-
key: rel + ":cap",
|
|
33143
|
-
depth,
|
|
33144
|
-
text: "目录条目过多,仅显示前 4000 项"
|
|
33145
|
-
});
|
|
33146
|
-
}
|
|
33147
|
-
visit("", 0);
|
|
33148
|
-
return result;
|
|
34147
|
+
return flattenTree(entriesOf, expanded, loading);
|
|
33149
34148
|
}
|
|
33150
34149
|
const treeRows = react.default.useMemo(flatten, [
|
|
33151
34150
|
dirs,
|
|
@@ -33153,6 +34152,99 @@ window.__ModuleLoader__.load({
|
|
|
33153
34152
|
loading,
|
|
33154
34153
|
scope
|
|
33155
34154
|
]);
|
|
34155
|
+
const previousRows = react.default.useRef({
|
|
34156
|
+
scope,
|
|
34157
|
+
rows: treeRows
|
|
34158
|
+
});
|
|
34159
|
+
let anchoredTop = 0;
|
|
34160
|
+
if (viewport.scope === scope) {
|
|
34161
|
+
anchoredTop = viewport.top;
|
|
34162
|
+
if (previousRows.current.scope === scope && previousRows.current.rows !== treeRows) anchoredTop = treeAnchorTop(previousRows.current.rows, treeRows, viewport.top);
|
|
34163
|
+
}
|
|
34164
|
+
const currentViewport = {
|
|
34165
|
+
top: anchoredTop,
|
|
34166
|
+
height: viewport.height
|
|
34167
|
+
};
|
|
34168
|
+
const windowRows = treeWindow(treeRows.length, currentViewport);
|
|
34169
|
+
/** @private @author ddj 2026年10月22号 Read current geometry after scroll, resize or scope restoration. */
|
|
34170
|
+
function measureTree() {
|
|
34171
|
+
const host = treeRef.current;
|
|
34172
|
+
if (!host || host.clientHeight <= 0) return;
|
|
34173
|
+
setViewport({
|
|
34174
|
+
scope,
|
|
34175
|
+
top: host.scrollTop,
|
|
34176
|
+
height: host.clientHeight
|
|
34177
|
+
});
|
|
34178
|
+
}
|
|
34179
|
+
const scrollFrame = react.default.useRef(null);
|
|
34180
|
+
/** @private @author ddj 2026年10月22号 Coalesce high-frequency scrolling into one measured render per frame. */
|
|
34181
|
+
function scrollTree() {
|
|
34182
|
+
if (scrollFrame.current !== null) return;
|
|
34183
|
+
scrollFrame.current = requestAnimationFrame(readScroll);
|
|
34184
|
+
}
|
|
34185
|
+
/** @private @author ddj 2026年10月22号 Read only after the pending animation frame fires. */
|
|
34186
|
+
function readScroll() {
|
|
34187
|
+
scrollFrame.current = null;
|
|
34188
|
+
measureTree();
|
|
34189
|
+
}
|
|
34190
|
+
/** @private @author ddj 2026年10月22号 @returns Resize observer and pending frame cleanup. Keep hidden trees measurable when shown. */
|
|
34191
|
+
function watchTree() {
|
|
34192
|
+
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(measureTree) : null;
|
|
34193
|
+
if (treeRef.current) observer?.observe(treeRef.current);
|
|
34194
|
+
measureTree();
|
|
34195
|
+
/** @private @author ddj 2026年10月22号 Stop all viewport callbacks owned by this scope. */
|
|
34196
|
+
function cleanup() {
|
|
34197
|
+
observer?.disconnect();
|
|
34198
|
+
if (scrollFrame.current !== null) cancelAnimationFrame(scrollFrame.current);
|
|
34199
|
+
scrollFrame.current = null;
|
|
34200
|
+
}
|
|
34201
|
+
return cleanup;
|
|
34202
|
+
}
|
|
34203
|
+
react.default.useLayoutEffect(watchTree, [scope]);
|
|
34204
|
+
/** @private @author ddj 2026年10月22号 Clamp actual scroll after collapse, filtering, or workspace switch. */
|
|
34205
|
+
function clampTree() {
|
|
34206
|
+
const host = treeRef.current;
|
|
34207
|
+
previousRows.current = {
|
|
34208
|
+
scope,
|
|
34209
|
+
rows: treeRows
|
|
34210
|
+
};
|
|
34211
|
+
if (!host || host.clientHeight <= 0) return;
|
|
34212
|
+
if (host.scrollTop !== windowRows.top) host.scrollTop = windowRows.top;
|
|
34213
|
+
if (viewport.scope !== scope || viewport.top !== windowRows.top) setViewport({
|
|
34214
|
+
scope,
|
|
34215
|
+
top: windowRows.top,
|
|
34216
|
+
height: host.clientHeight
|
|
34217
|
+
});
|
|
34218
|
+
}
|
|
34219
|
+
react.default.useLayoutEffect(clampTree, [
|
|
34220
|
+
scope,
|
|
34221
|
+
windowRows.top,
|
|
34222
|
+
treeRows
|
|
34223
|
+
]);
|
|
34224
|
+
/** @private @author ddj 2026年10月22号 @param path Requested file. @returns Whether its complete model row is available. */
|
|
34225
|
+
function locateTree(path) {
|
|
34226
|
+
const host = treeRef.current;
|
|
34227
|
+
if (!host) return false;
|
|
34228
|
+
const top = treeRevealTop(treeRows, path, {
|
|
34229
|
+
top: host.scrollTop,
|
|
34230
|
+
height: host.clientHeight
|
|
34231
|
+
});
|
|
34232
|
+
if (top === null) return false;
|
|
34233
|
+
host.scrollTop = top;
|
|
34234
|
+
setViewport({
|
|
34235
|
+
scope,
|
|
34236
|
+
top,
|
|
34237
|
+
height: host.clientHeight
|
|
34238
|
+
});
|
|
34239
|
+
return true;
|
|
34240
|
+
}
|
|
34241
|
+
const { revealPath } = useExplorerReveal({
|
|
34242
|
+
scope,
|
|
34243
|
+
activePath,
|
|
34244
|
+
treeRef,
|
|
34245
|
+
prepare: prepareReveal,
|
|
34246
|
+
locate: locateTree
|
|
34247
|
+
});
|
|
33156
34248
|
/** @private @author ddj 2026年10月10号 @returns Stable RPC row projection, independent of icon-only renders. */
|
|
33157
34249
|
function projectRows() {
|
|
33158
34250
|
return treeRows.filter((row) => row.e).map((row) => ({
|
|
@@ -33173,6 +34265,7 @@ window.__ModuleLoader__.load({
|
|
|
33173
34265
|
event.preventDefault();
|
|
33174
34266
|
event.stopPropagation();
|
|
33175
34267
|
setMenu({
|
|
34268
|
+
scope,
|
|
33176
34269
|
x: event.clientX,
|
|
33177
34270
|
y: event.clientY,
|
|
33178
34271
|
target: {
|
|
@@ -33182,7 +34275,7 @@ window.__ModuleLoader__.load({
|
|
|
33182
34275
|
});
|
|
33183
34276
|
}
|
|
33184
34277
|
const onSelect = react.default.useCallback((path, isDir) => selectRef.current(path, isDir), []);
|
|
33185
|
-
const onContext = react.default.useCallback(contextRow, []);
|
|
34278
|
+
const onContext = react.default.useCallback(contextRow, [scope]);
|
|
33186
34279
|
const rootName = root ? String(root).split(/[\\/]/).pop() || root : "";
|
|
33187
34280
|
/** @private @author ddj 2026年10月10号 @param row Flat descriptor. @returns Memo row; unaffected status objects retain identity. */
|
|
33188
34281
|
function renderRow(row) {
|
|
@@ -33215,6 +34308,7 @@ window.__ModuleLoader__.load({
|
|
|
33215
34308
|
ev.stopPropagation();
|
|
33216
34309
|
if (!ev.currentTarget.contains(ev.target)) return;
|
|
33217
34310
|
setMenu({
|
|
34311
|
+
scope,
|
|
33218
34312
|
x: ev.clientX,
|
|
33219
34313
|
y: ev.clientY,
|
|
33220
34314
|
target: {
|
|
@@ -33223,7 +34317,13 @@ window.__ModuleLoader__.load({
|
|
|
33223
34317
|
}
|
|
33224
34318
|
});
|
|
33225
34319
|
};
|
|
33226
|
-
const
|
|
34320
|
+
const menuValid = menu?.scope === scope && (menu.target.path === "" || treeRows.some((row) => row.e?.path === menu.target.path));
|
|
34321
|
+
/** @private @author ddj 2026年10月22号 Close stale path menus instead of rebinding recycled rows. */
|
|
34322
|
+
function closeLostMenu() {
|
|
34323
|
+
if (menu && !menuValid) setMenu(null);
|
|
34324
|
+
}
|
|
34325
|
+
react.default.useEffect(closeLostMenu, [menu, menuValid]);
|
|
34326
|
+
const menuEntries = menuValid ? buildTreeMenu(ctx?.fileMenuItems, menu.target, ctx).map((item) => ({
|
|
33227
34327
|
id: item.id,
|
|
33228
34328
|
label: item.label,
|
|
33229
34329
|
danger: item.danger,
|
|
@@ -33263,16 +34363,23 @@ window.__ModuleLoader__.load({
|
|
|
33263
34363
|
}, refreshIconEl$1())), svn.message ? react.default.createElement("div", {
|
|
33264
34364
|
className: "edrv-tree-status",
|
|
33265
34365
|
role: "status"
|
|
33266
|
-
}, svn.message) : null, react.default.createElement("div", {
|
|
33267
|
-
className: "edrv-tree",
|
|
33268
|
-
ref: treeRef
|
|
33269
|
-
}, errorText ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(errorText)), react.default.createElement("button", {
|
|
34366
|
+
}, svn.message) : null, errorText ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(errorText)), react.default.createElement("button", {
|
|
33270
34367
|
className: "edrv-side-btn",
|
|
33271
34368
|
onClick: () => emitRefresh({
|
|
33272
34369
|
scope,
|
|
33273
34370
|
kind: "unknown"
|
|
33274
34371
|
})
|
|
33275
|
-
}, "重试")) : null,
|
|
34372
|
+
}, "重试")) : null, react.default.createElement("div", {
|
|
34373
|
+
className: "edrv-tree edrv-tree-virtual",
|
|
34374
|
+
ref: treeRef,
|
|
34375
|
+
onScroll: scrollTree
|
|
34376
|
+
}, react.default.createElement("div", {
|
|
34377
|
+
"aria-hidden": true,
|
|
34378
|
+
style: { height: windowRows.start * 24 }
|
|
34379
|
+
}), treeRows.slice(windowRows.start, windowRows.end).map(renderRow), react.default.createElement("div", {
|
|
34380
|
+
"aria-hidden": true,
|
|
34381
|
+
style: { height: (treeRows.length - windowRows.end) * 24 }
|
|
34382
|
+
})), menu && menuEntries.length ? react.default.createElement(ContextMenu, {
|
|
33276
34383
|
x: menu.x,
|
|
33277
34384
|
y: menu.y,
|
|
33278
34385
|
entries: menuEntries,
|
|
@@ -33767,713 +34874,79 @@ window.__ModuleLoader__.load({
|
|
|
33767
34874
|
})), optsBar, filterRows, summary, warnBanner, body);
|
|
33768
34875
|
}
|
|
33769
34876
|
//#endregion
|
|
33770
|
-
//#region src/client/
|
|
33771
|
-
/**
|
|
33772
|
-
|
|
33773
|
-
|
|
33774
|
-
|
|
33775
|
-
* @returns 去引号后的值
|
|
33776
|
-
*/
|
|
33777
|
-
function stripQuotes(raw) {
|
|
33778
|
-
const value = raw.trim();
|
|
33779
|
-
if (value.length >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) return value.slice(1, -1);
|
|
33780
|
-
return value;
|
|
33781
|
-
}
|
|
33782
|
-
/**
|
|
33783
|
-
* 解析 .mdc 全文:首行 --- 且 100 行内有闭合 --- 才视为 frontmatter;
|
|
33784
|
-
* 已知键 description/alwaysApply/globs/enabled,其余原行进 fmExtra;永不抛错。
|
|
33785
|
-
* @author ddj 2026年09月07号
|
|
33786
|
-
* @param text 文件全文
|
|
33787
|
-
* @returns 解析结果
|
|
33788
|
-
*/
|
|
33789
|
-
function parseRuleFm(text) {
|
|
33790
|
-
const bom = text.startsWith("");
|
|
33791
|
-
const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
33792
|
-
const none = {
|
|
33793
|
-
type: "manual",
|
|
33794
|
-
description: "",
|
|
33795
|
-
alwaysApply: false,
|
|
33796
|
-
globs: [],
|
|
33797
|
-
enabled: true,
|
|
33798
|
-
hasFm: false,
|
|
33799
|
-
body: text.replace(/^\uFEFF/, ""),
|
|
33800
|
-
fmExtra: [],
|
|
33801
|
-
bom,
|
|
33802
|
-
hasEnabledLine: false
|
|
33803
|
-
};
|
|
33804
|
-
if (lines[0]?.trim() !== "---") return none;
|
|
33805
|
-
let close = -1;
|
|
33806
|
-
for (let i = 1; i < lines.length && i <= 101; i++) if (lines[i].trim() === "---") {
|
|
33807
|
-
close = i;
|
|
33808
|
-
break;
|
|
33809
|
-
}
|
|
33810
|
-
if (close < 0) return none;
|
|
33811
|
-
const parsed = {
|
|
33812
|
-
type: "manual",
|
|
33813
|
-
description: "",
|
|
33814
|
-
alwaysApply: false,
|
|
33815
|
-
globs: [],
|
|
33816
|
-
enabled: true,
|
|
33817
|
-
hasFm: true,
|
|
33818
|
-
body: lines.slice(close + 1).join("\n"),
|
|
33819
|
-
fmExtra: [],
|
|
33820
|
-
bom,
|
|
33821
|
-
hasEnabledLine: false
|
|
33822
|
-
};
|
|
33823
|
-
for (let i = 1; i < close; i++) {
|
|
33824
|
-
const match = /^([A-Za-z_-]+)[ \t]*:[ \t]?(.*)$/.exec(lines[i]);
|
|
33825
|
-
if (!match) {
|
|
33826
|
-
parsed.fmExtra.push(lines[i]);
|
|
33827
|
-
continue;
|
|
33828
|
-
}
|
|
33829
|
-
const [, key, raw] = match;
|
|
33830
|
-
if (key === "description") parsed.description = stripQuotes(raw);
|
|
33831
|
-
else if (key === "alwaysApply") parsed.alwaysApply = stripQuotes(raw).toLowerCase() === "true";
|
|
33832
|
-
else if (key === "enabled") {
|
|
33833
|
-
parsed.enabled = stripQuotes(raw).toLowerCase() !== "false";
|
|
33834
|
-
parsed.hasEnabledLine = true;
|
|
33835
|
-
} else if (key === "globs") parsed.globs = parseGlobsValue(raw, lines, i, close);
|
|
33836
|
-
else parsed.fmExtra.push(lines[i]);
|
|
33837
|
-
}
|
|
33838
|
-
parsed.type = fmTypeOf(parsed);
|
|
33839
|
-
return parsed;
|
|
33840
|
-
}
|
|
33841
|
-
/**
|
|
33842
|
-
* 解析 globs 值:内联逗号分隔串,或空值后接 YAML 短横列表(与 host parseGlobs 一致)。
|
|
33843
|
-
* @author ddj 2026年09月07号
|
|
33844
|
-
* @param value 内联值
|
|
33845
|
-
* @param lines 全文行
|
|
33846
|
-
* @param start globs 行下标
|
|
33847
|
-
* @param close 闭合 --- 下标
|
|
33848
|
-
* @returns glob 数组
|
|
33849
|
-
*/
|
|
33850
|
-
function parseGlobsValue(value, lines, start, close) {
|
|
33851
|
-
const inline = stripQuotes(value);
|
|
33852
|
-
if (inline) return inline.split(",").map((item) => stripQuotes(item)).filter(Boolean);
|
|
33853
|
-
const list = [];
|
|
33854
|
-
for (let j = start + 1; j < close; j++) {
|
|
33855
|
-
const item = /^[ \t]*-[ \t]*(.+)$/.exec(lines[j]);
|
|
33856
|
-
if (!item) break;
|
|
33857
|
-
list.push(stripQuotes(item[1]));
|
|
33858
|
-
}
|
|
33859
|
-
return list;
|
|
33860
|
-
}
|
|
33861
|
-
/**
|
|
33862
|
-
* 由 frontmatter 推导类型:alwaysApply=true → 总是;有 globs → 自动;否则手动。
|
|
33863
|
-
* @author ddj 2026年09月07号
|
|
33864
|
-
* @param parsed 解析结果
|
|
33865
|
-
* @returns 规则类型
|
|
33866
|
-
*/
|
|
33867
|
-
function fmTypeOf(parsed) {
|
|
33868
|
-
if (parsed.alwaysApply) return "always";
|
|
33869
|
-
if (parsed.globs.length) return "auto";
|
|
33870
|
-
return "manual";
|
|
33871
|
-
}
|
|
33872
|
-
/**
|
|
33873
|
-
* 按最新控件值重写 frontmatter:known 键固定顺序 + fmExtra + 闭合 --- + 原 body;
|
|
33874
|
-
* 无 frontmatter 且目标为手动时:有描述则新建仅含描述的 frontmatter,否则原文返回。
|
|
33875
|
-
* @author ddj 2026年09月07号
|
|
33876
|
-
* @param parsed parseRuleFm 的结果(必须来自同一文本)
|
|
33877
|
-
* @param next 控件最新值
|
|
33878
|
-
* @returns 重写后的全文
|
|
33879
|
-
*/
|
|
33880
|
-
function applyRuleMeta(parsed, next) {
|
|
33881
|
-
if (!parsed.hasFm && next.type === "manual") {
|
|
33882
|
-
if (!next.description.trim()) return (parsed.bom ? "" : "") + parsed.body;
|
|
33883
|
-
return (parsed.bom ? "" : "") + [
|
|
33884
|
-
"---",
|
|
33885
|
-
"description: " + next.description.trim(),
|
|
33886
|
-
"---"
|
|
33887
|
-
].join("\n") + "\n" + parsed.body;
|
|
33888
|
-
}
|
|
33889
|
-
const lines = ["---"];
|
|
33890
|
-
lines.push("description: " + next.description.trim());
|
|
33891
|
-
if (next.type === "always") lines.push("alwaysApply: true");
|
|
33892
|
-
if (next.type === "auto") lines.push("globs: " + next.globs.join(", "));
|
|
33893
|
-
if (parsed.hasEnabledLine) lines.push("enabled: " + (parsed.enabled ? "true" : "false"));
|
|
33894
|
-
lines.push(...parsed.fmExtra);
|
|
33895
|
-
lines.push("---");
|
|
33896
|
-
return (parsed.bom ? "" : "") + lines.join("\n") + "\n" + parsed.body;
|
|
33897
|
-
}
|
|
33898
|
-
//#endregion
|
|
33899
|
-
//#region src/client/sidebar/panels/RulesPanel.ts
|
|
33900
|
-
/**
|
|
33901
|
-
* dsh-vscode-mode client — 侧边栏「规则」面板(Codebuddy 规则管理形态)。
|
|
33902
|
-
* 双 Tab:用户规则(~/.dsh/rules/)与项目规则(<工作区>/.dsh/rules/);
|
|
33903
|
-
* 列表行 = 文件名 + 相对提示,第二行类型徽标与描述,右侧 编辑/删除 图标 + 滑动启用开关;
|
|
33904
|
-
* 新建/编辑 = 顶部面包屑 + 规则类型下拉(总是/自动/手动)+ 描述/globs 控件,与原始 .mdc
|
|
33905
|
-
* 文本域双向同步(控件改写 frontmatter,文本域回读控件),保存走 rules.save。
|
|
33906
|
-
* 作者 ddj 2026年09月03号(2026年09月07号 交互性调整)
|
|
33907
|
-
*/
|
|
33908
|
-
/** 新建规则模板(frontmatter 字段与 host parseRuleMdc 对齐)。 */
|
|
33909
|
-
const NEW_TEMPLATE = "---\ndescription: \nalwaysApply: true\n---\n\n";
|
|
33910
|
-
const TYPE_LABEL = {
|
|
33911
|
-
always: "总是",
|
|
33912
|
-
auto: "自动",
|
|
33913
|
-
manual: "手动"
|
|
34877
|
+
//#region src/client/lazyPanel.ts
|
|
34878
|
+
/** Package-local CJS panel loading; transport, revision URLs and HMR belong to DSH. @author ddj 2026年10月22号 */
|
|
34879
|
+
const FILES = {
|
|
34880
|
+
rules: "./client.rules.js",
|
|
34881
|
+
debug: "./client.debug.js"
|
|
33914
34882
|
};
|
|
33915
|
-
|
|
33916
|
-
|
|
33917
|
-
|
|
33918
|
-
|
|
33919
|
-
|
|
33920
|
-
|
|
33921
|
-
|
|
33922
|
-
|
|
33923
|
-
|
|
33924
|
-
|
|
33925
|
-
|
|
33926
|
-
|
|
33927
|
-
|
|
33928
|
-
|
|
33929
|
-
|
|
33930
|
-
|
|
33931
|
-
|
|
33932
|
-
|
|
33933
|
-
|
|
33934
|
-
|
|
33935
|
-
|
|
33936
|
-
}
|
|
33937
|
-
|
|
33938
|
-
|
|
33939
|
-
|
|
33940
|
-
|
|
33941
|
-
|
|
33942
|
-
|
|
33943
|
-
|
|
33944
|
-
|
|
33945
|
-
|
|
33946
|
-
localStorage.setItem(key, value);
|
|
33947
|
-
} catch (e) {}
|
|
33948
|
-
}
|
|
33949
|
-
/**
|
|
33950
|
-
* 类型徽标(总是=绿 / 自动=蓝 / 手动=灰)。
|
|
33951
|
-
* @author ddj 2026年09月03号
|
|
33952
|
-
* @param type 规则类型
|
|
33953
|
-
* @returns 徽标元素
|
|
33954
|
-
*/
|
|
33955
|
-
function typeBadge(type) {
|
|
33956
|
-
return react.default.createElement("span", { className: "edrv-rules-type edrv-rules-type-" + type }, TYPE_LABEL[type] ?? type);
|
|
33957
|
-
}
|
|
33958
|
-
/**
|
|
33959
|
-
* 按字符上限截断列表行描述,超出以省略号收尾(上限随面板宽度动态变化;
|
|
33960
|
-
* 窄面板下 CSS 省略号仍会按像素提前截断,二者取更小)。
|
|
33961
|
-
* @author ddj 2026年09月07号
|
|
33962
|
-
* @param text 完整描述
|
|
33963
|
-
* @param cap 本帧字符上限
|
|
33964
|
-
* @returns 截断后的展示文本
|
|
33965
|
-
*/
|
|
33966
|
-
function clampDesc(text, cap) {
|
|
33967
|
-
const chars = Array.from(String(text ?? ""));
|
|
33968
|
-
const limit = Math.max(4, Number(cap) || DESC_MAX);
|
|
33969
|
-
return chars.length > limit ? chars.slice(0, limit).join("") + "…" : chars.join("");
|
|
33970
|
-
}
|
|
33971
|
-
/**
|
|
33972
|
-
* 由面板宽度估算描述可用字符数:随宽度动态变化,并与基础上限取小、极窄时保底。
|
|
33973
|
-
* @author ddj 2026年09月07号
|
|
33974
|
-
* @param panelWidth 面板内容宽度(px)
|
|
33975
|
-
* @returns 本帧描述字符上限
|
|
33976
|
-
*/
|
|
33977
|
-
function descCapOf(panelWidth) {
|
|
33978
|
-
const byWidth = Math.floor((Number(panelWidth) - DESC_RESERVE_PX) / DESC_CHAR_PX);
|
|
33979
|
-
return Math.max(DESC_CAP_MIN, Math.min(DESC_MAX, byWidth));
|
|
33980
|
-
}
|
|
33981
|
-
/**
|
|
33982
|
-
* 编辑图标(线性铅笔,与 DiffBox chevron 同风格:currentColor 描边装饰图)。
|
|
33983
|
-
* @author ddj 2026年09月07号
|
|
33984
|
-
* @returns 13×13 装饰性 SVG
|
|
33985
|
-
*/
|
|
33986
|
-
function pencilIcon() {
|
|
33987
|
-
return react.default.createElement("svg", {
|
|
33988
|
-
width: 13,
|
|
33989
|
-
height: 13,
|
|
33990
|
-
viewBox: "0 0 24 24",
|
|
33991
|
-
fill: "none",
|
|
33992
|
-
"aria-hidden": true,
|
|
33993
|
-
focusable: false
|
|
33994
|
-
}, react.default.createElement("path", {
|
|
33995
|
-
d: "M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z",
|
|
33996
|
-
stroke: "currentColor",
|
|
33997
|
-
strokeWidth: 2,
|
|
33998
|
-
strokeLinecap: "round",
|
|
33999
|
-
strokeLinejoin: "round"
|
|
34000
|
-
}));
|
|
34001
|
-
}
|
|
34002
|
-
/**
|
|
34003
|
-
* 删除图标(线性垃圾桶,风格同 pencilIcon)。
|
|
34004
|
-
* @author ddj 2026年09月07号
|
|
34005
|
-
* @returns 13×13 装饰性 SVG
|
|
34006
|
-
*/
|
|
34007
|
-
function trashIcon() {
|
|
34008
|
-
return react.default.createElement("svg", {
|
|
34009
|
-
width: 13,
|
|
34010
|
-
height: 13,
|
|
34011
|
-
viewBox: "0 0 24 24",
|
|
34012
|
-
fill: "none",
|
|
34013
|
-
"aria-hidden": true,
|
|
34014
|
-
focusable: false
|
|
34015
|
-
}, react.default.createElement("path", {
|
|
34016
|
-
d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14zM10 11v6M14 11v6",
|
|
34017
|
-
stroke: "currentColor",
|
|
34018
|
-
strokeWidth: 2,
|
|
34019
|
-
strokeLinecap: "round",
|
|
34020
|
-
strokeLinejoin: "round"
|
|
34021
|
-
}));
|
|
34022
|
-
}
|
|
34023
|
-
/**
|
|
34024
|
-
* 逗号分隔的 globs 输入串 → 数组(去空项)。
|
|
34025
|
-
* @author ddj 2026年09月07号
|
|
34026
|
-
* @param text 输入串
|
|
34027
|
-
* @returns glob 数组
|
|
34028
|
-
*/
|
|
34029
|
-
function globList(text) {
|
|
34030
|
-
return String(text ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
34031
|
-
}
|
|
34032
|
-
/**
|
|
34033
|
-
* 由控件值重建 .mdc 全文:frontmatter 按类型/描述/globs 重写,正文与其余键不动。
|
|
34034
|
-
* @author ddj 2026年09月07号
|
|
34035
|
-
* @param base 当前全文
|
|
34036
|
-
* @param fmType 目标类型(always/auto/manual)
|
|
34037
|
-
* @param desc 描述文本
|
|
34038
|
-
* @param globsText globs 输入串
|
|
34039
|
-
* @returns 重写后的全文
|
|
34040
|
-
*/
|
|
34041
|
-
function rebuildContent(base, fmType, desc, globsText) {
|
|
34042
|
-
return applyRuleMeta(parseRuleFm(base), {
|
|
34043
|
-
type: fmType,
|
|
34044
|
-
description: desc,
|
|
34045
|
-
globs: globList(globsText)
|
|
34883
|
+
const EXPORTS = {
|
|
34884
|
+
rules: "RulesPanel",
|
|
34885
|
+
debug: "DebugPanel"
|
|
34886
|
+
};
|
|
34887
|
+
const loads = /* @__PURE__ */ new Map();
|
|
34888
|
+
let generation = 0;
|
|
34889
|
+
let importer;
|
|
34890
|
+
/** @public @author ddj 2026年10月22号 @param transport Injected ModuleLoader require.async, never a global require. @description Start a new plugin generation and discard older results. */
|
|
34891
|
+
function setPanelImport(transport) {
|
|
34892
|
+
generation++;
|
|
34893
|
+
loads.clear();
|
|
34894
|
+
importer = transport;
|
|
34895
|
+
}
|
|
34896
|
+
/** @public @author ddj 2026年10月22号 @param key Fixed built-in panel key. @returns Shared pending load or cached component. @throws Unsupported loader or invalid chunk export. */
|
|
34897
|
+
function loadPanel(key) {
|
|
34898
|
+
const cached = loads.get(key);
|
|
34899
|
+
if (cached?.component) return Promise.resolve(cached.component);
|
|
34900
|
+
if (cached?.pending) return cached.pending;
|
|
34901
|
+
if (!importer) return Promise.reject(/* @__PURE__ */ new Error("当前 DSH 不支持包内面板加载,请升级宿主"));
|
|
34902
|
+
const epoch = generation;
|
|
34903
|
+
const transport = importer;
|
|
34904
|
+
const state = {};
|
|
34905
|
+
const pending = Promise.resolve().then(() => transport(FILES[key])).then((exports) => {
|
|
34906
|
+
const component = exports[EXPORTS[key]];
|
|
34907
|
+
if (typeof component !== "function") throw new Error("面板脚本缺少组件导出");
|
|
34908
|
+
if (epoch !== generation) throw new Error("面板加载已失效,请重试");
|
|
34909
|
+
state.component = component;
|
|
34910
|
+
return state.component;
|
|
34911
|
+
}).catch((error) => {
|
|
34912
|
+
if (loads.get(key) === state) loads.delete(key);
|
|
34913
|
+
throw error;
|
|
34046
34914
|
});
|
|
34915
|
+
state.pending = pending;
|
|
34916
|
+
loads.set(key, state);
|
|
34917
|
+
return pending;
|
|
34047
34918
|
}
|
|
34048
|
-
/**
|
|
34049
|
-
|
|
34050
|
-
|
|
34051
|
-
|
|
34052
|
-
|
|
34053
|
-
|
|
34054
|
-
|
|
34055
|
-
|
|
34056
|
-
|
|
34057
|
-
|
|
34058
|
-
|
|
34059
|
-
|
|
34060
|
-
|
|
34061
|
-
|
|
34062
|
-
|
|
34063
|
-
|
|
34064
|
-
|
|
34065
|
-
className: "edrv-rules-path",
|
|
34066
|
-
title: rule.absPath
|
|
34067
|
-
}, "旧目录") : null, !rule.enabled ? react.default.createElement("span", { className: "edrv-rules-offtag" }, "已停用") : null), react.default.createElement("div", { className: "edrv-rules-sub" }, react.default.createElement("span", { className: "edrv-rules-sublabel" }, "类型:"), typeBadge(rule.type), desc ? react.default.createElement("span", {
|
|
34068
|
-
className: "edrv-rules-desc",
|
|
34069
|
-
title: desc
|
|
34070
|
-
}, clampDesc(desc, props.descCap)) : null)), react.default.createElement("div", { className: "edrv-rules-actions" }, react.default.createElement("button", {
|
|
34071
|
-
className: "edrv-rules-act",
|
|
34072
|
-
title: "编辑",
|
|
34073
|
-
onClick: () => props.onEdit(rule)
|
|
34074
|
-
}, pencilIcon()), react.default.createElement("button", {
|
|
34075
|
-
className: "edrv-rules-act",
|
|
34076
|
-
title: "删除",
|
|
34077
|
-
onClick: () => props.onRemove(rule)
|
|
34078
|
-
}, trashIcon()), react.default.createElement("button", {
|
|
34079
|
-
className: "edrv-rules-switch" + (rule.enabled ? " on" : ""),
|
|
34080
|
-
role: "switch",
|
|
34081
|
-
"aria-checked": rule.enabled,
|
|
34082
|
-
title: rule.enabled ? "已启用(点击停用)" : "已停用(点击启用)",
|
|
34083
|
-
onClick: () => props.onToggle(rule)
|
|
34084
|
-
}, react.default.createElement("span", { className: "edrv-rules-knob" }))));
|
|
34085
|
-
}
|
|
34086
|
-
/**
|
|
34087
|
-
* 新建/编辑表单:面包屑头部 + 规则类型下拉/描述/globs 控件行 + 原始 .mdc 文本域 + 保存/取消。
|
|
34088
|
-
* @author ddj 2026年09月03号
|
|
34089
|
-
* @param props.form 表单状态(mode/scope/workspacePath/relHint/file/content/fmType/desc/globs/error/saving)
|
|
34090
|
-
* @param props.onType 类型下拉变更回调
|
|
34091
|
-
* @param props.onDesc 描述输入回调
|
|
34092
|
-
* @param props.onGlobs globs 输入回调
|
|
34093
|
-
* @param props.onBody 文本域编辑回调
|
|
34094
|
-
* @param props.onSave 保存回调
|
|
34095
|
-
* @param props.onCancel 取消回调
|
|
34096
|
-
* @returns 表单元素
|
|
34097
|
-
*/
|
|
34098
|
-
function RuleEditor(props) {
|
|
34099
|
-
const form = props.form;
|
|
34100
|
-
const isNew = form.mode === "new";
|
|
34101
|
-
const crumb = (form.scope === "project" ? "项目规则" : "用户规则") + " > " + form.relHint + " > " + (form.file || "新建规则.mdc");
|
|
34102
|
-
return react.default.createElement("div", { className: "edrv-rules-editor" }, react.default.createElement("div", { className: "edrv-rules-editor-head" }, react.default.createElement("span", {
|
|
34103
|
-
className: "edrv-rules-crumb",
|
|
34104
|
-
title: crumb
|
|
34105
|
-
}, crumb), react.default.createElement("button", {
|
|
34106
|
-
className: "edrv-rules-act",
|
|
34107
|
-
title: "取消",
|
|
34108
|
-
onClick: props.onCancel
|
|
34109
|
-
}, "✕")), react.default.createElement("div", { className: "edrv-rules-editor-ctl" }, react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "规则类型"), react.default.createElement("select", {
|
|
34110
|
-
className: "edrv-rules-select",
|
|
34111
|
-
value: form.fmType,
|
|
34112
|
-
onChange: props.onType
|
|
34113
|
-
}, Object.keys(TYPE_LABEL).map((key) => react.default.createElement("option", {
|
|
34114
|
-
key,
|
|
34115
|
-
value: key
|
|
34116
|
-
}, TYPE_LABEL[key]))), react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "描述"), react.default.createElement("input", {
|
|
34117
|
-
className: "edrv-rules-input edrv-rules-desc-input",
|
|
34118
|
-
value: form.desc,
|
|
34119
|
-
spellCheck: false,
|
|
34120
|
-
placeholder: isNew ? "规则用途说明(写入 frontmatter description)" : "规则用途说明",
|
|
34121
|
-
onChange: props.onDesc
|
|
34122
|
-
})), form.fmType === "auto" ? react.default.createElement("div", { className: "edrv-rules-editor-ctl" }, react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "globs"), react.default.createElement("input", {
|
|
34123
|
-
className: "edrv-rules-input",
|
|
34124
|
-
value: form.globs,
|
|
34125
|
-
spellCheck: false,
|
|
34126
|
-
placeholder: "src/**/*.ts, *.md(逗号分隔,命中才注入)",
|
|
34127
|
-
onChange: props.onGlobs
|
|
34128
|
-
})) : null, react.default.createElement("textarea", {
|
|
34129
|
-
className: "edrv-rules-body",
|
|
34130
|
-
spellCheck: false,
|
|
34131
|
-
value: form.content,
|
|
34132
|
-
onChange: props.onBody
|
|
34133
|
-
}), react.default.createElement("div", { className: "edrv-rules-editor-hint" }, "类型/描述/globs 与 frontmatter 双向同步;enabled 与其余键原样保留;无 frontmatter 的纯 markdown 视为「手动」规则"), form.error ? react.default.createElement("div", { className: "edrv-rules-err" }, form.error) : null, react.default.createElement("div", { className: "edrv-rules-editor-foot" }, react.default.createElement("button", {
|
|
34134
|
-
className: "edrv-rules-save",
|
|
34135
|
-
disabled: form.saving,
|
|
34136
|
-
onClick: props.onSave
|
|
34137
|
-
}, form.saving ? "保存中…" : "保存")));
|
|
34138
|
-
}
|
|
34139
|
-
/**
|
|
34140
|
-
* 规则面板主体。
|
|
34141
|
-
* @param props.ctx 面板共享上下文(notify 可选)
|
|
34142
|
-
*/
|
|
34143
|
-
function RulesPanel(props) {
|
|
34144
|
-
const ctx = props?.ctx;
|
|
34145
|
-
const notify = ctx?.notify ?? ((message) => {});
|
|
34146
|
-
const cwd = ctx?.cwd ?? null;
|
|
34147
|
-
const [tab, setTab] = react.default.useState(() => readLocal(CACHE_KEY.rules + "tab") === "project" ? "project" : "user");
|
|
34148
|
-
const [data, setData] = react.default.useState(null);
|
|
34149
|
-
const [error, setError] = react.default.useState("");
|
|
34150
|
-
const panelRef = react.default.useRef(null);
|
|
34151
|
-
const [panelW, setPanelW] = react.default.useState(0);
|
|
34152
|
-
const [form, setForm] = react.default.useState(null);
|
|
34153
|
-
const mountedRef = react.default.useRef(true);
|
|
34154
|
-
/**
|
|
34155
|
-
* 当前工作区对应的项目聚合(按 ctx.cwd 自动匹配;无会话/未注册返回 null)。
|
|
34156
|
-
* @returns RuleProject 或 null
|
|
34157
|
-
*/
|
|
34158
|
-
const activeProject = () => {
|
|
34159
|
-
if (!cwd) return null;
|
|
34160
|
-
return (data?.projects ?? []).find((p) => p.workspacePath === cwd) ?? null;
|
|
34161
|
-
};
|
|
34162
|
-
/** 拉取规则总列表(rules.list)。 */
|
|
34163
|
-
const load = () => {
|
|
34164
|
-
rpc("rules.list", {}).then((res) => {
|
|
34165
|
-
if (!mountedRef.current) return;
|
|
34166
|
-
if (res && res.ok) {
|
|
34167
|
-
setData(res);
|
|
34168
|
-
setError("");
|
|
34169
|
-
} else setError(res?.error ?? "读取规则失败");
|
|
34170
|
-
}).catch((e) => {
|
|
34171
|
-
if (mountedRef.current) setError("读取规则异常: " + String(e));
|
|
34172
|
-
});
|
|
34173
|
-
};
|
|
34174
|
-
react.default.useEffect(() => {
|
|
34175
|
-
mountedRef.current = true;
|
|
34176
|
-
load();
|
|
34919
|
+
/** @public @author ddj 2026年10月22号 @param props Fixed panel and current context. @returns Loading, explicit retry, or the loaded panel subtree. */
|
|
34920
|
+
function LazyPanel(props) {
|
|
34921
|
+
const [attempt, retry] = react.default.useState(0);
|
|
34922
|
+
const [view, setView] = react.default.useState({});
|
|
34923
|
+
/** @private @author ddj 2026年10月22号 @returns Cleanup invalidating callbacks after switch or unmount. */
|
|
34924
|
+
function loadEffect() {
|
|
34925
|
+
let active = true;
|
|
34926
|
+
setView({});
|
|
34927
|
+
/** @author ddj 2026年10月22号 @param component Loaded panel. */
|
|
34928
|
+
function ready(component) {
|
|
34929
|
+
if (active) setView({ component });
|
|
34930
|
+
}
|
|
34931
|
+
/** @author ddj 2026年10月22号 @param error Loader failure. */
|
|
34932
|
+
function failed(error) {
|
|
34933
|
+
if (active) setView({ error: String(error) });
|
|
34934
|
+
}
|
|
34935
|
+
loadPanel(props.panel).then(ready, failed);
|
|
34177
34936
|
return () => {
|
|
34178
|
-
|
|
34937
|
+
active = false;
|
|
34179
34938
|
};
|
|
34180
|
-
}
|
|
34181
|
-
react.default.useEffect(
|
|
34182
|
-
|
|
34183
|
-
|
|
34184
|
-
|
|
34185
|
-
|
|
34186
|
-
|
|
34187
|
-
return () => observer.disconnect();
|
|
34188
|
-
}, []);
|
|
34189
|
-
const descCap = panelW > 0 ? descCapOf(panelW) : DESC_MAX;
|
|
34190
|
-
const narrow = panelW > 0 && panelW < NARROW_W;
|
|
34191
|
-
/**
|
|
34192
|
-
* 切换 Tab(记忆到 localStorage)。
|
|
34193
|
-
* @param next 目标 Tab
|
|
34194
|
-
*/
|
|
34195
|
-
const switchTab = (next) => {
|
|
34196
|
-
setTab(next);
|
|
34197
|
-
writeLocal(CACHE_KEY.rules + "tab", next);
|
|
34198
|
-
};
|
|
34199
|
-
/**
|
|
34200
|
-
* 打开新建表单(按当前 Tab 决定作用域;项目规则跟随当前会话工作区)。
|
|
34201
|
-
*/
|
|
34202
|
-
const openNew = () => {
|
|
34203
|
-
const scope = tab === "project" ? "project" : "user";
|
|
34204
|
-
if (scope === "project" && !cwd) {
|
|
34205
|
-
notify("当前会话没有工作区,无法新建项目规则");
|
|
34206
|
-
return;
|
|
34207
|
-
}
|
|
34208
|
-
const parsed = parseRuleFm(NEW_TEMPLATE);
|
|
34209
|
-
setForm({
|
|
34210
|
-
mode: "new",
|
|
34211
|
-
scope,
|
|
34212
|
-
origin: scope === "user" ? "current" : void 0,
|
|
34213
|
-
workspacePath: scope === "project" ? cwd : void 0,
|
|
34214
|
-
relHint: scope === "project" ? ".dsh/rules/" : "rules/",
|
|
34215
|
-
file: "",
|
|
34216
|
-
content: NEW_TEMPLATE,
|
|
34217
|
-
fmType: parsed.type,
|
|
34218
|
-
desc: parsed.description,
|
|
34219
|
-
globs: parsed.globs.join(", "),
|
|
34220
|
-
error: "",
|
|
34221
|
-
saving: false
|
|
34222
|
-
});
|
|
34223
|
-
};
|
|
34224
|
-
/**
|
|
34225
|
-
* 打开编辑表单:先拉全文,再解析 frontmatter 回填类型/描述/globs 控件。
|
|
34226
|
-
* @param rule 目标规则
|
|
34227
|
-
*/
|
|
34228
|
-
const openEdit = (rule) => {
|
|
34229
|
-
rpc("rules.read", {
|
|
34230
|
-
scope: rule.scope,
|
|
34231
|
-
origin: rule.origin,
|
|
34232
|
-
workspacePath: rule.scope === "project" ? rule.workspacePath : void 0,
|
|
34233
|
-
file: rule.file
|
|
34234
|
-
}).then((res) => {
|
|
34235
|
-
if (!res || !res.ok) {
|
|
34236
|
-
notify(res?.error ?? "读取规则失败");
|
|
34237
|
-
return;
|
|
34238
|
-
}
|
|
34239
|
-
const parsed = parseRuleFm(res.content);
|
|
34240
|
-
setForm({
|
|
34241
|
-
mode: "edit",
|
|
34242
|
-
scope: rule.scope,
|
|
34243
|
-
origin: rule.origin,
|
|
34244
|
-
workspacePath: rule.workspacePath,
|
|
34245
|
-
relHint: rule.relHint,
|
|
34246
|
-
file: rule.file,
|
|
34247
|
-
content: res.content,
|
|
34248
|
-
fmType: parsed.type,
|
|
34249
|
-
desc: parsed.description,
|
|
34250
|
-
globs: parsed.globs.join(", "),
|
|
34251
|
-
error: "",
|
|
34252
|
-
saving: false
|
|
34253
|
-
});
|
|
34254
|
-
}).catch((e) => notify("读取规则异常: " + String(e)));
|
|
34255
|
-
};
|
|
34256
|
-
/** 类型下拉变更:frontmatter 按新类型重写,控件同步。 */
|
|
34257
|
-
const onTypeChange = (e) => {
|
|
34258
|
-
const nextType = e.target.value;
|
|
34259
|
-
setForm((prev) => ({
|
|
34260
|
-
...prev,
|
|
34261
|
-
fmType: nextType,
|
|
34262
|
-
content: rebuildContent(prev.content, nextType, prev.desc, prev.globs),
|
|
34263
|
-
error: ""
|
|
34264
|
-
}));
|
|
34265
|
-
};
|
|
34266
|
-
/** 描述输入变更:同步 frontmatter description 行。 */
|
|
34267
|
-
const onDescChange = (e) => {
|
|
34268
|
-
const nextDesc = e.target.value;
|
|
34269
|
-
setForm((prev) => ({
|
|
34270
|
-
...prev,
|
|
34271
|
-
desc: nextDesc,
|
|
34272
|
-
content: rebuildContent(prev.content, prev.fmType, nextDesc, prev.globs),
|
|
34273
|
-
error: ""
|
|
34274
|
-
}));
|
|
34275
|
-
};
|
|
34276
|
-
/** globs 输入变更(自动规则):同步 frontmatter globs 行。 */
|
|
34277
|
-
const onGlobsChange = (e) => {
|
|
34278
|
-
const nextGlobs = e.target.value;
|
|
34279
|
-
setForm((prev) => ({
|
|
34280
|
-
...prev,
|
|
34281
|
-
globs: nextGlobs,
|
|
34282
|
-
content: rebuildContent(prev.content, prev.fmType, prev.desc, nextGlobs),
|
|
34283
|
-
error: ""
|
|
34284
|
-
}));
|
|
34285
|
-
};
|
|
34286
|
-
/** 文本域直接编辑:回读 frontmatter,同步类型/描述/globs 控件(文本域是唯一真源)。 */
|
|
34287
|
-
const onBodyChange = (e) => {
|
|
34288
|
-
const parsed = parseRuleFm(e.target.value);
|
|
34289
|
-
setForm((prev) => ({
|
|
34290
|
-
...prev,
|
|
34291
|
-
content: e.target.value,
|
|
34292
|
-
fmType: parsed.type,
|
|
34293
|
-
desc: parsed.description,
|
|
34294
|
-
globs: parsed.globs.join(", ")
|
|
34295
|
-
}));
|
|
34296
|
-
};
|
|
34297
|
-
/** 保存表单(rules.save;文件名非空 + 自动规则必须有 globs)。 */
|
|
34298
|
-
const saveForm = () => {
|
|
34299
|
-
const next = {
|
|
34300
|
-
...form,
|
|
34301
|
-
saving: true,
|
|
34302
|
-
error: ""
|
|
34303
|
-
};
|
|
34304
|
-
const file = String(form.file ?? "").trim();
|
|
34305
|
-
if (!file) {
|
|
34306
|
-
setForm({
|
|
34307
|
-
...next,
|
|
34308
|
-
saving: false,
|
|
34309
|
-
error: "文件名不能为空"
|
|
34310
|
-
});
|
|
34311
|
-
return;
|
|
34312
|
-
}
|
|
34313
|
-
if (form.fmType === "auto" && globList(form.globs).length === 0) {
|
|
34314
|
-
setForm({
|
|
34315
|
-
...next,
|
|
34316
|
-
saving: false,
|
|
34317
|
-
error: "自动规则需要至少一个 glob(逗号分隔)"
|
|
34318
|
-
});
|
|
34319
|
-
return;
|
|
34320
|
-
}
|
|
34321
|
-
setForm(next);
|
|
34322
|
-
rpc("rules.save", {
|
|
34323
|
-
scope: form.scope,
|
|
34324
|
-
origin: form.origin,
|
|
34325
|
-
workspacePath: form.workspacePath,
|
|
34326
|
-
file,
|
|
34327
|
-
content: form.content
|
|
34328
|
-
}).then((res) => {
|
|
34329
|
-
if (res && res.ok) {
|
|
34330
|
-
setForm(null);
|
|
34331
|
-
notify("规则已保存:" + file);
|
|
34332
|
-
load();
|
|
34333
|
-
} else setForm((prev) => ({
|
|
34334
|
-
...prev,
|
|
34335
|
-
saving: false,
|
|
34336
|
-
error: res?.error ?? "保存失败"
|
|
34337
|
-
}));
|
|
34338
|
-
}).catch((e) => setForm((prev) => ({
|
|
34339
|
-
...prev,
|
|
34340
|
-
saving: false,
|
|
34341
|
-
error: "保存异常: " + String(e)
|
|
34342
|
-
})));
|
|
34343
|
-
};
|
|
34344
|
-
/**
|
|
34345
|
-
* 删除规则(confirm 后走 rules.remove)。
|
|
34346
|
-
* @param rule 目标规则
|
|
34347
|
-
*/
|
|
34348
|
-
const removeRule = (rule) => {
|
|
34349
|
-
const reveal = rule.scope === "user" && rule.origin !== "legacy" ? "\n若旧目录有同名规则,删除后它将重新生效。" : "";
|
|
34350
|
-
if (!window.confirm("删除规则 " + rule.absPath + "?(不可恢复)" + reveal)) return;
|
|
34351
|
-
rpc("rules.remove", {
|
|
34352
|
-
scope: rule.scope,
|
|
34353
|
-
origin: rule.origin,
|
|
34354
|
-
workspacePath: rule.workspacePath,
|
|
34355
|
-
file: rule.file
|
|
34356
|
-
}).then((res) => {
|
|
34357
|
-
if (res && res.ok) {
|
|
34358
|
-
notify("已删除:" + rule.file);
|
|
34359
|
-
load();
|
|
34360
|
-
} else notify(res?.error ?? "删除失败");
|
|
34361
|
-
}).catch((e) => notify("删除异常: " + String(e)));
|
|
34362
|
-
};
|
|
34363
|
-
/**
|
|
34364
|
-
* 切换启用开关:本地乐观翻转(按 workspacePath+file 定位,失败回滚)。
|
|
34365
|
-
* @param rule 目标规则(project 已注入 workspacePath)
|
|
34366
|
-
*/
|
|
34367
|
-
const toggleRule = (rule) => {
|
|
34368
|
-
const enabled = !rule.enabled;
|
|
34369
|
-
const applyLocal = (on) => setData((prev) => prev ? {
|
|
34370
|
-
...prev,
|
|
34371
|
-
user: prev.user.map((r) => rule.scope === "user" && r.file === rule.file && (r.origin ?? "current") === (rule.origin ?? "current") ? {
|
|
34372
|
-
...r,
|
|
34373
|
-
enabled: on
|
|
34374
|
-
} : r),
|
|
34375
|
-
projects: prev.projects.map((p) => p.workspacePath === rule.workspacePath ? {
|
|
34376
|
-
...p,
|
|
34377
|
-
rules: p.rules.map((r) => r.file === rule.file ? {
|
|
34378
|
-
...r,
|
|
34379
|
-
enabled: on
|
|
34380
|
-
} : r)
|
|
34381
|
-
} : p)
|
|
34382
|
-
} : prev);
|
|
34383
|
-
applyLocal(enabled);
|
|
34384
|
-
rpc("rules.toggle", {
|
|
34385
|
-
scope: rule.scope,
|
|
34386
|
-
origin: rule.origin,
|
|
34387
|
-
workspacePath: rule.workspacePath,
|
|
34388
|
-
file: rule.file,
|
|
34389
|
-
enabled
|
|
34390
|
-
}).then((res) => {
|
|
34391
|
-
if (res && res.ok) {
|
|
34392
|
-
applyLocal(res.rule?.enabled === true);
|
|
34393
|
-
notify(res.rule?.enabled === false ? "已停用:" + rule.file : "已启用:" + rule.file);
|
|
34394
|
-
} else {
|
|
34395
|
-
applyLocal(!enabled);
|
|
34396
|
-
notify(res?.error ?? "切换失败");
|
|
34397
|
-
}
|
|
34398
|
-
}).catch((e) => {
|
|
34399
|
-
applyLocal(!enabled);
|
|
34400
|
-
notify("切换异常: " + String(e));
|
|
34401
|
-
});
|
|
34402
|
-
};
|
|
34403
|
-
/** 当前列表数据(按 Tab 取用户规则或当前工作区的项目规则)。 */
|
|
34404
|
-
const rowsOf = () => {
|
|
34405
|
-
if (!data) return [];
|
|
34406
|
-
if (tab === "user") return data.user;
|
|
34407
|
-
return activeProject()?.rules ?? [];
|
|
34408
|
-
};
|
|
34409
|
-
const renderTabs = () => react.default.createElement("div", { className: "edrv-rules-tabs" }, react.default.createElement("button", {
|
|
34410
|
-
className: "edrv-rules-tab" + (tab === "user" ? " on" : ""),
|
|
34411
|
-
onClick: () => switchTab("user")
|
|
34412
|
-
}, "用户规则"), react.default.createElement("button", {
|
|
34413
|
-
className: "edrv-rules-tab" + (tab === "project" ? " on" : ""),
|
|
34414
|
-
onClick: () => switchTab("project")
|
|
34415
|
-
}, "项目规则"), react.default.createElement("button", {
|
|
34416
|
-
className: "edrv-rules-new",
|
|
34417
|
-
title: "新建规则",
|
|
34418
|
-
onClick: openNew
|
|
34419
|
-
}, "+ 新建规则"));
|
|
34420
|
-
/** 项目 Tab 的工作区展示(不可编辑,自动跟随当前会话)。 */
|
|
34421
|
-
const renderWsLine = () => {
|
|
34422
|
-
if (tab !== "project") return null;
|
|
34423
|
-
return react.default.createElement("div", {
|
|
34424
|
-
className: "edrv-rules-ws-line",
|
|
34425
|
-
title: cwd ?? ""
|
|
34426
|
-
}, "工作区:" + (cwd ?? "当前会话未打开工作区"));
|
|
34427
|
-
};
|
|
34428
|
-
const renderEmpty = () => {
|
|
34429
|
-
if (tab === "project") {
|
|
34430
|
-
if (!cwd) return "当前会话没有工作区,项目规则不可用(项目规则跟随会话所在工作区)";
|
|
34431
|
-
if (!data?.projects?.length) return "当前 DSH 没有已注册的工作区;在目标工作区打开一次会话后再来管理";
|
|
34432
|
-
if (!activeProject()) return "当前工作区未注册为 DSH workspace,暂不能管理其项目规则";
|
|
34433
|
-
return "<工作区>/.dsh/rules/ 还没有规则,点击「+ 新建规则」创建";
|
|
34434
|
-
}
|
|
34435
|
-
return "还没有用户规则(当前 DSH_HOME/rules/ 或旧目录 ~/.dsh/rules/),点击「+ 新建规则」创建";
|
|
34436
|
-
};
|
|
34437
|
-
const renderBody = () => {
|
|
34438
|
-
if (form) return react.default.createElement(RuleEditor, {
|
|
34439
|
-
form,
|
|
34440
|
-
onType: onTypeChange,
|
|
34441
|
-
onDesc: onDescChange,
|
|
34442
|
-
onGlobs: onGlobsChange,
|
|
34443
|
-
onBody: onBodyChange,
|
|
34444
|
-
onSave: saveForm,
|
|
34445
|
-
onCancel: () => setForm(null)
|
|
34446
|
-
});
|
|
34447
|
-
if (!data) return react.default.createElement("div", { className: "edrv-rules-empty" }, "加载中…");
|
|
34448
|
-
if (error) return react.default.createElement("div", { className: "edrv-rules-err" }, error);
|
|
34449
|
-
const rows = rowsOf();
|
|
34450
|
-
if (!rows.length) return react.default.createElement("div", { className: "edrv-rules-empty" }, renderEmpty());
|
|
34451
|
-
const common = {
|
|
34452
|
-
descCap,
|
|
34453
|
-
narrow,
|
|
34454
|
-
onEdit: (rule) => openEdit({
|
|
34455
|
-
...rule,
|
|
34456
|
-
workspacePath: rule.scope === "project" ? cwd : void 0
|
|
34457
|
-
}),
|
|
34458
|
-
onRemove: (rule) => removeRule({
|
|
34459
|
-
...rule,
|
|
34460
|
-
workspacePath: rule.scope === "project" ? cwd : void 0
|
|
34461
|
-
}),
|
|
34462
|
-
onToggle: (rule) => toggleRule({
|
|
34463
|
-
...rule,
|
|
34464
|
-
workspacePath: rule.scope === "project" ? cwd : void 0
|
|
34465
|
-
})
|
|
34466
|
-
};
|
|
34467
|
-
return react.default.createElement("div", { className: "edrv-rules-list" }, rows.map((rule) => react.default.createElement(RuleRow, {
|
|
34468
|
-
key: rule.scope + ":" + (rule.origin ?? "project") + ":" + rule.file,
|
|
34469
|
-
rule,
|
|
34470
|
-
...common
|
|
34471
|
-
})));
|
|
34472
|
-
};
|
|
34939
|
+
}
|
|
34940
|
+
react.default.useEffect(loadEffect, [props.panel, attempt]);
|
|
34941
|
+
/** @private @author ddj 2026年10月22号 @description Explicit retry; failures never loop automatically. */
|
|
34942
|
+
function onRetry() {
|
|
34943
|
+
retry(attempt + 1);
|
|
34944
|
+
}
|
|
34945
|
+
if (view.component) return react.default.createElement(view.component, { ctx: props.ctx });
|
|
34473
34946
|
return react.default.createElement("div", {
|
|
34474
|
-
className: "edrv-
|
|
34475
|
-
|
|
34476
|
-
},
|
|
34947
|
+
className: "edrv-empty",
|
|
34948
|
+
role: view.error ? "alert" : "status"
|
|
34949
|
+
}, view.error ?? "正在加载面板…", view.error && react.default.createElement("button", { onClick: onRetry }, "重试"));
|
|
34477
34950
|
}
|
|
34478
34951
|
//#endregion
|
|
34479
34952
|
//#region src/client/ui/SvnAiPlanDialog.ts
|
|
@@ -35203,7 +35676,7 @@ window.__ModuleLoader__.load({
|
|
|
35203
35676
|
const [busy, setBusy] = react.default.useState(false);
|
|
35204
35677
|
const [remote, setRemote] = react.default.useState(null);
|
|
35205
35678
|
const [remoteBusy, setRemoteBusy] = react.default.useState(false);
|
|
35206
|
-
const [
|
|
35679
|
+
const [sizeSnapshot, setSizes] = react.default.useState(null);
|
|
35207
35680
|
const [renderLimit, setRenderLimit] = react.default.useState(RENDER_STEP);
|
|
35208
35681
|
const ctxRef = react.default.useRef(ctx);
|
|
35209
35682
|
ctxRef.current = ctx;
|
|
@@ -35304,17 +35777,39 @@ window.__ModuleLoader__.load({
|
|
|
35304
35777
|
const missingEntries = react.default.useMemo(() => (entries ?? []).filter((entry) => entry.status === "missing"), [entries]);
|
|
35305
35778
|
const unversionedEntries = react.default.useMemo(() => (entries ?? []).filter((entry) => entry.status === "unversioned"), [entries]);
|
|
35306
35779
|
const requested = react.default.useMemo(() => [...missingEntries, ...unversionedEntries].slice(0, 200).map((entry) => entry.path), [missingEntries, unversionedEntries]);
|
|
35307
|
-
const candidateKey =
|
|
35308
|
-
const
|
|
35309
|
-
|
|
35310
|
-
|
|
35311
|
-
if (pairsKnown || !requested.length) return;
|
|
35312
|
-
svnFileSizes(sessionId, requested).then((table) => setSizes(table));
|
|
35313
|
-
}, [
|
|
35314
|
-
pairsKnown,
|
|
35780
|
+
const candidateKey = JSON.stringify(requested);
|
|
35781
|
+
const sizeOwner = JSON.stringify([
|
|
35782
|
+
scope,
|
|
35783
|
+
sessionId,
|
|
35315
35784
|
candidateKey,
|
|
35316
|
-
|
|
35785
|
+
managed
|
|
35317
35786
|
]);
|
|
35787
|
+
const sizeOwnerRef = react.default.useRef(sizeOwner);
|
|
35788
|
+
sizeOwnerRef.current = sizeOwner;
|
|
35789
|
+
const sizes = sizeSnapshot?.owner === sizeOwner ? sizeSnapshot.table : {};
|
|
35790
|
+
const pairsNeeded = managed && missingEntries.length > 0 && unversionedEntries.length > 0;
|
|
35791
|
+
const pairsKnown = pairsNeeded && requested.every((path) => path in sizes);
|
|
35792
|
+
/** @private @author ddj 2026年10月22号 @returns Cleanup invalidating this request. @description Keep sizes scoped to this candidate generation; failures do not retry in a render loop. */
|
|
35793
|
+
function loadPairSizes() {
|
|
35794
|
+
if (!pairsNeeded || !requested.length) return;
|
|
35795
|
+
let disposed = false;
|
|
35796
|
+
/** @author ddj 2026年10月22号 @param table Candidate sizes. @description Publish only to the current request owner. */
|
|
35797
|
+
function acceptSizes(table) {
|
|
35798
|
+
if (!disposed && sizeOwnerRef.current === sizeOwner) setSizes({
|
|
35799
|
+
owner: sizeOwner,
|
|
35800
|
+
table
|
|
35801
|
+
});
|
|
35802
|
+
}
|
|
35803
|
+
/** @author ddj 2026年10月22号 @description Failed reads leave pairing unknown without breaking the changes list. */
|
|
35804
|
+
function ignoreFailure() {}
|
|
35805
|
+
/** @author ddj 2026年10月22号 @description Revoke this request on dependency change or unmount. */
|
|
35806
|
+
function cancelSizes() {
|
|
35807
|
+
disposed = true;
|
|
35808
|
+
}
|
|
35809
|
+
svnFileSizes(sessionId, requested).then(acceptSizes, ignoreFailure);
|
|
35810
|
+
return cancelSizes;
|
|
35811
|
+
}
|
|
35812
|
+
react.default.useEffect(loadPairSizes, [sizeOwner, pairsNeeded]);
|
|
35318
35813
|
const pairNoteOf = react.default.useMemo(() => {
|
|
35319
35814
|
if (!pairsKnown) return {};
|
|
35320
35815
|
const notes = {};
|
|
@@ -35786,851 +36281,6 @@ window.__ModuleLoader__.load({
|
|
|
35786
36281
|
}), remoteEl, pairCapEl, ...toolbar, react.default.createElement("div", { className: "edrv-svn-list" }, svnListBody(listState, handlers)), menuEl, aiPlanEl);
|
|
35787
36282
|
}
|
|
35788
36283
|
//#endregion
|
|
35789
|
-
//#region src/client/dap/variableTree.ts
|
|
35790
|
-
/**
|
|
35791
|
-
* 将变量树展平为 sibling rows;循环 ref 只展开一次,避免异常适配器数据造成递归。
|
|
35792
|
-
* @author ddj 2026年09月29号
|
|
35793
|
-
* @param roots 根变量
|
|
35794
|
-
* @param expanded 已展开的 variablesReference
|
|
35795
|
-
* @param cache ref → 子变量缓存
|
|
35796
|
-
* @param options 行数/深度上限
|
|
35797
|
-
*/
|
|
35798
|
-
function flattenVariableRows(roots, expanded, cache, options = {}) {
|
|
35799
|
-
const rows = [];
|
|
35800
|
-
const maxRows = options.maxRows ?? 1e3;
|
|
35801
|
-
const maxDepth = options.maxDepth ?? 32;
|
|
35802
|
-
const walk = (items, depth, ancestry, prefix) => {
|
|
35803
|
-
if (depth > maxDepth || rows.length >= maxRows) return;
|
|
35804
|
-
for (let index = 0; index < items.length && rows.length < maxRows; index += 1) {
|
|
35805
|
-
const variable = items[index];
|
|
35806
|
-
const key = prefix + "/" + index + ":" + variable.name;
|
|
35807
|
-
rows.push({
|
|
35808
|
-
key,
|
|
35809
|
-
variable,
|
|
35810
|
-
depth
|
|
35811
|
-
});
|
|
35812
|
-
if (variable.ref <= 0 || !expanded.has(variable.ref) || ancestry.has(variable.ref)) continue;
|
|
35813
|
-
const children = cache.get(variable.ref);
|
|
35814
|
-
if (!children?.length) continue;
|
|
35815
|
-
const nextAncestry = new Set(ancestry);
|
|
35816
|
-
nextAncestry.add(variable.ref);
|
|
35817
|
-
walk(children, depth + 1, nextAncestry, key);
|
|
35818
|
-
}
|
|
35819
|
-
};
|
|
35820
|
-
walk(roots, 0, /* @__PURE__ */ new Set(), "root");
|
|
35821
|
-
return rows;
|
|
35822
|
-
}
|
|
35823
|
-
//#endregion
|
|
35824
|
-
//#region src/client/dap/bpRowMenu.ts
|
|
35825
|
-
/**
|
|
35826
|
-
* 构造调试面板断点行右键菜单条目。
|
|
35827
|
-
* @author ddj 2026年09月21号
|
|
35828
|
-
* @param state 行内断点状态
|
|
35829
|
-
* @returns 菜单条目列表(顺序即展示顺序)
|
|
35830
|
-
*/
|
|
35831
|
-
function buildBpRowMenu(state) {
|
|
35832
|
-
return [
|
|
35833
|
-
{
|
|
35834
|
-
id: "edit",
|
|
35835
|
-
label: "编辑断点…"
|
|
35836
|
-
},
|
|
35837
|
-
{
|
|
35838
|
-
id: "edit-condition",
|
|
35839
|
-
label: "编辑条件…"
|
|
35840
|
-
},
|
|
35841
|
-
{
|
|
35842
|
-
id: "edit-hit-count",
|
|
35843
|
-
label: "编辑命中次数…"
|
|
35844
|
-
},
|
|
35845
|
-
{
|
|
35846
|
-
id: "edit-logpoint",
|
|
35847
|
-
label: "编辑记录点…"
|
|
35848
|
-
},
|
|
35849
|
-
state.enabled !== false ? {
|
|
35850
|
-
id: "disable",
|
|
35851
|
-
label: "禁用断点",
|
|
35852
|
-
separator: true
|
|
35853
|
-
} : {
|
|
35854
|
-
id: "enable",
|
|
35855
|
-
label: "启用断点",
|
|
35856
|
-
separator: true
|
|
35857
|
-
},
|
|
35858
|
-
{
|
|
35859
|
-
id: "remove",
|
|
35860
|
-
label: "删除断点"
|
|
35861
|
-
}
|
|
35862
|
-
];
|
|
35863
|
-
}
|
|
35864
|
-
//#endregion
|
|
35865
|
-
//#region src/client/dap/panelSplit.ts
|
|
35866
|
-
/**
|
|
35867
|
-
* dsh-vscode-mode client — 调试面板四段分栏高度(纯函数 + localStorage 持久)。
|
|
35868
|
-
* 对齐 VS Code/CodeBuddy splitview 语义:段间 sash 拖动时相邻两段高度重分配
|
|
35869
|
-
* (拖下 = 上段增高、下段减矮;任一段触到夹取界时拖动量被"吸收",分隔条停在可行位置)。
|
|
35870
|
-
* 存储:`edrv.dap.split.<scope>` → { variables, watch, stack, breakpoints }。
|
|
35871
|
-
* 作者 ddj 2026年09月21号
|
|
35872
|
-
*/
|
|
35873
|
-
/** 分栏段 key(与 DebugPanel section key 一致)。 */
|
|
35874
|
-
const SPLIT_KEYS = [
|
|
35875
|
-
"variables",
|
|
35876
|
-
"watch",
|
|
35877
|
-
"stack",
|
|
35878
|
-
"breakpoints"
|
|
35879
|
-
];
|
|
35880
|
-
/** 默认高度(沿用重构前各段 max-height 值,改前改后视觉一致)。 */
|
|
35881
|
-
const DEFAULT_HEIGHTS = {
|
|
35882
|
-
variables: 360,
|
|
35883
|
-
watch: 220,
|
|
35884
|
-
stack: 180,
|
|
35885
|
-
breakpoints: 260
|
|
35886
|
-
};
|
|
35887
|
-
/** 单段最大高度(防无限拖爆面板)。 */
|
|
35888
|
-
const MAX_H = 1200;
|
|
35889
|
-
/** localStorage key 前缀。 */
|
|
35890
|
-
const KEY_PREFIX = "edrv.dap.split.";
|
|
35891
|
-
/**
|
|
35892
|
-
* 夹取单段高度到 [MIN_H, MAX_H](非有限数回退最小值)。
|
|
35893
|
-
* @author ddj 2026年09月21号
|
|
35894
|
-
* @param px 目标高度
|
|
35895
|
-
*/
|
|
35896
|
-
function clampSectionHeight(px) {
|
|
35897
|
-
if (!Number.isFinite(px)) return 64;
|
|
35898
|
-
return Math.min(MAX_H, Math.max(64, Math.round(px)));
|
|
35899
|
-
}
|
|
35900
|
-
/**
|
|
35901
|
-
* 归一化高度表:非对象/缺 key 回退默认,非法值丢弃(损坏安全)。
|
|
35902
|
-
* @author ddj 2026年09月21号
|
|
35903
|
-
* @param input localStorage 解析结果
|
|
35904
|
-
*/
|
|
35905
|
-
function sanitizeHeights(input) {
|
|
35906
|
-
const out = { ...DEFAULT_HEIGHTS };
|
|
35907
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) return out;
|
|
35908
|
-
const data = input;
|
|
35909
|
-
for (const key of SPLIT_KEYS) {
|
|
35910
|
-
const value = data[key];
|
|
35911
|
-
if (typeof value === "number" && Number.isFinite(value)) out[key] = clampSectionHeight(value);
|
|
35912
|
-
}
|
|
35913
|
-
return out;
|
|
35914
|
-
}
|
|
35915
|
-
/**
|
|
35916
|
-
* 相邻两段高度重分配:above += dy、below −= dy,各自夹取;
|
|
35917
|
-
* 一段被夹取吸收的量补给另一段(分隔条停在可行位置,拖动量尽量生效)。
|
|
35918
|
-
* @author ddj 2026年09月21号
|
|
35919
|
-
* @param heights 拖动起点高度表(不修改入参)
|
|
35920
|
-
* @param above 上邻段 key
|
|
35921
|
-
* @param below 下邻段 key
|
|
35922
|
-
* @param dy 拖动位移(>0 向下拖 = 上段增高)
|
|
35923
|
-
* @returns 新高度表
|
|
35924
|
-
*/
|
|
35925
|
-
function redistribute(heights, above, below, dy) {
|
|
35926
|
-
const clean = Math.round(Number.isFinite(dy) ? dy : 0);
|
|
35927
|
-
const next = { ...heights };
|
|
35928
|
-
const startAbove = heights[above] ?? 64;
|
|
35929
|
-
const startBelow = heights[below] ?? 64;
|
|
35930
|
-
const targetAbove = clampSectionHeight(startAbove + clean);
|
|
35931
|
-
const targetBelow = clampSectionHeight(startBelow - clean);
|
|
35932
|
-
next[above] = targetAbove;
|
|
35933
|
-
next[below] = targetBelow;
|
|
35934
|
-
const lostAbove = startAbove + clean - targetAbove;
|
|
35935
|
-
const lostBelow = startBelow - clean - targetBelow;
|
|
35936
|
-
if (lostAbove > 0) next[below] = clampSectionHeight(targetBelow + lostAbove);
|
|
35937
|
-
else if (lostBelow > 0) next[above] = clampSectionHeight(targetAbove + lostBelow);
|
|
35938
|
-
return next;
|
|
35939
|
-
}
|
|
35940
|
-
/**
|
|
35941
|
-
* 读高度表(缺失/损坏回退默认值)。
|
|
35942
|
-
* @author ddj 2026年09月21号
|
|
35943
|
-
* @param scope 状态作用域
|
|
35944
|
-
*/
|
|
35945
|
-
function loadSectionHeights(scope) {
|
|
35946
|
-
if (!scope) return { ...DEFAULT_HEIGHTS };
|
|
35947
|
-
try {
|
|
35948
|
-
const raw = localStorage.getItem(KEY_PREFIX + scope);
|
|
35949
|
-
return sanitizeHeights(raw ? JSON.parse(raw) : null);
|
|
35950
|
-
} catch {
|
|
35951
|
-
return { ...DEFAULT_HEIGHTS };
|
|
35952
|
-
}
|
|
35953
|
-
}
|
|
35954
|
-
/**
|
|
35955
|
-
* 写高度表(写失败静默;写入前归一化)。
|
|
35956
|
-
* @author ddj 2026年09月21号
|
|
35957
|
-
* @param scope 状态作用域
|
|
35958
|
-
* @param heights 高度表
|
|
35959
|
-
*/
|
|
35960
|
-
function saveSectionHeights(scope, heights) {
|
|
35961
|
-
try {
|
|
35962
|
-
localStorage.setItem(KEY_PREFIX + scope, JSON.stringify(sanitizeHeights(heights)));
|
|
35963
|
-
} catch {}
|
|
35964
|
-
}
|
|
35965
|
-
//#endregion
|
|
35966
|
-
//#region src/client/sidebar/panels/DebugPanel.ts
|
|
35967
|
-
/**
|
|
35968
|
-
* dsh-vscode-mode client — 侧边栏「调试」面板(对齐 CodeBuddy「运行和调试」视图)。
|
|
35969
|
-
* 顶部工具条:配置下拉 + 启动/继续 + 暂停 + 单步组 + 停止(禁用逻辑镜像编辑器浮动条)。
|
|
35970
|
-
* 四段折叠区(变量 / 监视 / 调用堆栈 / 断点,图2 顺序)+ 底部调试控制台(REPL);
|
|
35971
|
-
* 每段头部可折叠(状态按 scope 持久化)且带悬浮动作按钮。
|
|
35972
|
-
* 断点多方式管理(对齐 CodeBuddy 断点区):段头「新建断点」菜单(条件断点/记录点,
|
|
35973
|
-
* 落在活动编辑器光标行)、禁用/启用所有、删除所有;断点行右键菜单(编辑条件/命中次数/
|
|
35974
|
-
* 记录点/启停/删除)→ 行内展开编辑器(Enter 提交 / Escape 取消,字段语义同 BpWidget);
|
|
35975
|
-
* 行内点展示适配器回执(未验证 = 空心点 + tooltip)。
|
|
35976
|
-
* 数据自取:paused 时经 dapStore 拉栈/作用域/变量/监视求值;断点读写 dapStore 断点表;
|
|
35977
|
-
* 输出流 = host poll 事件缓存(outputs)。host 不可达或未暂停时各段空态降级。
|
|
35978
|
-
* 断点行整行可点:除启停 checkbox 与删除 × 外,点击任一处即打开并定位到该断点行。
|
|
35979
|
-
* 作者 ddj 2026年09月29号 / 2026年09月21号
|
|
35980
|
-
*/
|
|
35981
|
-
/** 变量展开缓存上限(ref → 子级;防长会话累积)。 */
|
|
35982
|
-
const VAR_CACHE_CAP = 200;
|
|
35983
|
-
/** REPL 历史行上限。 */
|
|
35984
|
-
const REPL_CAP = 200;
|
|
35985
|
-
/** 监视持久化 key 前缀(CACHE_KEY 无调试段,自管命名空间)。 */
|
|
35986
|
-
const WATCH_KEY = "edrv.dap.watch.";
|
|
35987
|
-
/** 段折叠持久化 key 前缀(存折叠段名数组;损坏安全,默认全展开)。 */
|
|
35988
|
-
const FOLD_KEY = "edrv.dap.fold.";
|
|
35989
|
-
/** 调试配置文件(工作区相对路径;⚙ 按钮跳转/新建目标;与 VS Code 的 .vscode/launch.json 互不干扰)。 */
|
|
35990
|
-
const LAUNCH_REL = DAP_LAUNCH_REL;
|
|
35991
|
-
/** 新建调试配置模板(JSONC;示例按本机已装的 EmmyLua 适配器写)。 */
|
|
35992
|
-
const LAUNCH_TEMPLATE = `{
|
|
35993
|
-
// DSH 调试配置(插件专属 .dsh/launch.json,支持注释;改完整页刷新生效)
|
|
35994
|
-
// type 来自本机已装扩展的 contributes.debuggers(EmmyLua:emmylua_attach / emmylua_launch / emmylua_new)
|
|
35995
|
-
"version": "0.2.0",
|
|
35996
|
-
"configurations": [
|
|
35997
|
-
{
|
|
35998
|
-
"name": "附加 Unity (xLua)",
|
|
35999
|
-
"type": "emmylua_attach",
|
|
36000
|
-
"request": "attach",
|
|
36001
|
-
"processName": "Unity",
|
|
36002
|
-
"captureLog": true
|
|
36003
|
-
}
|
|
36004
|
-
]
|
|
36005
|
-
}`;
|
|
36006
|
-
/** 相位徽标文案。 */
|
|
36007
|
-
const PHASE_LABEL = {
|
|
36008
|
-
idle: "未启动",
|
|
36009
|
-
starting: "启动中…",
|
|
36010
|
-
waiting: "等待目标…",
|
|
36011
|
-
running: "运行中",
|
|
36012
|
-
paused: "已暂停",
|
|
36013
|
-
terminated: "已结束"
|
|
36014
|
-
};
|
|
36015
|
-
/** 读取监视表达式列表(损坏安全)。 */
|
|
36016
|
-
function loadWatches(scope) {
|
|
36017
|
-
if (!scope) return [];
|
|
36018
|
-
try {
|
|
36019
|
-
const raw = window.localStorage.getItem(WATCH_KEY + scope);
|
|
36020
|
-
const list = raw ? JSON.parse(raw) : [];
|
|
36021
|
-
return Array.isArray(list) ? list.filter((item) => typeof item === "string" && item.trim()) : [];
|
|
36022
|
-
} catch {
|
|
36023
|
-
return [];
|
|
36024
|
-
}
|
|
36025
|
-
}
|
|
36026
|
-
/** 写监视表达式列表。 */
|
|
36027
|
-
function saveWatches(scope, list) {
|
|
36028
|
-
try {
|
|
36029
|
-
window.localStorage.setItem(WATCH_KEY + scope, JSON.stringify(list));
|
|
36030
|
-
} catch {}
|
|
36031
|
-
}
|
|
36032
|
-
/** 读折叠段名列表(损坏安全;非字符串项丢弃)。 */
|
|
36033
|
-
function loadFolds(scope) {
|
|
36034
|
-
if (!scope) return [];
|
|
36035
|
-
try {
|
|
36036
|
-
const raw = window.localStorage.getItem(FOLD_KEY + scope);
|
|
36037
|
-
const list = raw ? JSON.parse(raw) : [];
|
|
36038
|
-
return Array.isArray(list) ? list.filter((item) => typeof item === "string" && item) : [];
|
|
36039
|
-
} catch {
|
|
36040
|
-
return [];
|
|
36041
|
-
}
|
|
36042
|
-
}
|
|
36043
|
-
/** 写折叠段名列表(写失败静默)。 */
|
|
36044
|
-
function saveFolds(scope, list) {
|
|
36045
|
-
try {
|
|
36046
|
-
window.localStorage.setItem(FOLD_KEY + scope, JSON.stringify(list));
|
|
36047
|
-
} catch {}
|
|
36048
|
-
}
|
|
36049
|
-
/**
|
|
36050
|
-
* 「调试」面板组件。
|
|
36051
|
-
* @param ctx 侧栏上下文(openFileAt 跳转 / scope 持久化 / cwd / editor 光标)
|
|
36052
|
-
*/
|
|
36053
|
-
function DebugPanel({ ctx }) {
|
|
36054
|
-
const scope = ctx?.scope || "";
|
|
36055
|
-
const [, setTick] = react.default.useState(0);
|
|
36056
|
-
react.default.useEffect(() => dapStore.subscribe(() => setTick((t) => t + 1)), []);
|
|
36057
|
-
const snap = dapStore.getSnapshot();
|
|
36058
|
-
const paused = snap.phase === "paused";
|
|
36059
|
-
const dapActive = snap.phase !== "idle" && snap.phase !== "terminated";
|
|
36060
|
-
const [stacks, setStacks] = react.default.useState([]);
|
|
36061
|
-
const [scopes, setScopes] = react.default.useState([]);
|
|
36062
|
-
const [varCache, setVarCache] = react.default.useState(() => /* @__PURE__ */ new Map());
|
|
36063
|
-
const [expanded, setExpanded] = react.default.useState(() => /* @__PURE__ */ new Set());
|
|
36064
|
-
const [watches, setWatches] = react.default.useState(() => loadWatches(scope));
|
|
36065
|
-
const [watchHits, setWatchHits] = react.default.useState({});
|
|
36066
|
-
const [watchDraft, setWatchDraft] = react.default.useState("");
|
|
36067
|
-
const [replLog, setReplLog] = react.default.useState([]);
|
|
36068
|
-
const [replDraft, setReplDraft] = react.default.useState("");
|
|
36069
|
-
const [folds, setFolds] = react.default.useState(() => new Set(loadFolds(scope)));
|
|
36070
|
-
const [heights, setHeights] = react.default.useState(() => loadSectionHeights(scope));
|
|
36071
|
-
const [splitDragging, setSplitDragging] = react.default.useState(false);
|
|
36072
|
-
const [bpMenu, setBpMenu] = react.default.useState(null);
|
|
36073
|
-
const [newBpMenu, setNewBpMenu] = react.default.useState(null);
|
|
36074
|
-
const [bpEdit, setBpEdit] = react.default.useState(null);
|
|
36075
|
-
const [pickPid, setPickPid] = react.default.useState(0);
|
|
36076
|
-
const watchInputRef = react.default.useRef(null);
|
|
36077
|
-
const stackTopKey = paused && snap.topFrame ? snap.topFrame.file + ":" + snap.topFrame.line : "";
|
|
36078
|
-
react.default.useEffect(() => {
|
|
36079
|
-
if (!ctx?.cwd) return;
|
|
36080
|
-
dapStore.setWorkspace(ctx.cwd);
|
|
36081
|
-
dapStore.ensureConfigs();
|
|
36082
|
-
}, [ctx?.cwd]);
|
|
36083
|
-
const [reloadTick, setReloadTick] = react.default.useState(0);
|
|
36084
|
-
react.default.useEffect(() => {
|
|
36085
|
-
if (!paused) {
|
|
36086
|
-
setStacks([]);
|
|
36087
|
-
setScopes([]);
|
|
36088
|
-
setVarCache(/* @__PURE__ */ new Map());
|
|
36089
|
-
setExpanded(/* @__PURE__ */ new Set());
|
|
36090
|
-
setWatchHits({});
|
|
36091
|
-
return;
|
|
36092
|
-
}
|
|
36093
|
-
let alive = true;
|
|
36094
|
-
const load = async () => {
|
|
36095
|
-
const frames = await dapStore.stackTrace();
|
|
36096
|
-
if (!alive) return;
|
|
36097
|
-
setStacks(frames);
|
|
36098
|
-
const list = await dapStore.scopes(snap.selectedFrameId);
|
|
36099
|
-
if (!alive) return;
|
|
36100
|
-
setScopes(list);
|
|
36101
|
-
const first = list[0];
|
|
36102
|
-
if (first?.ref) {
|
|
36103
|
-
const vars = await dapStore.variables(first.ref);
|
|
36104
|
-
if (!alive) return;
|
|
36105
|
-
setVarCache((prev) => new Map(prev).set(first.ref, vars));
|
|
36106
|
-
setExpanded((prev) => new Set(prev).add(first.ref));
|
|
36107
|
-
}
|
|
36108
|
-
const hits = {};
|
|
36109
|
-
for (const expr of watches) hits[expr] = await dapStore.evaluate(expr, snap.selectedFrameId);
|
|
36110
|
-
if (alive) setWatchHits(hits);
|
|
36111
|
-
};
|
|
36112
|
-
load();
|
|
36113
|
-
return () => {
|
|
36114
|
-
alive = false;
|
|
36115
|
-
};
|
|
36116
|
-
}, [
|
|
36117
|
-
paused,
|
|
36118
|
-
stackTopKey,
|
|
36119
|
-
snap.selectedFrameId,
|
|
36120
|
-
watches,
|
|
36121
|
-
reloadTick
|
|
36122
|
-
]);
|
|
36123
|
-
const expandVar = async (ref) => {
|
|
36124
|
-
setExpanded((prev) => {
|
|
36125
|
-
const next = new Set(prev);
|
|
36126
|
-
if (next.has(ref)) next.delete(ref);
|
|
36127
|
-
else next.add(ref);
|
|
36128
|
-
return next;
|
|
36129
|
-
});
|
|
36130
|
-
if (!varCache.has(ref)) {
|
|
36131
|
-
const vars = await dapStore.variables(ref);
|
|
36132
|
-
setVarCache((prev) => {
|
|
36133
|
-
const next = new Map(prev);
|
|
36134
|
-
next.set(ref, vars);
|
|
36135
|
-
if (next.size > VAR_CACHE_CAP) next.delete(next.keys().next().value);
|
|
36136
|
-
return next;
|
|
36137
|
-
});
|
|
36138
|
-
}
|
|
36139
|
-
};
|
|
36140
|
-
const addWatch = () => {
|
|
36141
|
-
const expr = watchDraft.trim();
|
|
36142
|
-
if (!expr || watches.includes(expr)) {
|
|
36143
|
-
setWatchDraft("");
|
|
36144
|
-
return;
|
|
36145
|
-
}
|
|
36146
|
-
const next = [...watches, expr];
|
|
36147
|
-
setWatches(next);
|
|
36148
|
-
saveWatches(scope, next);
|
|
36149
|
-
setWatchDraft("");
|
|
36150
|
-
if (paused) dapStore.evaluate(expr, snap.selectedFrameId).then((r) => setWatchHits((prev) => ({
|
|
36151
|
-
...prev,
|
|
36152
|
-
[expr]: r
|
|
36153
|
-
})));
|
|
36154
|
-
};
|
|
36155
|
-
const removeWatch = (expr) => {
|
|
36156
|
-
const next = watches.filter((item) => item !== expr);
|
|
36157
|
-
setWatches(next);
|
|
36158
|
-
saveWatches(scope, next);
|
|
36159
|
-
};
|
|
36160
|
-
/** 移除全部监视(段头动作;持久化与命中缓存同步清)。 */
|
|
36161
|
-
const clearWatches = () => {
|
|
36162
|
-
setWatches([]);
|
|
36163
|
-
saveWatches(scope, []);
|
|
36164
|
-
setWatchHits({});
|
|
36165
|
-
};
|
|
36166
|
-
/** 聚焦监视新增输入(段折叠时先展开,二次点击聚焦)。 */
|
|
36167
|
-
const focusWatchInput = () => {
|
|
36168
|
-
if (folds.has("watch")) {
|
|
36169
|
-
toggleFold("watch");
|
|
36170
|
-
return;
|
|
36171
|
-
}
|
|
36172
|
-
watchInputRef.current?.focus?.();
|
|
36173
|
-
};
|
|
36174
|
-
const runRepl = async () => {
|
|
36175
|
-
const expr = replDraft.trim();
|
|
36176
|
-
if (!expr) return;
|
|
36177
|
-
setReplDraft("");
|
|
36178
|
-
const r = await dapStore.evaluate(expr, snap.selectedFrameId);
|
|
36179
|
-
setReplLog((prev) => {
|
|
36180
|
-
const next = [...prev, {
|
|
36181
|
-
expr,
|
|
36182
|
-
result: r.result,
|
|
36183
|
-
type: r.type
|
|
36184
|
-
}];
|
|
36185
|
-
return next.length > REPL_CAP ? next.slice(next.length - REPL_CAP) : next;
|
|
36186
|
-
});
|
|
36187
|
-
};
|
|
36188
|
-
/** 切换段折叠(持久化到 localStorage)。 */
|
|
36189
|
-
const toggleFold = (key) => {
|
|
36190
|
-
setFolds((prev) => {
|
|
36191
|
-
const next = new Set(prev);
|
|
36192
|
-
if (next.has(key)) next.delete(key);
|
|
36193
|
-
else next.add(key);
|
|
36194
|
-
saveFolds(scope, [...next]);
|
|
36195
|
-
return next;
|
|
36196
|
-
});
|
|
36197
|
-
};
|
|
36198
|
-
const splitCleanupRef = react.default.useRef(null);
|
|
36199
|
-
react.default.useEffect(() => () => {
|
|
36200
|
-
const cleanup = splitCleanupRef.current;
|
|
36201
|
-
if (cleanup) cleanup();
|
|
36202
|
-
}, []);
|
|
36203
|
-
/** 段间 sash 拖动:以起点高度为基准按 dy 相邻重分配;pointerup 持久化(镜像 dapbar 拖动模式)。 */
|
|
36204
|
-
const startSplitDrag = (above, below) => (e) => {
|
|
36205
|
-
e.preventDefault();
|
|
36206
|
-
const startY = e.clientY;
|
|
36207
|
-
const startHeights = { ...heights };
|
|
36208
|
-
const onMove = (ev) => {
|
|
36209
|
-
setHeights(redistribute(startHeights, above, below, ev.clientY - startY));
|
|
36210
|
-
};
|
|
36211
|
-
const detach = () => {
|
|
36212
|
-
window.removeEventListener("pointermove", onMove);
|
|
36213
|
-
window.removeEventListener("pointerup", onUp);
|
|
36214
|
-
splitCleanupRef.current = null;
|
|
36215
|
-
setSplitDragging(false);
|
|
36216
|
-
};
|
|
36217
|
-
const onUp = () => {
|
|
36218
|
-
detach();
|
|
36219
|
-
setHeights((prev) => {
|
|
36220
|
-
saveSectionHeights(scope, prev);
|
|
36221
|
-
return prev;
|
|
36222
|
-
});
|
|
36223
|
-
};
|
|
36224
|
-
splitCleanupRef.current = detach;
|
|
36225
|
-
window.addEventListener("pointermove", onMove);
|
|
36226
|
-
window.addEventListener("pointerup", onUp);
|
|
36227
|
-
setSplitDragging(true);
|
|
36228
|
-
};
|
|
36229
|
-
const cwd = ctx?.cwd || dapStore.workspace();
|
|
36230
|
-
const bpMap = dapStore.bpMapOf(scope);
|
|
36231
|
-
const bpRows = Object.entries(bpMap).flatMap(([path, list]) => list.map((entry) => ({
|
|
36232
|
-
path,
|
|
36233
|
-
entry
|
|
36234
|
-
}))).sort((a, b) => a.path.localeCompare(b.path) || a.entry.line - b.entry.line);
|
|
36235
|
-
const allBpEnabled = bpRows.length > 0 && bpRows.every((row) => row.entry.enabled);
|
|
36236
|
-
const toggleBpEnabled = (row) => dapStore.setBpEnabled(scope, cwd, row.path, row.entry.line, !row.entry.enabled);
|
|
36237
|
-
const removeBp = (row) => dapStore.toggleAt(scope, cwd, row.path, row.entry.line);
|
|
36238
|
-
/** 删除所有断点(confirm 守卫;逐文件同步空载荷清空 Monaco 装饰与 host 断点)。 */
|
|
36239
|
-
const removeAllBps = () => {
|
|
36240
|
-
if (ctx?.confirm && !ctx.confirm("删除所有断点?")) return;
|
|
36241
|
-
dapStore.removeAllBps(scope, cwd);
|
|
36242
|
-
setBpEdit(null);
|
|
36243
|
-
};
|
|
36244
|
-
const jumpBp = (row) => {
|
|
36245
|
-
const target = bpJumpTargetOf(row);
|
|
36246
|
-
if (target) ctx?.openFileAt?.(target.path, target.line, target.column);
|
|
36247
|
-
};
|
|
36248
|
-
const autoBpModeOf = (entry) => entry?.logMessage ? BP_MODE.LOG_MESSAGE : entry?.hitCondition ? BP_MODE.HIT_COUNT : BP_MODE.EXPRESSION;
|
|
36249
|
-
const openBpRowEditor = (path, line, entry, mode) => {
|
|
36250
|
-
setBpEdit({
|
|
36251
|
-
path,
|
|
36252
|
-
line,
|
|
36253
|
-
mode: typeof mode === "number" ? mode : autoBpModeOf(entry),
|
|
36254
|
-
drafts: {
|
|
36255
|
-
condition: entry?.condition || "",
|
|
36256
|
-
hitCondition: entry?.hitCondition || "",
|
|
36257
|
-
logMessage: entry?.logMessage || ""
|
|
36258
|
-
}
|
|
36259
|
-
});
|
|
36260
|
-
};
|
|
36261
|
-
const commitBpEdit = () => {
|
|
36262
|
-
if (!bpEdit) return;
|
|
36263
|
-
dapStore.applyBpFields(scope, cwd, bpEdit.path, bpEdit.line, { ...bpEdit.drafts });
|
|
36264
|
-
setBpEdit(null);
|
|
36265
|
-
ctx?.notify?.("断点已更新");
|
|
36266
|
-
};
|
|
36267
|
-
/** 断点行右键菜单动作分发(菜单数据由 buildBpRowMenu 产出)。 */
|
|
36268
|
-
const runBpRowAction = (action, row) => {
|
|
36269
|
-
if (action === "remove") {
|
|
36270
|
-
removeBp(row);
|
|
36271
|
-
return;
|
|
36272
|
-
}
|
|
36273
|
-
if (action === "enable" || action === "disable") {
|
|
36274
|
-
dapStore.setBpEnabled(scope, cwd, row.path, row.entry.line, action === "enable");
|
|
36275
|
-
return;
|
|
36276
|
-
}
|
|
36277
|
-
const mode = action === "edit-condition" ? BP_MODE.EXPRESSION : action === "edit-hit-count" ? BP_MODE.HIT_COUNT : action === "edit-logpoint" ? BP_MODE.LOG_MESSAGE : void 0;
|
|
36278
|
-
openBpRowEditor(row.path, row.entry.line, row.entry, mode);
|
|
36279
|
-
};
|
|
36280
|
-
/** 段头「新建断点」:条件断点/记录点落在活动编辑器光标行(已有断点则直接编辑不翻转)。 */
|
|
36281
|
-
const addBpAtCursor = (mode) => {
|
|
36282
|
-
const ed = ctx?.editor?.();
|
|
36283
|
-
const path = editorModelPathOf(ed);
|
|
36284
|
-
const line = ed?.getPosition?.()?.lineNumber;
|
|
36285
|
-
if (!path || !line || !Number.isFinite(line)) {
|
|
36286
|
-
ctx?.notify?.("无活动文件,无法添加断点");
|
|
36287
|
-
return;
|
|
36288
|
-
}
|
|
36289
|
-
if (!(dapStore.bpMapOf(scope)[path] ?? []).find((it) => it.line === line)) dapStore.toggleAt(scope, cwd, path, line);
|
|
36290
|
-
const entry = (dapStore.bpMapOf(scope)[path] ?? []).find((it) => it.line === line);
|
|
36291
|
-
if (entry) openBpRowEditor(path, line, entry, mode);
|
|
36292
|
-
};
|
|
36293
|
-
/** ⚙ 打开/新建调试配置:存在即跳转;缺失经确认后写模板(edrv.save 无 rev 仅用于新建,绝不覆盖已有文件)。 */
|
|
36294
|
-
const openLaunchConfig = async () => {
|
|
36295
|
-
if (!ctx?.cwd) {
|
|
36296
|
-
ctx?.notify?.("无工作区,无法定位 " + LAUNCH_REL);
|
|
36297
|
-
return;
|
|
36298
|
-
}
|
|
36299
|
-
try {
|
|
36300
|
-
if ((await rpc("edrv.read", {
|
|
36301
|
-
sessionId: ctx?.sessionId,
|
|
36302
|
-
path: LAUNCH_REL
|
|
36303
|
-
}).catch(() => null))?.ok) {
|
|
36304
|
-
ctx?.openFile?.(LAUNCH_REL);
|
|
36305
|
-
return;
|
|
36306
|
-
}
|
|
36307
|
-
} catch {}
|
|
36308
|
-
if (ctx?.confirm && !ctx.confirm("未找到 " + LAUNCH_REL + ",是否创建调试配置模板?")) return;
|
|
36309
|
-
try {
|
|
36310
|
-
const saved = await rpc("edrv.save", {
|
|
36311
|
-
sessionId: ctx?.sessionId,
|
|
36312
|
-
path: LAUNCH_REL,
|
|
36313
|
-
content: LAUNCH_TEMPLATE
|
|
36314
|
-
});
|
|
36315
|
-
if (!saved?.ok) {
|
|
36316
|
-
ctx?.notify?.(saved?.error || "创建 " + LAUNCH_REL + " 失败");
|
|
36317
|
-
return;
|
|
36318
|
-
}
|
|
36319
|
-
dapStore.ensureConfigs(true);
|
|
36320
|
-
ctx?.openFile?.(LAUNCH_REL);
|
|
36321
|
-
ctx?.notify?.("已创建 " + LAUNCH_REL);
|
|
36322
|
-
} catch (error) {
|
|
36323
|
-
ctx?.notify?.("创建 " + LAUNCH_REL + " 失败:" + String(error));
|
|
36324
|
-
}
|
|
36325
|
-
};
|
|
36326
|
-
/** 段头动作按钮(hover 显隐,样式见 .edrv-dapseg-act)。 */
|
|
36327
|
-
const segAct = (key, label, title, onClick) => react.default.createElement("button", {
|
|
36328
|
-
key,
|
|
36329
|
-
className: "edrv-dapseg-act",
|
|
36330
|
-
title,
|
|
36331
|
-
onClick: (e) => {
|
|
36332
|
-
e.stopPropagation();
|
|
36333
|
-
onClick(e);
|
|
36334
|
-
}
|
|
36335
|
-
}, label);
|
|
36336
|
-
/** 折叠段壳:chevron + 标题 + 悬浮动作区 + 可折叠主体(高度由分栏 state 内联接管)。 */
|
|
36337
|
-
const section = (title, key, variant, actions, children) => {
|
|
36338
|
-
const folded = folds.has(key);
|
|
36339
|
-
return react.default.createElement("div", {
|
|
36340
|
-
className: "edrv-dapseg edrv-dapseg-" + variant,
|
|
36341
|
-
key
|
|
36342
|
-
}, react.default.createElement("div", {
|
|
36343
|
-
className: "edrv-dapseg-head",
|
|
36344
|
-
onClick: () => toggleFold(key)
|
|
36345
|
-
}, react.default.createElement("span", {
|
|
36346
|
-
className: "edrv-dapseg-chevron",
|
|
36347
|
-
title: folded ? "展开" : "折叠"
|
|
36348
|
-
}, folded ? "▸" : "▾"), react.default.createElement("span", { className: "edrv-dapseg-title" }, title), react.default.createElement("span", { style: { flex: 1 } }), ...folded ? [] : actions || []), ...folded ? [] : [react.default.createElement("div", {
|
|
36349
|
-
className: "edrv-dapseg-body",
|
|
36350
|
-
style: { height: (heights[key] ?? 200) + "px" }
|
|
36351
|
-
}, ...children)]);
|
|
36352
|
-
};
|
|
36353
|
-
/** 段间分隔条(两侧段都展开时才渲染;拖动调整相邻两段高度占比)。 */
|
|
36354
|
-
const sash = (above, below) => folds.has(above) || folds.has(below) ? null : react.default.createElement("div", {
|
|
36355
|
-
key: "sash-" + above + "-" + below,
|
|
36356
|
-
className: "edrv-dapseg-sash" + (splitDragging ? " dragging" : ""),
|
|
36357
|
-
title: "拖动调整上下两段高度占比",
|
|
36358
|
-
onPointerDown: startSplitDrag(above, below)
|
|
36359
|
-
});
|
|
36360
|
-
const renderVarRow = (row) => {
|
|
36361
|
-
const v = row.variable;
|
|
36362
|
-
const depth = row.depth + 1;
|
|
36363
|
-
return react.default.createElement("div", {
|
|
36364
|
-
key: row.key,
|
|
36365
|
-
className: "edrv-dapvar",
|
|
36366
|
-
style: { paddingLeft: 8 + depth * 12 }
|
|
36367
|
-
}, v.ref > 0 ? react.default.createElement("button", {
|
|
36368
|
-
className: "edrv-dapvar-tw",
|
|
36369
|
-
title: expanded.has(v.ref) ? "折叠" : "展开",
|
|
36370
|
-
onClick: () => {
|
|
36371
|
-
expandVar(v.ref);
|
|
36372
|
-
}
|
|
36373
|
-
}, expanded.has(v.ref) ? "▾" : "▸") : react.default.createElement("span", { className: "edrv-dapvar-tw" }, ""), react.default.createElement("span", {
|
|
36374
|
-
className: "edrv-dapvar-name",
|
|
36375
|
-
title: v.name
|
|
36376
|
-
}, v.name), react.default.createElement("span", {
|
|
36377
|
-
className: "edrv-dapvar-val",
|
|
36378
|
-
title: v.value
|
|
36379
|
-
}, v.value), v.type ? react.default.createElement("span", { className: "edrv-dapvar-type" }, v.type) : null);
|
|
36380
|
-
};
|
|
36381
|
-
const stackRows = stacks.length ? stacks.map((f) => react.default.createElement("div", {
|
|
36382
|
-
key: f.id,
|
|
36383
|
-
className: "edrv-dapframe" + (f.id === snap.selectedFrameId ? " selected" : ""),
|
|
36384
|
-
title: f.rawFile,
|
|
36385
|
-
onClick: () => {
|
|
36386
|
-
dapStore.selectFrame(f.id);
|
|
36387
|
-
const target = f.file || f.rawFile;
|
|
36388
|
-
if (target) ctx?.openFileAt?.(target, f.line, 1);
|
|
36389
|
-
}
|
|
36390
|
-
}, react.default.createElement("span", { className: "edrv-dapframe-name" }, f.name || "(anonymous)"), react.default.createElement("span", { className: "edrv-dapframe-loc" }, (f.file || f.rawFile) + ":" + f.line))) : [react.default.createElement("div", {
|
|
36391
|
-
key: "empty",
|
|
36392
|
-
className: "edrv-dapempty"
|
|
36393
|
-
}, paused ? "(无调用栈)" : "暂停时显示调用堆栈")];
|
|
36394
|
-
const scopeRows = scopes.flatMap((s) => [react.default.createElement("div", {
|
|
36395
|
-
key: "scope" + s.name,
|
|
36396
|
-
className: "edrv-dapscope"
|
|
36397
|
-
}, s.name), ...flattenVariableRows(varCache.get(s.ref) ?? [], expanded, varCache, { maxRows: 800 }).map(renderVarRow)]);
|
|
36398
|
-
const varRows = scopes.length ? scopeRows : [react.default.createElement("div", {
|
|
36399
|
-
key: "e",
|
|
36400
|
-
className: "edrv-dapempty"
|
|
36401
|
-
}, "暂停时显示变量")];
|
|
36402
|
-
const watchRows = watches.flatMap((expr) => {
|
|
36403
|
-
const hit = watchHits[expr];
|
|
36404
|
-
const rows = [react.default.createElement("div", {
|
|
36405
|
-
key: expr,
|
|
36406
|
-
className: "edrv-dapwatch"
|
|
36407
|
-
}, react.default.createElement("button", {
|
|
36408
|
-
className: "edrv-dapwatch-x",
|
|
36409
|
-
title: "移除监视",
|
|
36410
|
-
onClick: () => removeWatch(expr)
|
|
36411
|
-
}, "×"), hit?.ref > 0 ? react.default.createElement("button", {
|
|
36412
|
-
className: "edrv-dapvar-tw",
|
|
36413
|
-
title: expanded.has(hit.ref) ? "折叠 table" : "展开 table",
|
|
36414
|
-
onClick: () => {
|
|
36415
|
-
expandVar(hit.ref);
|
|
36416
|
-
}
|
|
36417
|
-
}, expanded.has(hit.ref) ? "▾" : "▸") : react.default.createElement("span", { className: "edrv-dapvar-tw" }, ""), react.default.createElement("span", {
|
|
36418
|
-
className: "edrv-dapvar-name",
|
|
36419
|
-
title: expr
|
|
36420
|
-
}, expr), react.default.createElement("span", {
|
|
36421
|
-
className: "edrv-dapvar-val",
|
|
36422
|
-
title: hit?.result || ""
|
|
36423
|
-
}, hit ? "= " + hit.result : "= 不可用"), hit?.type ? react.default.createElement("span", { className: "edrv-dapvar-type" }, hit.type) : null)];
|
|
36424
|
-
if (hit?.ref > 0 && expanded.has(hit.ref)) rows.push(...flattenVariableRows(varCache.get(hit.ref) ?? [], expanded, varCache, { maxRows: 500 }).map(renderVarRow));
|
|
36425
|
-
return rows;
|
|
36426
|
-
});
|
|
36427
|
-
const watchSection = section("监视", "watch", "watch", [segAct("watch-add", "+", "新增监视表达式", focusWatchInput), segAct("watch-clear", "🗑", "移除全部监视", clearWatches)], [...watchRows, react.default.createElement("div", {
|
|
36428
|
-
key: "add",
|
|
36429
|
-
className: "edrv-dapwatch-add"
|
|
36430
|
-
}, react.default.createElement("input", {
|
|
36431
|
-
ref: watchInputRef,
|
|
36432
|
-
className: "edrv-daprepl-in",
|
|
36433
|
-
placeholder: "新增监视表达式,回车确认",
|
|
36434
|
-
value: watchDraft,
|
|
36435
|
-
onChange: (e) => setWatchDraft(e.target.value),
|
|
36436
|
-
onKeyDown: (e) => {
|
|
36437
|
-
if (e.key === "Enter") addWatch();
|
|
36438
|
-
}
|
|
36439
|
-
}))]);
|
|
36440
|
-
const bpSectionChildren = bpRows.length ? bpRows.flatMap((row) => {
|
|
36441
|
-
const base = row.path.split("/").pop() || row.path;
|
|
36442
|
-
const dir = row.path.includes("/") ? row.path.slice(0, row.path.lastIndexOf("/")) : "";
|
|
36443
|
-
const ack = dapStore.ackOf(row.path, row.entry.line);
|
|
36444
|
-
const unverified = Boolean(ack && !ack.verified);
|
|
36445
|
-
const editing = bpEdit && bpEdit.path === row.path && bpEdit.line === row.entry.line;
|
|
36446
|
-
const rows = [react.default.createElement("div", {
|
|
36447
|
-
key: row.path + ":" + row.entry.line,
|
|
36448
|
-
className: "edrv-dapbp",
|
|
36449
|
-
title: "跳转到 " + row.path + ":" + row.entry.line + (unverified && ack.message ? "(" + ack.message + ")" : ""),
|
|
36450
|
-
onClick: () => jumpBp(row),
|
|
36451
|
-
onContextMenu: (e) => {
|
|
36452
|
-
e.preventDefault();
|
|
36453
|
-
setBpMenu({
|
|
36454
|
-
x: e.clientX,
|
|
36455
|
-
y: e.clientY,
|
|
36456
|
-
row
|
|
36457
|
-
});
|
|
36458
|
-
}
|
|
36459
|
-
}, react.default.createElement("input", {
|
|
36460
|
-
type: "checkbox",
|
|
36461
|
-
checked: row.entry.enabled,
|
|
36462
|
-
title: row.entry.enabled ? "禁用断点" : "启用断点",
|
|
36463
|
-
onChange: () => toggleBpEnabled(row),
|
|
36464
|
-
onClick: (e) => e.stopPropagation()
|
|
36465
|
-
}), react.default.createElement("span", {
|
|
36466
|
-
className: "edrv-dapbp-dot" + (row.entry.enabled ? "" : " off") + (unverified ? " unverified" : ""),
|
|
36467
|
-
title: unverified && ack.message ? ack.message : void 0
|
|
36468
|
-
}), react.default.createElement("button", {
|
|
36469
|
-
className: "edrv-dapbp-file",
|
|
36470
|
-
title: row.path
|
|
36471
|
-
}, base), react.default.createElement("span", {
|
|
36472
|
-
className: "edrv-dapbp-dir",
|
|
36473
|
-
title: dir
|
|
36474
|
-
}, dir), react.default.createElement("span", { className: "edrv-dapbp-line" }, row.entry.line), react.default.createElement("button", {
|
|
36475
|
-
className: "edrv-dapbp-x",
|
|
36476
|
-
title: "删除断点",
|
|
36477
|
-
onClick: (e) => {
|
|
36478
|
-
e.stopPropagation();
|
|
36479
|
-
removeBp(row);
|
|
36480
|
-
}
|
|
36481
|
-
}, "×"))];
|
|
36482
|
-
if (editing) {
|
|
36483
|
-
const fieldKey = fieldOfMode(bpEdit.mode);
|
|
36484
|
-
const modeItem = MODES.find((m) => m.mode === bpEdit.mode) ?? MODES[0];
|
|
36485
|
-
rows.push(react.default.createElement("div", {
|
|
36486
|
-
key: row.path + ":" + row.entry.line + ":edit",
|
|
36487
|
-
className: "edrv-dapbp-edit"
|
|
36488
|
-
}, react.default.createElement("select", {
|
|
36489
|
-
className: "edrv-bpwidget-mode",
|
|
36490
|
-
title: "断点类型",
|
|
36491
|
-
value: String(bpEdit.mode),
|
|
36492
|
-
onChange: (e) => setBpEdit({
|
|
36493
|
-
...bpEdit,
|
|
36494
|
-
mode: Number(e.target.value)
|
|
36495
|
-
})
|
|
36496
|
-
}, MODES.filter((m) => m.mode !== BP_MODE.WAIT).map((m) => react.default.createElement("option", {
|
|
36497
|
-
key: m.mode,
|
|
36498
|
-
value: String(m.mode)
|
|
36499
|
-
}, m.label))), react.default.createElement("input", {
|
|
36500
|
-
className: "edrv-bpwidget-input",
|
|
36501
|
-
autoFocus: true,
|
|
36502
|
-
spellCheck: false,
|
|
36503
|
-
placeholder: modeItem?.placeholder ?? "",
|
|
36504
|
-
hidden: fieldKey === null,
|
|
36505
|
-
value: fieldKey ? bpEdit.drafts[fieldKey] || "" : "",
|
|
36506
|
-
onChange: (e) => setBpEdit({
|
|
36507
|
-
...bpEdit,
|
|
36508
|
-
drafts: {
|
|
36509
|
-
...bpEdit.drafts,
|
|
36510
|
-
[fieldKey]: e.target.value
|
|
36511
|
-
}
|
|
36512
|
-
}),
|
|
36513
|
-
onKeyDown: (e) => {
|
|
36514
|
-
if (e.key === "Enter") {
|
|
36515
|
-
e.preventDefault();
|
|
36516
|
-
commitBpEdit();
|
|
36517
|
-
return;
|
|
36518
|
-
}
|
|
36519
|
-
if (e.key === "Escape") {
|
|
36520
|
-
e.preventDefault();
|
|
36521
|
-
setBpEdit(null);
|
|
36522
|
-
}
|
|
36523
|
-
}
|
|
36524
|
-
})));
|
|
36525
|
-
}
|
|
36526
|
-
return rows;
|
|
36527
|
-
}) : [react.default.createElement("div", {
|
|
36528
|
-
key: "e",
|
|
36529
|
-
className: "edrv-dapempty"
|
|
36530
|
-
}, "在编辑器行首点击或 F9 添加断点")];
|
|
36531
|
-
const bpSection = section("断点", "breakpoints", "breakpoints", [
|
|
36532
|
-
segAct("bp-new", "新建断点 ▾", "添加条件断点 / 记录点(落在当前编辑器光标行)", (e) => setNewBpMenu({
|
|
36533
|
-
x: e.clientX,
|
|
36534
|
-
y: e.clientY
|
|
36535
|
-
})),
|
|
36536
|
-
bpRows.length ? segAct("bp-toggle", allBpEnabled ? "⊘" : "◉", allBpEnabled ? "禁用所有断点" : "启用所有断点", () => dapStore.setAllBpEnabled(scope, cwd, !allBpEnabled)) : null,
|
|
36537
|
-
bpRows.length ? segAct("bp-clear", "🗑", "删除所有断点", removeAllBps) : null
|
|
36538
|
-
], bpSectionChildren);
|
|
36539
|
-
const outputRows = dapStore.outputs().slice(-60).map((ev, idx) => react.default.createElement("div", {
|
|
36540
|
-
key: ev.seq + ":" + idx,
|
|
36541
|
-
className: "edrv-daprepl-line out"
|
|
36542
|
-
}, ev.text?.trimEnd() || ""));
|
|
36543
|
-
const configReady = snap.configs.some((c) => snap.adapters.length === 0 || snap.adapters.find((a) => a.type === c.type)?.available === true);
|
|
36544
|
-
const toolbarBtn = (key, label, title, disabled, onClick) => react.default.createElement("button", {
|
|
36545
|
-
key,
|
|
36546
|
-
className: "edrv-dappanel-run",
|
|
36547
|
-
title,
|
|
36548
|
-
disabled: disabled === true,
|
|
36549
|
-
onClick
|
|
36550
|
-
}, label);
|
|
36551
|
-
const bpMenuEl = bpMenu ? react.default.createElement(ContextMenu, {
|
|
36552
|
-
x: bpMenu.x,
|
|
36553
|
-
y: bpMenu.y,
|
|
36554
|
-
onClose: () => setBpMenu(null),
|
|
36555
|
-
entries: buildBpRowMenu({ enabled: bpMenu.row.entry.enabled }).map((item) => ({
|
|
36556
|
-
id: item.id,
|
|
36557
|
-
label: item.label,
|
|
36558
|
-
separator: item.separator,
|
|
36559
|
-
onClick: () => runBpRowAction(item.id, bpMenu.row)
|
|
36560
|
-
}))
|
|
36561
|
-
}) : null;
|
|
36562
|
-
const newBpMenuEl = newBpMenu ? react.default.createElement(ContextMenu, {
|
|
36563
|
-
x: newBpMenu.x,
|
|
36564
|
-
y: newBpMenu.y,
|
|
36565
|
-
onClose: () => setNewBpMenu(null),
|
|
36566
|
-
entries: [{
|
|
36567
|
-
id: "add-conditional",
|
|
36568
|
-
label: "添加条件断点...",
|
|
36569
|
-
onClick: () => addBpAtCursor(BP_MODE.EXPRESSION)
|
|
36570
|
-
}, {
|
|
36571
|
-
id: "add-logpoint",
|
|
36572
|
-
label: "添加记录点...",
|
|
36573
|
-
onClick: () => addBpAtCursor(BP_MODE.LOG_MESSAGE)
|
|
36574
|
-
}]
|
|
36575
|
-
}) : null;
|
|
36576
|
-
return react.default.createElement("div", { className: "edrv-dappanel" }, react.default.createElement("div", { className: "edrv-dappanel-head" }, ...snap.candidates ? [
|
|
36577
|
-
react.default.createElement("select", {
|
|
36578
|
-
key: "pick",
|
|
36579
|
-
className: "edrv-dappanel-cfg-select",
|
|
36580
|
-
title: "选择附加目标进程(processName 命中多个候选)",
|
|
36581
|
-
value: String(pickPid || snap.candidates[0]?.pid || ""),
|
|
36582
|
-
onChange: (e) => setPickPid(Number(e.target.value))
|
|
36583
|
-
}, snap.candidates.map((it) => react.default.createElement("option", {
|
|
36584
|
-
key: it.pid,
|
|
36585
|
-
value: String(it.pid)
|
|
36586
|
-
}, it.pid + " : " + it.name + (it.title ? " — " + it.title.slice(0, 44) : "")))),
|
|
36587
|
-
toolbarBtn("pick-ok", "✓", "附加到选中进程", false, () => dapStore.confirmPick(pickPid || snap.candidates[0]?.pid || 0)),
|
|
36588
|
-
toolbarBtn("pick-cancel", "✕", "取消", false, () => dapStore.cancelPick())
|
|
36589
|
-
] : [react.default.createElement("select", {
|
|
36590
|
-
key: "cfg",
|
|
36591
|
-
className: "edrv-dappanel-cfg-select",
|
|
36592
|
-
title: "调试配置(读取工作区 .dsh/launch.json;适配器由扩展清单 contributes.debuggers 提供)",
|
|
36593
|
-
value: snap.selectedConfig ?? "",
|
|
36594
|
-
onChange: (e) => dapStore.selectConfig(e.target.value)
|
|
36595
|
-
}, snap.configs.length ? snap.configs.map((c) => {
|
|
36596
|
-
const adapter = snap.adapters.find((a) => a.type === c.type);
|
|
36597
|
-
const ready = snap.adapters.length === 0 || adapter?.available === true;
|
|
36598
|
-
const suffix = ready ? "" : "(" + (adapter?.reason || "未发现该类型适配器") + ")";
|
|
36599
|
-
return react.default.createElement("option", {
|
|
36600
|
-
key: c.name,
|
|
36601
|
-
value: c.name,
|
|
36602
|
-
disabled: !ready
|
|
36603
|
-
}, c.name + suffix);
|
|
36604
|
-
}) : react.default.createElement("option", { value: "" }, "无可用调试配置"))], toolbarBtn("run", "▶", paused ? "继续(F5)" : "启动调试(F5)", !paused && (!configReady || dapActive), () => dapStore.startOrContinue()), toolbarBtn("cfg-gear", "⚙", "打开/新建调试配置(" + LAUNCH_REL + ")", !ctx?.cwd, () => {
|
|
36605
|
-
openLaunchConfig();
|
|
36606
|
-
}), toolbarBtn("pause", "⏸", "暂停", !dapActive, () => {
|
|
36607
|
-
dapStore.action("pause");
|
|
36608
|
-
}), toolbarBtn("next", "⤵", "单步跳过(F10)", !paused, () => {
|
|
36609
|
-
dapStore.action("next");
|
|
36610
|
-
}), toolbarBtn("stepin", "↓", "单步步入(F11)", !paused, () => {
|
|
36611
|
-
dapStore.action("stepIn");
|
|
36612
|
-
}), toolbarBtn("stepout", "↑", "单步步出(Shift+F11)", !paused, () => {
|
|
36613
|
-
dapStore.action("stepOut");
|
|
36614
|
-
}), toolbarBtn("stop", "⏹", "停止(Shift+F5)", !dapActive, () => {
|
|
36615
|
-
dapStore.stop();
|
|
36616
|
-
}), react.default.createElement("span", { className: "edrv-dappanel-phase" + (paused ? " paused" : "") }, PHASE_LABEL[snap.phase] || snap.phase), snap.pid ? react.default.createElement("span", { className: "edrv-dappanel-pid" }, "pid " + snap.pid) : null, react.default.createElement("span", { style: { flex: 1 } }), snap.error ? react.default.createElement("span", {
|
|
36617
|
-
className: "edrv-dappanel-err",
|
|
36618
|
-
title: snap.error
|
|
36619
|
-
}, "⚠") : null), section("变量", "variables", "variables", [segAct("var-reload", "⟳", "刷新变量", () => setReloadTick((t) => t + 1))], varRows), sash("variables", "watch"), watchSection, sash("watch", "stack"), section("调用堆栈", "stack", "stack", [], stackRows), sash("stack", "breakpoints"), bpSection, react.default.createElement("div", { className: "edrv-daprepl" }, react.default.createElement("div", { className: "edrv-dapseg-head" }, "调试控制台"), react.default.createElement("div", { className: "edrv-daprepl-log" }, ...replLog.map((row, idx) => react.default.createElement("div", {
|
|
36620
|
-
key: idx,
|
|
36621
|
-
className: "edrv-daprepl-line"
|
|
36622
|
-
}, react.default.createElement("span", { className: "edrv-dapvar-name" }, row.expr), react.default.createElement("span", { className: "edrv-dapvar-val" }, "= " + row.result))), ...outputRows), react.default.createElement("input", {
|
|
36623
|
-
className: "edrv-daprepl-in",
|
|
36624
|
-
placeholder: paused ? "输入表达式求值,回车执行(如 self.itemId)" : "暂停时才可求值",
|
|
36625
|
-
disabled: !paused,
|
|
36626
|
-
value: replDraft,
|
|
36627
|
-
onChange: (e) => setReplDraft(e.target.value),
|
|
36628
|
-
onKeyDown: (e) => {
|
|
36629
|
-
if (e.key === "Enter") runRepl();
|
|
36630
|
-
}
|
|
36631
|
-
})), bpMenuEl, newBpMenuEl);
|
|
36632
|
-
}
|
|
36633
|
-
//#endregion
|
|
36634
36284
|
//#region src/client/sidebar/panels/index.ts
|
|
36635
36285
|
/**
|
|
36636
36286
|
* dsh-vscode-mode client — 侧边栏面板定义(文件管理 + 搜索 + 规则 + SVN 变更)。
|
|
@@ -36678,7 +36328,11 @@ window.__ModuleLoader__.load({
|
|
|
36678
36328
|
title: "规则",
|
|
36679
36329
|
icon: IconListPenOutline16,
|
|
36680
36330
|
order: 20,
|
|
36681
|
-
render: (ctx) => react.default.createElement(
|
|
36331
|
+
render: (ctx) => react.default.createElement(LazyPanel, {
|
|
36332
|
+
key: "rules",
|
|
36333
|
+
panel: "rules",
|
|
36334
|
+
ctx
|
|
36335
|
+
})
|
|
36682
36336
|
};
|
|
36683
36337
|
}
|
|
36684
36338
|
/**
|
|
@@ -36731,7 +36385,11 @@ window.__ModuleLoader__.load({
|
|
|
36731
36385
|
icon: "🐞",
|
|
36732
36386
|
order: 22,
|
|
36733
36387
|
badge: () => dapStore.getSnapshot().phase === "paused" ? 1 : null,
|
|
36734
|
-
render: (ctx) => react.default.createElement(
|
|
36388
|
+
render: (ctx) => react.default.createElement(LazyPanel, {
|
|
36389
|
+
key: "debug",
|
|
36390
|
+
panel: "debug",
|
|
36391
|
+
ctx
|
|
36392
|
+
})
|
|
36735
36393
|
};
|
|
36736
36394
|
}
|
|
36737
36395
|
//#endregion
|
|
@@ -38517,6 +38175,16 @@ window.__ModuleLoader__.load({
|
|
|
38517
38175
|
* conversation.input.dock。
|
|
38518
38176
|
* 作者 ddj 2026-08-20
|
|
38519
38177
|
*/
|
|
38178
|
+
/** Root-owned live objects shared with independent panel chunks; no copied stores or transport state. */
|
|
38179
|
+
const __lazyShared = {
|
|
38180
|
+
rpc,
|
|
38181
|
+
dbg,
|
|
38182
|
+
dapStore,
|
|
38183
|
+
BP_MODE,
|
|
38184
|
+
MODES,
|
|
38185
|
+
fieldOfMode,
|
|
38186
|
+
ContextMenu
|
|
38187
|
+
};
|
|
38520
38188
|
const inject = [
|
|
38521
38189
|
"slots",
|
|
38522
38190
|
"timer",
|
|
@@ -39069,6 +38737,34 @@ window.__ModuleLoader__.load({
|
|
|
39069
38737
|
}, 2e3);
|
|
39070
38738
|
};
|
|
39071
38739
|
retrySideService();
|
|
38740
|
+
ctx.effect(() => {
|
|
38741
|
+
const store = mcpSessionState();
|
|
38742
|
+
/** @author ddj 2026年10月20号 @param props Official root row identity. @returns Leading marker with shared polling. */
|
|
38743
|
+
const renderIcon = (props) => react.default.createElement(McpSessionIcon, {
|
|
38744
|
+
...props,
|
|
38745
|
+
store
|
|
38746
|
+
});
|
|
38747
|
+
/** @author ddj 2026年10月20号 @param props Official hover identity. @returns Project-only initialization details. */
|
|
38748
|
+
const renderDetail = (props) => react.default.createElement(McpSessionDetail, {
|
|
38749
|
+
...props,
|
|
38750
|
+
store
|
|
38751
|
+
});
|
|
38752
|
+
const leading = registerSlotSafely(ctx, {
|
|
38753
|
+
name: "sidebar.session.row.leading",
|
|
38754
|
+
id: "edrv-mcp-status",
|
|
38755
|
+
order: 20
|
|
38756
|
+
}, renderIcon);
|
|
38757
|
+
const hover = registerSlotSafely(ctx, {
|
|
38758
|
+
name: "sidebar.session.row.hover",
|
|
38759
|
+
id: "edrv-mcp-detail",
|
|
38760
|
+
order: 20
|
|
38761
|
+
}, renderDetail);
|
|
38762
|
+
return () => {
|
|
38763
|
+
leading?.();
|
|
38764
|
+
hover?.();
|
|
38765
|
+
store.dispose();
|
|
38766
|
+
};
|
|
38767
|
+
}, "vscode-mode: session MCP status");
|
|
39072
38768
|
registerSlotSafely(ctx, {
|
|
39073
38769
|
name: "conversation.input.dock",
|
|
39074
38770
|
id: "edrv-diff-dock",
|
|
@@ -39095,6 +38791,7 @@ window.__ModuleLoader__.load({
|
|
|
39095
38791
|
label: () => t("slot.settings")
|
|
39096
38792
|
}, () => react.default.createElement(SettingsContext.Provider, { value: bridge.scope() }, react.default.createElement(McpSettings, {
|
|
39097
38793
|
openerRegistry: registry,
|
|
38794
|
+
getMcpSession: () => readSessionScope(ctx).sessionId,
|
|
39098
38795
|
compatSummary,
|
|
39099
38796
|
getShortcuts: () => detectShortcuts(ctx)
|
|
39100
38797
|
})));
|
|
@@ -39104,6 +38801,7 @@ window.__ModuleLoader__.load({
|
|
|
39104
38801
|
order: 40
|
|
39105
38802
|
}, (props) => react.default.createElement(PluginDetailSection, Object.assign({}, props)));
|
|
39106
38803
|
ctx.effect(() => () => {
|
|
38804
|
+
setPanelImport();
|
|
39107
38805
|
try {
|
|
39108
38806
|
disposeSnippets();
|
|
39109
38807
|
} catch {}
|
|
@@ -39131,8 +38829,11 @@ window.__ModuleLoader__.load({
|
|
|
39131
38829
|
reportUiStateEvent("客户端装配完成:水合" + (uiStateSettled() ? "已结束" : "仍在进行(界面稍后重跑恢复)"));
|
|
39132
38830
|
}
|
|
39133
38831
|
//#endregion
|
|
38832
|
+
exports.__lazyShared = __lazyShared;
|
|
38833
|
+
exports.__setPanelImport = setPanelImport;
|
|
39134
38834
|
exports.apply = apply;
|
|
39135
38835
|
exports.inject = inject;
|
|
38836
|
+
module.exports.__setPanelImport(require.async?.bind(require));
|
|
39136
38837
|
return module.exports;
|
|
39137
38838
|
}
|
|
39138
38839
|
});
|