dsh-vscode-mode 0.1.63 → 0.3.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/lib/client.js CHANGED
@@ -28,11 +28,11 @@ window.__ModuleLoader__.load({
28
28
  //#endregion
29
29
  let react = require("react");
30
30
  react = __toESM(react, 1);
31
+ let react_dom = require("react-dom");
31
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
- let react_dom = require("react-dom");
34
34
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
35
- const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 主题:跟随官方 --dsw-alias-* 令牌(DSH 明暗与第三方皮肤生效)。\n 本作用域不再重定义官方令牌(旧实现曾在此硬覆盖浅色盘,导致插件不跟随 DSH 主题);\n 旧环境/缺令牌场景由每条声明的 var(--dsw-alias-*, 回落值) 兜底。\n @author ddj 2026-09-10 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; }\n\n[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-tab .edrv-tab-star { color: var(--dsw-alias-brand-primary, #4f8cff); font-size: 13px; font-weight: 700; line-height: 1; flex-shrink: 0; } /* 未保存修改星号 @author ddj 2026年09月09号 */\n[data-edrv-view] .edrv-tab .edrv-tab-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 4px; color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.12)); color: var(--dsw-alias-label-primary, #eee); }\n[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }\n[data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }\n[data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }\n[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }\n[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }\n[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress-message { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress { flex: 0 0 auto; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-lsp-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-lsp-status-dot.busy { background: var(--dsw-alias-state-warn-primary, #d97706); animation: edrv-lsp-pulse 1.2s ease-in-out infinite; }\n@keyframes edrv-lsp-pulse { 50% { opacity: .45; } }\n/* Ctrl+hover 可导航标识符下划线提示(绑定 Monaco inlineClassName 装饰) */\n[data-edrv-view] .edrv-nav-underline { text-decoration: underline; text-decoration-color: var(--dsw-alias-interactive-primary, #4fc1ff); text-underline-offset: 1px; cursor: pointer; }\n[data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-chip-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-chip-btn:disabled:hover { background: transparent; }\n[data-edrv-view] .edrv-mn-add-line { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }\n[data-edrv-view] .edrv-mn-gutter-add { color: var(--dsw-alias-state-success-primary, #0f9d58) !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-mn-gutter-add::before { content: \"+\"; }\n[data-edrv-view] .edrv-del-zone { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); border-top: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); border-bottom: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); box-shadow: inset 0 0 0 1px var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); }\n[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: var(--dsw-alias-state-error-primary, #c0392b); }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }\n[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }\n[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n[data-edrv-view] .edrv-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }\n[data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }\n[data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); }\n[data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-pdf-page { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; min-width: 52px; text-align: center; }\n[data-edrv-view] .edrv-pdf-zoom { min-width: 26px; text-align: center; padding: 3px 7px; }\n[data-edrv-view] .edrv-pdf-sep { width: 1px; height: 16px; background: var(--dsw-alias-border-l1, #e0e6e8); margin: 0 2px; }\n[data-edrv-view] .edrv-pdf-mode-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-brand-primary, #0f9d58); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-pdf-dirty { color: var(--dsw-alias-state-warn-primary, #d97706); border-color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-pdf-stage { position: absolute; top: 34px; left: 0; right: 0; bottom: 0; overflow: auto; }\n[data-edrv-view] .edrv-pdf-stage .edrv-pdf-viewer { margin: 0 auto; }\n[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }\n[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }\n[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }\n[data-edrv-view] .edrv-pill-keep { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-state-success-primary, #0f7a45); border: 1px solid var(--dsw-alias-state-success-primary, rgba(15,157,88,.4)); }\n[data-edrv-view] .edrv-pill-undo { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); color: var(--dsw-alias-state-error-primary, #c0392b); border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-pill-ghost { background: var(--dsw-alias-bg-layer-1, #ffffff); color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 编辑区 / Tab 右键菜单(添加文件/选中内容到对话) */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 200px; max-width: 320px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-item:disabled,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled { color: var(--dsw-alias-label-disabled, #b0b7c0); cursor: default; background: transparent; }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-primary, rgba(217,119,6,.5)); cursor: default; }\n[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }\n[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n/* 编辑区 hover 差异块的 Keep/Undo 浮层 */\n[data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }\n[data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }\n[data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }\n[data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }\n[data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }\n[data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }\n[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }\n[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }\n[data-edrv-view] .monaco-editor { overflow: hidden !important; }\n[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }\n[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }\n[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }\n[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }\n[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }\n[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-progress-value { transition: none; }\n}\n\n/* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */\n[data-edrv-diff-dock-shell] {\n box-sizing: border-box;\n width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n margin: 0 auto;\n padding: 0;\n flex: none;\n border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);\n border-radius: 12px;\n background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));\n box-shadow: none;\n overflow: hidden;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock {\n box-sizing: border-box;\n position: relative;\n left: auto;\n right: auto;\n bottom: auto;\n z-index: auto;\n width: 100%;\n max-width: none;\n margin: 0;\n display: flex;\n flex-direction: column;\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: visible;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-nav,\n[data-edrv-diff-dock-shell] .edrv-diffbar-files,\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }\n[data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }\n@media (max-width: 620px) {\n [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }\n [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }\n}\n\n/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\n[data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col > .edrv-pathbar,\n[data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }\n[data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 1; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }\n[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }\n[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; }\n/* 树行图标:官方原语(文件夹/文件类型),尺寸与行高对齐 @author ddj 2026-09-10 */\n[data-edrv-view] .edrv-tree-icon { flex-shrink: 0; display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active .edrv-tree-icon { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-context { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); }\n[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }\n[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* 大纲面板:kind 字形 / detail / 行号 */\n[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== 侧栏形态(betterSidebar 面板内编辑区) ===== */\n[data-edrv-view][data-edrv-layout='side'] { box-sizing: border-box; height: 100%; }\n\n/* ===== 侧边栏引导条(旧页签形态、未装 betterSidebar 时) ===== */\n[data-edrv-view] .edrv-side-hint { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; padding: 6px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-side-hint-text { white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-cmd { font-size: 11px; padding: 2px 6px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #ffffff); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-close { margin-left: auto; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-hint-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== search panel (sidebar, VSCode search view) ===== */\n[data-edrv-view] .edrv-search-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-search-qrow { display: flex; gap: 4px; }\n[data-edrv-view] .edrv-search-input { flex: 1; min-width: 0; height: 26px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-opts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-search-opt { min-width: 24px; height: 22px; padding: 0 6px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); cursor: pointer; }\n[data-edrv-view] .edrv-search-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-opt.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-border-l2, #d5dde3); }\n[data-edrv-view] .edrv-search-spacer { flex: 1; }\n[data-edrv-view] .edrv-search-summary { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 2px 4px; }\n[data-edrv-view] .edrv-search-results { flex: 1; min-height: 0; overflow: auto; font-size: 12px; }\n[data-edrv-view] .edrv-search-group { margin-bottom: 2px; }\n[data-edrv-view] .edrv-search-file { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-search-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-search-fname { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-search-fdir { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-search-fcount { flex-shrink: 0; min-width: 16px; height: 15px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 15px; text-align: center; }\n[data-edrv-view] .edrv-search-line { display: flex; gap: 6px; padding: 1px 4px 1px 16px; border-radius: 4px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-search-line.alt { background: var(--dsw-alias-bg-layer-1, #ffffff); }\n[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }\n[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: var(--dsw-alias-label-primary-inverted, #1f2933); border-radius: 2px; padding: 0 1px; }\n[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-warn-primary, rgba(240,173,78,.45)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(240,173,78,.12)); color: var(--dsw-alias-state-warn-primary, #b8860b); font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n/* ===== rules panel (sidebar, Codebuddy rules view) ===== */\n[data-edrv-view] .edrv-rules-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-rules-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-tab { height: 24px; padding: 0 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-tab.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-rules-new { margin-left: auto; height: 24px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-new:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-ws-line { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 auto; user-select: none; }\n[data-edrv-view] .edrv-rules-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 0 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-list { flex: 1; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-rules-row { display: flex; align-items: center; gap: 4px; padding: 6px 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; }\n[data-edrv-view] .edrv-rules-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-row.off .edrv-rules-main { opacity: .5; }\n[data-edrv-view] .edrv-rules-main { flex: 1; min-width: 0; cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-rules-line1 { display: flex; align-items: center; gap: 6px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sub { display: flex; align-items: center; gap: 5px; margin-top: 3px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sublabel { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }\n[data-edrv-view] .edrv-rules-type-always { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.15)); color: var(--dsw-alias-state-success-primary, #0b7c46); }\n[data-edrv-view] .edrv-rules-type-auto { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(79,140,255,.16)); color: var(--dsw-alias-link, #3367c9); }\n[data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }\n[data-edrv-view] .edrv-rules-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }\n[data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); display: inline-flex; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: var(--dsw-alias-border-l2, #b9c4cc); cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }\n[data-edrv-view] .edrv-rules-switch.on { background: var(--dsw-alias-brand-primary, #7c5cff); }\n[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--dsw-alias-bg-layer-1, #fff); transition: left .15s; }\n[data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }\n[data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }\n[data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }\n[data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-editor-ctl { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-ctl-label { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select { height: 24px; padding: 0 4px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-desc-input { flex: 1; min-width: 0; }\n[data-edrv-view] .edrv-rules-input { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-body { flex: 1; min-height: 0; resize: none; padding: 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; line-height: 1.5; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }\n\n\n\n";
35
+ const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 主题:跟随官方 --dsw-alias-* 令牌(DSH 明暗与第三方皮肤生效)。\n 本作用域不再重定义官方令牌(旧实现曾在此硬覆盖浅色盘,导致插件不跟随 DSH 主题);\n 旧环境/缺令牌场景由每条声明的 var(--dsw-alias-*, 回落值) 兜底。\n @author ddj 2026-09-10 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; }\n\n[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-tab .edrv-tab-star { color: var(--dsw-alias-brand-primary, #4f8cff); font-size: 13px; font-weight: 700; line-height: 1; flex-shrink: 0; } /* 未保存修改星号 @author ddj 2026年09月09号 */\n[data-edrv-view] .edrv-tab .edrv-tab-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 4px; color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.12)); color: var(--dsw-alias-label-primary, #eee); }\n[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }\n[data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }\n[data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }\n[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }\n[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }\n[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress-message { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress { flex: 0 0 auto; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-lsp-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-lsp-status-dot.busy { background: var(--dsw-alias-state-warn-primary, #d97706); animation: edrv-lsp-pulse 1.2s ease-in-out infinite; }\n@keyframes edrv-lsp-pulse { 50% { opacity: .45; } }\n/* Ctrl+hover 可导航标识符下划线提示(绑定 Monaco inlineClassName 装饰) */\n[data-edrv-view] .edrv-nav-underline { text-decoration: underline; text-decoration-color: var(--dsw-alias-interactive-primary, #4fc1ff); text-underline-offset: 1px; cursor: pointer; }\n[data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-chip-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-chip-btn:disabled:hover { background: transparent; }\n[data-edrv-view] .edrv-mn-add-line { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }\n[data-edrv-view] .edrv-mn-gutter-add { color: var(--dsw-alias-state-success-primary, #0f9d58) !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-mn-gutter-add::before { content: \"+\"; }\n[data-edrv-view] .edrv-del-zone { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); border-top: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); border-bottom: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.35)); box-shadow: inset 0 0 0 1px var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); }\n[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: var(--dsw-alias-state-error-primary, #c0392b); }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }\n[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }\n[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n[data-edrv-view] .edrv-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }\n[data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }\n[data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); }\n[data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-pdf-page { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; min-width: 52px; text-align: center; }\n[data-edrv-view] .edrv-pdf-zoom { min-width: 26px; text-align: center; padding: 3px 7px; }\n[data-edrv-view] .edrv-pdf-sep { width: 1px; height: 16px; background: var(--dsw-alias-border-l1, #e0e6e8); margin: 0 2px; }\n[data-edrv-view] .edrv-pdf-mode-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-brand-primary, #0f9d58); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-pdf-dirty { color: var(--dsw-alias-state-warn-primary, #d97706); border-color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-pdf-stage { position: absolute; top: 34px; left: 0; right: 0; bottom: 0; overflow: auto; }\n[data-edrv-view] .edrv-pdf-stage .edrv-pdf-viewer { margin: 0 auto; }\n[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }\n[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }\n[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }\n[data-edrv-view] .edrv-pill-keep { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-state-success-primary, #0f7a45); border: 1px solid var(--dsw-alias-state-success-primary, rgba(15,157,88,.4)); }\n[data-edrv-view] .edrv-pill-undo { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); color: var(--dsw-alias-state-error-primary, #c0392b); border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-pill-ghost { background: var(--dsw-alias-bg-layer-1, #ffffff); color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 编辑区 / Tab 右键菜单(添加文件/选中内容到对话) */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 200px; max-width: 320px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-item:disabled,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled { color: var(--dsw-alias-label-disabled, #b0b7c0); cursor: default; background: transparent; }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-primary, rgba(217,119,6,.5)); cursor: default; }\n[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }\n[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n/* 编辑区 hover 差异块的 Keep/Undo 浮层 */\n[data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }\n[data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }\n[data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }\n[data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }\n[data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }\n[data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }\n[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }\n[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }\n[data-edrv-view] .monaco-editor { overflow: hidden !important; }\n[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }\n[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }\n[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }\n[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }\n[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }\n[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-progress-value { transition: none; }\n}\n\n/* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */\n[data-edrv-diff-dock-shell] {\n box-sizing: border-box;\n width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n margin: 0 auto;\n padding: 0;\n flex: none;\n border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);\n border-radius: 12px;\n background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));\n box-shadow: none;\n overflow: hidden;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock {\n box-sizing: border-box;\n position: relative;\n left: auto;\n right: auto;\n bottom: auto;\n z-index: auto;\n width: 100%;\n max-width: none;\n margin: 0;\n display: flex;\n flex-direction: column;\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: visible;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-nav,\n[data-edrv-diff-dock-shell] .edrv-diffbar-files,\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }\n[data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }\n@media (max-width: 620px) {\n [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }\n [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }\n}\n\n/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\n[data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col > .edrv-pathbar,\n[data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }\n[data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 1; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }\n[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }\n[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; }\n/* 树行图标:官方原语(文件夹/文件类型),尺寸与行高对齐 @author ddj 2026-09-10 */\n[data-edrv-view] .edrv-tree-icon { flex-shrink: 0; display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active .edrv-tree-icon { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-context { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); }\n[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }\n[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* 大纲面板:kind 字形 / detail / 行号 */\n[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== 侧栏形态(betterSidebar 面板内编辑区) ===== */\n[data-edrv-view][data-edrv-layout='side'] { box-sizing: border-box; height: 100%; }\n\n/* ===== 侧边栏引导条(旧页签形态、未装 betterSidebar 时) ===== */\n[data-edrv-view] .edrv-side-hint { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; padding: 6px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-side-hint-text { white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-cmd { font-size: 11px; padding: 2px 6px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #ffffff); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-close { margin-left: auto; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-hint-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== search panel (sidebar, VSCode search view) ===== */\n[data-edrv-view] .edrv-search-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-search-qrow { display: flex; gap: 4px; }\n[data-edrv-view] .edrv-search-input { flex: 1; min-width: 0; height: 26px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-opts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-search-opt { min-width: 24px; height: 22px; padding: 0 6px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); cursor: pointer; }\n[data-edrv-view] .edrv-search-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-opt.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-border-l2, #d5dde3); }\n[data-edrv-view] .edrv-search-spacer { flex: 1; }\n[data-edrv-view] .edrv-search-summary { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 2px 4px; }\n[data-edrv-view] .edrv-search-results { flex: 1; min-height: 0; overflow: auto; font-size: 12px; }\n[data-edrv-view] .edrv-search-group { margin-bottom: 2px; }\n[data-edrv-view] .edrv-search-file { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-search-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-search-fname { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-search-fdir { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-search-fcount { flex-shrink: 0; min-width: 16px; height: 15px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 15px; text-align: center; }\n[data-edrv-view] .edrv-search-line { display: flex; gap: 6px; padding: 1px 4px 1px 16px; border-radius: 4px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-search-line.alt { background: var(--dsw-alias-bg-layer-1, #ffffff); }\n[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }\n[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: var(--dsw-alias-label-primary-inverted, #1f2933); border-radius: 2px; padding: 0 1px; }\n[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-warn-primary, rgba(240,173,78,.45)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(240,173,78,.12)); color: var(--dsw-alias-state-warn-primary, #b8860b); font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n/* ===== rules panel (sidebar, Codebuddy rules view) ===== */\n[data-edrv-view] .edrv-rules-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-rules-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-tab { height: 24px; padding: 0 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-tab.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-rules-new { margin-left: auto; height: 24px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-new:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-ws-line { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 auto; user-select: none; }\n[data-edrv-view] .edrv-rules-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 0 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-list { flex: 1; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-rules-row { display: flex; align-items: center; gap: 4px; padding: 6px 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; }\n[data-edrv-view] .edrv-rules-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-row.off .edrv-rules-main { opacity: .5; }\n[data-edrv-view] .edrv-rules-main { flex: 1; min-width: 0; cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-rules-line1 { display: flex; align-items: center; gap: 6px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sub { display: flex; align-items: center; gap: 5px; margin-top: 3px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sublabel { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }\n[data-edrv-view] .edrv-rules-type-always { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.15)); color: var(--dsw-alias-state-success-primary, #0b7c46); }\n[data-edrv-view] .edrv-rules-type-auto { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(79,140,255,.16)); color: var(--dsw-alias-link, #3367c9); }\n[data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }\n[data-edrv-view] .edrv-rules-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }\n[data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); display: inline-flex; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: var(--dsw-alias-border-l2, #b9c4cc); cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }\n[data-edrv-view] .edrv-rules-switch.on { background: var(--dsw-alias-brand-primary, #7c5cff); }\n[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--dsw-alias-bg-layer-1, #fff); transition: left .15s; }\n[data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }\n[data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }\n[data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }\n[data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-editor-ctl { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-ctl-label { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select { height: 24px; padding: 0 4px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-desc-input { flex: 1; min-width: 0; }\n[data-edrv-view] .edrv-rules-input { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-body { flex: 1; min-height: 0; resize: none; padding: 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; line-height: 1.5; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }\n\n/* 命令栏(Ctrl+Shift+P / F1):body 级 portal 浮层,故不依赖编辑区几何。\n ⚠️ 浮层 z-index 阶梯(自下而上;改动务必保持顺序,tests/overlayZOrder.test.ts 守约):\n 编辑器内浮层 60(.edrv-search-pop) / 71(.edrv-ctxmenu)\n < 190 命令栏遮罩 < 200 命令栏\n < 195 片段浮窗遮罩 < 197 片段二级弹窗遮罩\n 命令栏必须**高于**片段浮窗:曾因片段遮罩(192) 压过命令栏(191),导致片段浮窗打开时\n 命令栏被整体盖住,按 Ctrl+Shift+P 因「已打开即早退」而毫无反应(表现为快捷键失效)。 */\n[data-edrv-view] .edrv-palette-mask { position: fixed; inset: 0; z-index: 190; background: rgba(0, 0, 0, .28); }\n[data-edrv-view] .edrv-palette { position: fixed; top: 12vh; left: 50%; transform: translateX(-50%); z-index: 200; width: min(580px, calc(100vw - 32px)); max-height: 64vh; display: flex; flex-direction: column; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 12px 34px rgba(31, 41, 51, .24); overflow: hidden; }\n[data-edrv-view] .edrv-palette-input { height: 38px; flex: 0 0 auto; padding: 0 12px; border: none; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: transparent; color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; outline: none; }\n[data-edrv-view] .edrv-palette-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 4px; }\n[data-edrv-view] .edrv-palette-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; }\n[data-edrv-view] .edrv-palette-row.edrv-palette-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 157, 88, .10)); }\n[data-edrv-view] .edrv-palette-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-palette-cat { flex: 0 0 auto; font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-palette-key { flex: 0 0 auto; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 10px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-palette-empty { padding: 14px 10px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #7b8794); text-align: center; }\n[data-edrv-view] .edrv-palette-notice { flex: 0 0 auto; padding: 6px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-state-warn-primary, #b7791f); }\n\n/* 代码片段选择器(配置 / 插入):居中浮窗 + 新建二级弹窗。\n 卡片复用 mcp.css 的 .vsm-mcp-dialog(与「添加 MCP」「工作区选择」同一视觉语言),\n 此处只补遮罩、尺寸与列表布局。⚠️ 根节点须带 data-edrv-view,否则这些规则全部失配。 */\n[data-edrv-view] .edrv-snip-mask { position: fixed; inset: 0; z-index: 195; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, rgba(0, 0, 0, .55)); }\n/* 二级弹窗叠在一级浮窗之上(仍在命令栏之下,见上方阶梯说明) */\n[data-edrv-view] .edrv-snip-mask-top { z-index: 197; }\n[data-edrv-view] .edrv-snip-dialog { display: flex; flex-direction: column; overflow: hidden; width: min(620px, calc(100vw - 48px)); }\n[data-edrv-view] .edrv-snip-hint { margin: 0 0 12px; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-list { display: flex; flex-direction: column; gap: 1px; min-height: 0; max-height: 46vh; overflow: auto; }\n[data-edrv-view] .edrv-snip-group { padding: 12px 2px 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-row { display: flex; align-items: center; gap: 4px; width: 100%; box-sizing: border-box; padding: 2px; border: none; border-radius: 8px; background: transparent; text-align: left; color: var(--dsw-alias-label-primary, #e6e6e6); font: inherit; }\n[data-edrv-view] .edrv-snip-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, .06)); }\n[data-edrv-view] button.edrv-snip-row { cursor: pointer; }\n[data-edrv-view] .edrv-snip-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border: none; border-radius: 6px; background: transparent; text-align: left; cursor: pointer; color: inherit; font: inherit; }\n[data-edrv-view] .edrv-snip-label { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #e6e6e6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-desc { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-src { flex: 0 0 auto; padding-right: 6px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-warn { flex: 0 0 auto; font-size: 11px; padding: 2px 7px; border-radius: 999px; background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .16)); color: var(--dsw-alias-state-warn-primary, #e5b93f); white-space: nowrap; }\n[data-edrv-view] .edrv-snip-act { flex: 0 0 auto; width: 26px; height: 26px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-snip-act:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n[data-edrv-view] .edrv-snip-empty { padding: 12px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n/* 当前文件语言提示条(片段按语言绑定,明确告知生效范围) */\n[data-edrv-view] .edrv-snip-langbar { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; margin: 0 0 10px; padding: 8px 10px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #2b2b2b); font-size: 12px; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-langbar b { color: var(--dsw-alias-label-primary, #e6e6e6); }\n/* 二级弹窗的动作按钮(原生 .vsm-mcp-dialog-actions 已提供排版与按钮样式) */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-dialog-actions { flex-wrap: wrap; }\n/* 删除图标按钮:.vsm-danger 的规则全部限定在 .vsm-mcp-actions button 之下,裸用无样式,此处补自有类 */\n[data-edrv-view] .edrv-snip-act.edrv-snip-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n/* 浮窗内的原生空态(.vsm-mcp-empty 默认为整页尺寸)压缩为对话框尺寸 */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-empty { padding: 26px 16px; border: 0; }\n\n\n\n\n";
36
36
  const tagId$1 = "dsh-vscode-mode/editor.css";
37
37
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
38
38
  const tag = document.createElement("style");
@@ -1481,7 +1481,8 @@ window.__ModuleLoader__.load({
1481
1481
  properties: "ini",
1482
1482
  json5: "jsonc",
1483
1483
  log: "plaintext",
1484
- txt: "plaintext"
1484
+ txt: "plaintext",
1485
+ "code-snippets": "json"
1485
1486
  };
1486
1487
  /**
1487
1488
  * 路径 → Monaco language id:取 basename 扩展名(dockerfile/Dockerfile 特判)。
@@ -1496,6 +1497,26 @@ window.__ModuleLoader__.load({
1496
1497
  if (dot <= 0) return "plaintext";
1497
1498
  return LANG_BY_EXT[base.slice(dot + 1).toLowerCase()] ?? "plaintext";
1498
1499
  }
1500
+ /** 片段文件后缀(判断「文件自身是片段文件」而非普通源码)。 */
1501
+ const SNIPPET_FILE_SUFFIX = ".code-snippets";
1502
+ /**
1503
+ * 片段文件 → 它绑定的语言 id(VS Code 文件名约定 `<language>.code-snippets`)。
1504
+ *
1505
+ * 为什么不能直接用 {@link langOf}:`.code-snippets` 本身在 LANG_BY_EXT 里映射为 json
1506
+ * (编辑片段文件时要按 JSON 高亮),于是 langOf('lua.code-snippets') 会得到 'json' 而不是
1507
+ * 'lua' —— 曾导致「新建」默认文件名被算成 `json.code-snippets`。故这里按片段命名约定
1508
+ * 单独解析:去掉 `.code-snippets` 后缀取语言前缀。
1509
+ *
1510
+ * @author ddj 2026年09月10号
1511
+ * @param path 片段文件路径
1512
+ * @returns 语言 id(`global.code-snippets` / 无法识别 → 空串 = 全语言)
1513
+ */
1514
+ function snippetLanguageOf(path) {
1515
+ const base = String(path || "").split(/[\\/]/).pop() || "";
1516
+ if (!base.toLowerCase().endsWith(SNIPPET_FILE_SUFFIX)) return langOf(path);
1517
+ const lang = base.slice(0, -14).toLowerCase();
1518
+ return lang === "global" ? "" : lang;
1519
+ }
1499
1520
  /**
1500
1521
  * 加载 Monaco Editor(AMD 构建,随插件包离线分发):注入 loader.js → require.config → editor.main。
1501
1522
  * @author ddj 2026年08月20号
@@ -2487,7 +2508,9 @@ window.__ModuleLoader__.load({
2487
2508
  * 纯数据模块:host(settings schema 默认值)与 client(执行匹配/设置页)共用。
2488
2509
  * 键位格式:修饰符 + 主键,`+` 连接,如 `Ctrl+Shift+F`;空串 = 未绑定;
2489
2510
  * `|` 连接多个候选(任一命中即触发),如 `Alt+ArrowLeft|Ctrl+Alt+-`。
2490
- * 作者 ddj 2026年08月26号
2511
+ * 约束:本模块被 host 与 client 两半共用,**禁止 import client 侧模块**(React/浏览器 API)。
2512
+ * 与 client/ui/commandCatalog 的一致性由 tests/commands.test.ts 断言兜底。
2513
+ * 作者 ddj 2026年08月26号 / 2026年09月10号
2491
2514
  */
2492
2515
  /** 命令 id → 默认键位(可为多候选)。命令目录以此为准,新增命令只需加一项。 */
2493
2516
  const KEYBINDING_DEFAULTS = {
@@ -2498,7 +2521,11 @@ window.__ModuleLoader__.load({
2498
2521
  "edrv.navigateBack": "Alt+ArrowLeft|Ctrl+Alt+-",
2499
2522
  "edrv.navigateForward": "Alt+ArrowRight|Ctrl+Shift+-",
2500
2523
  "edrv.nextTab": "Ctrl+Alt+ArrowRight|Ctrl+PageDown",
2501
- "edrv.prevTab": "Ctrl+Alt+ArrowLeft|Ctrl+PageUp"
2524
+ "edrv.prevTab": "Ctrl+Alt+ArrowLeft|Ctrl+PageUp",
2525
+ "edrv.showCommands": "Ctrl+Shift+P|F1",
2526
+ "edrv.nextEditorRow": "Ctrl+Alt+ArrowDown",
2527
+ "edrv.prevEditorRow": "Ctrl+Alt+ArrowUp",
2528
+ "edrv.addSelectionRef": "Ctrl+U"
2502
2529
  };
2503
2530
  /**
2504
2531
  * 返回默认键位的独立副本(调用方修改不污染目录)。
@@ -2522,49 +2549,298 @@ window.__ModuleLoader__.load({
2522
2549
  return out;
2523
2550
  }
2524
2551
  //#endregion
2525
- //#region src/client/keybindings.ts
2552
+ //#region src/client/editorModelState.ts
2526
2553
  /**
2527
- * dsh-vscode-mode client — 快捷键配置模块(解析/匹配/状态同步)。
2528
- * 纯逻辑(parseChord/parseChords/formatChord/matchEvent/chordFromEvent/normalizeKey)不依赖 DOM,可单测;
2529
- * 模块状态由 settings 订阅驱动(client/index.ts 调 keybindingsApply)。
2530
- * 键位语义:Ctrl 与 Cmd 互认(延续现有 Ctrl+P/Ctrl+B 捕获行为)。
2531
- * 作者 ddj 2026年08月26号
2554
+ * 当前文档是否已有 Monaco 活动模型(编辑器打开着文件而非空态)。
2555
+ * @author ddj 2026年09月10号
2556
+ * @returns 是否存在活动模型
2532
2557
  */
2533
- /** 命令目录(设置页展示标签;执行按目录序先匹配先执行,冲突键位确定性)。 */
2534
- const COMMANDS = [
2558
+ function hasEditorModel() {
2559
+ if (typeof document === "undefined") return false;
2560
+ return document.querySelector(".monaco-editor textarea.inputarea") !== null;
2561
+ }
2562
+ //#endregion
2563
+ //#region src/client/ui/commandCatalog.ts
2564
+ /**
2565
+ * dsh-vscode-mode client — 编辑器指令目录(纯元数据)。
2566
+ * 「一条命令 = 一条注册数据」:命令栏、快捷键设置页、快捷键监听与 Monaco 右键菜单
2567
+ * 全部从本表读取;新增一条能力只需在 EDITOR_COMMANDS 追加一项(可选 keybinding)。
2568
+ * run 只派发 `edrv.command.*` 窗口事件,不直接触碰 React/Monaco(`edrv.command.` 前缀
2569
+ * 与既有 `edrv:` 刷新/主题事件分属不同命名空间,互不干扰)。
2570
+ * 作者 ddj 2026年09月10号
2571
+ */
2572
+ /**
2573
+ * 派发编辑器命令事件(`edrv.command.<action>`)。
2574
+ * 无 window 的运行环境(纯 Node 单测)静默跳过,命令本身不因此失败。
2575
+ * @author ddj 2026年09月10号
2576
+ * @param action 动作名(不含前缀)
2577
+ */
2578
+ function emit(action) {
2579
+ if (typeof window === "undefined") return;
2580
+ window.dispatchEvent(new CustomEvent("edrv.command." + action));
2581
+ }
2582
+ /** 需要活动编辑器模型才可用的命令(命令栏隐藏并拒绝执行)。 */
2583
+ function needsModel() {
2584
+ return hasEditorModel();
2585
+ }
2586
+ /** 面板区显隐切换始终可用(未挂载编辑器时 run 为空操作)。 */
2587
+ function alwaysAvailable() {
2588
+ return true;
2589
+ }
2590
+ /**
2591
+ * 光标整行下移(编辑行导航)。
2592
+ * @author ddj 2026年09月10号
2593
+ * @returns 命令定义
2594
+ */
2595
+ function nextEditorRowDef() {
2596
+ return {
2597
+ id: "edrv.nextEditorRow",
2598
+ label: "下一编辑行(光标整行下移)",
2599
+ category: "导航",
2600
+ order: 50,
2601
+ keybinding: "Ctrl+Alt+ArrowDown",
2602
+ available: needsModel,
2603
+ run: () => emit("nextEditorRow")
2604
+ };
2605
+ }
2606
+ /**
2607
+ * 光标整行上移(编辑行导航)。
2608
+ * @author ddj 2026年09月10号
2609
+ * @returns 命令定义
2610
+ */
2611
+ function prevEditorRowDef() {
2612
+ return {
2613
+ id: "edrv.prevEditorRow",
2614
+ label: "上一编辑行(光标整行上移)",
2615
+ category: "导航",
2616
+ order: 60,
2617
+ keybinding: "Ctrl+Alt+ArrowUp",
2618
+ available: needsModel,
2619
+ run: () => emit("prevEditorRow")
2620
+ };
2621
+ }
2622
+ /**
2623
+ * 添加选中内容为引用(把当前选区追加进对话输入框;无选区则状态栏提示)。
2624
+ * 有活动编辑器模型才可用,保证对话框内按 Ctrl+U 不被本命令吞掉。
2625
+ * @author ddj 2026年09月10号
2626
+ * @returns 命令定义
2627
+ */
2628
+ function addSelectionRefDef() {
2629
+ return {
2630
+ id: "edrv.addSelectionRef",
2631
+ label: "添加选中内容为引用",
2632
+ category: "编辑",
2633
+ order: 10,
2634
+ keybinding: "Ctrl+U",
2635
+ available: needsModel,
2636
+ run: () => emit("addSelectionRef")
2637
+ };
2638
+ }
2639
+ /**
2640
+ * 编辑器内置指令目录(顺序 = 快捷键设置页展示顺序)。
2641
+ * 前置 8 条的键位由 EditorView / QuickOpen 自行 capture 监听(历史实现),
2642
+ * 故不进 BRIDGE_COMMANDS,避免同一按键双执行。
2643
+ * @author ddj 2026年09月10号
2644
+ */
2645
+ const EDITOR_COMMANDS = [
2535
2646
  {
2536
2647
  id: "edrv.save",
2537
- label: "保存文件"
2648
+ label: "保存文件",
2649
+ category: "文件",
2650
+ order: 10,
2651
+ keybinding: "Ctrl+S",
2652
+ available: needsModel,
2653
+ run: () => emit("save")
2538
2654
  },
2539
2655
  {
2540
2656
  id: "edrv.quickOpen",
2541
- label: "快速打开文件"
2657
+ label: "快速打开文件",
2658
+ category: "文件",
2659
+ order: 20,
2660
+ keybinding: "Ctrl+P",
2661
+ available: alwaysAvailable,
2662
+ run: () => emit("quickOpen")
2542
2663
  },
2543
2664
  {
2544
2665
  id: "edrv.toggleSidebar",
2545
- label: "切换侧边栏"
2666
+ label: "切换侧边栏",
2667
+ category: "视图",
2668
+ order: 10,
2669
+ keybinding: "Ctrl+B",
2670
+ available: alwaysAvailable,
2671
+ run: () => emit("toggleSidebar")
2546
2672
  },
2547
2673
  {
2548
2674
  id: "edrv.searchInFiles",
2549
- label: "在工作区中搜索"
2675
+ label: "在工作区中搜索",
2676
+ category: "视图",
2677
+ order: 20,
2678
+ keybinding: "Ctrl+Shift+F",
2679
+ available: alwaysAvailable,
2680
+ run: () => emit("searchInFiles")
2550
2681
  },
2551
2682
  {
2552
2683
  id: "edrv.navigateBack",
2553
- label: "后退(导航历史)"
2684
+ label: "后退(导航历史)",
2685
+ category: "导航",
2686
+ order: 10,
2687
+ keybinding: "Alt+ArrowLeft|Ctrl+Alt+-",
2688
+ available: needsModel,
2689
+ run: () => emit("navigateBack")
2554
2690
  },
2555
2691
  {
2556
2692
  id: "edrv.navigateForward",
2557
- label: "前进(导航历史)"
2693
+ label: "前进(导航历史)",
2694
+ category: "导航",
2695
+ order: 20,
2696
+ keybinding: "Alt+ArrowRight|Ctrl+Shift+-",
2697
+ available: needsModel,
2698
+ run: () => emit("navigateForward")
2558
2699
  },
2559
2700
  {
2560
2701
  id: "edrv.nextTab",
2561
- label: "下一个页签"
2702
+ label: "下一个页签",
2703
+ category: "导航",
2704
+ order: 30,
2705
+ keybinding: "Ctrl+Alt+ArrowRight|Ctrl+PageDown",
2706
+ available: needsModel,
2707
+ run: () => emit("nextTab")
2562
2708
  },
2563
2709
  {
2564
2710
  id: "edrv.prevTab",
2565
- label: "上一个页签"
2711
+ label: "上一个页签",
2712
+ category: "导航",
2713
+ order: 40,
2714
+ keybinding: "Ctrl+Alt+ArrowLeft|Ctrl+PageUp",
2715
+ available: needsModel,
2716
+ run: () => emit("prevTab")
2717
+ },
2718
+ {
2719
+ id: "edrv.goToDefinition",
2720
+ label: "转到定义",
2721
+ category: "语言智能",
2722
+ order: 10,
2723
+ available: needsModel,
2724
+ run: () => emit("goToDefinition")
2725
+ },
2726
+ {
2727
+ id: "edrv.findReferences",
2728
+ label: "查找所有引用",
2729
+ category: "语言智能",
2730
+ order: 20,
2731
+ available: needsModel,
2732
+ run: () => emit("findReferences")
2733
+ },
2734
+ {
2735
+ id: "edrv.triggerAi",
2736
+ label: "触发 AI 内联补全",
2737
+ category: "语言智能",
2738
+ order: 30,
2739
+ available: needsModel,
2740
+ run: () => emit("triggerAi")
2741
+ },
2742
+ {
2743
+ id: "edrv.openInExplorer",
2744
+ label: "在文件浏览器中打开",
2745
+ category: "文件",
2746
+ order: 30,
2747
+ available: needsModel,
2748
+ run: () => emit("openInExplorer")
2749
+ },
2750
+ {
2751
+ id: "edrv.configureSnippets",
2752
+ label: "代码片段:配置代码片段",
2753
+ category: "代码片段",
2754
+ order: 10,
2755
+ available: alwaysAvailable,
2756
+ run: () => emit("configureSnippets")
2757
+ },
2758
+ {
2759
+ id: "edrv.insertSnippet",
2760
+ label: "插入代码片段",
2761
+ category: "代码片段",
2762
+ order: 20,
2763
+ available: needsModel,
2764
+ run: () => emit("insertSnippet")
2566
2765
  }
2567
2766
  ];
2767
+ /**
2768
+ * 桥接派发指令(无原生监听,键位由 commandBridge 统一 capture 处理)。
2769
+ * 新增「只填目录、不写监听」的编辑器指令一律放这里:键位、命令栏、设置页自动可用。
2770
+ * @author ddj 2026年09月10号
2771
+ */
2772
+ const BRIDGE_COMMANDS = [
2773
+ nextEditorRowDef(),
2774
+ prevEditorRowDef(),
2775
+ addSelectionRefDef()
2776
+ ];
2777
+ /**
2778
+ * 命令栏自身的命令(避免循环依赖,由 commandBridge 注入 run 后注册)。
2779
+ * @author ddj 2026年09月10号
2780
+ * @param run 打开命令栏
2781
+ * @returns 命令定义
2782
+ */
2783
+ function showCommandsDef(run) {
2784
+ return {
2785
+ id: "edrv.showCommands",
2786
+ label: "显示所有命令",
2787
+ category: "视图",
2788
+ order: 1,
2789
+ keybinding: "Ctrl+Shift+P|F1",
2790
+ available: alwaysAvailable,
2791
+ run
2792
+ };
2793
+ }
2794
+ /**
2795
+ * 需要全局键位派发的指令(桥接类 + 命令栏自身;不含 EditorView/QuickOpen 原生监听的那些)。
2796
+ * @author ddj 2026年09月10号
2797
+ * @param showCommands 命令栏命令定义
2798
+ * @returns 派发集合
2799
+ */
2800
+ function dispatchedCommands(showCommands) {
2801
+ return [...BRIDGE_COMMANDS, showCommands];
2802
+ }
2803
+ //#endregion
2804
+ //#region src/client/keybindings.ts
2805
+ /**
2806
+ * dsh-vscode-mode client — 快捷键配置模块(解析/匹配/状态同步)。
2807
+ * 纯逻辑(parseChord/parseChords/formatChord/matchEvent/chordFromEvent/normalizeKey)不依赖 DOM,可单测;
2808
+ * 模块状态由 settings 订阅驱动(client/index.ts 调 keybindingsApply)。
2809
+ * 键位语义:Ctrl 与 Cmd 互认(延续 Ctrl+P/Ctrl+B 捕获行为)。
2810
+ * 命令目录(COMMANDS)派生自指令目录 commandCatalog(指令 = 单一数据源,设置页自动跟随);
2811
+ * 另支持运行时键位表(第三方命令 register 时声明,不落设置 schema,注销即失效)。
2812
+ * 作者 ddj 2026年08月26号 / 2026年09月10号
2813
+ */
2814
+ /** 命令目录(设置页展示标签;执行按目录序先匹配先执行,冲突键位确定性)。 */
2815
+ const COMMANDS = [
2816
+ ...EDITOR_COMMANDS.map((command) => ({
2817
+ id: command.id,
2818
+ label: command.label
2819
+ })),
2820
+ {
2821
+ id: "edrv.showCommands",
2822
+ label: "显示所有命令"
2823
+ },
2824
+ ...BRIDGE_COMMANDS.map((command) => ({
2825
+ id: command.id,
2826
+ label: command.label
2827
+ }))
2828
+ ];
2829
+ /** 命令栏命令定义(键位自检与目录展示共用;run 不参与键位逻辑)。 */
2830
+ function paletteCommand() {
2831
+ return showCommandsDef(() => {});
2832
+ }
2833
+ /**
2834
+ * 目录与默认键位表一致性自检(仅告警不中断):新指令漏写共享表时第一时间可见。
2835
+ * @author ddj 2026年09月10号
2836
+ */
2837
+ function checkDefaultsDrift() {
2838
+ for (const command of [...EDITOR_COMMANDS, paletteCommand()]) {
2839
+ if (!command.keybinding) continue;
2840
+ if (KEYBINDING_DEFAULTS[command.id] === command.keybinding) continue;
2841
+ log.warn("指令默认键位与共享表不一致:" + command.id + " 目录=" + command.keybinding + " 共享表=" + String(KEYBINDING_DEFAULTS[command.id]));
2842
+ }
2843
+ }
2568
2844
  const MODIFIERS = {
2569
2845
  ctrl: "ctrl",
2570
2846
  cmd: "meta",
@@ -2573,8 +2849,9 @@ window.__ModuleLoader__.load({
2573
2849
  alt: "alt"
2574
2850
  };
2575
2851
  let current = { ...KEYBINDING_DEFAULTS };
2576
- let parsed = {};
2577
- const listeners$1 = /* @__PURE__ */ new Set();
2852
+ const runtime = /* @__PURE__ */ new Map();
2853
+ const listeners$2 = /* @__PURE__ */ new Set();
2854
+ checkDefaultsDrift();
2578
2855
  /**
2579
2856
  * 应用设置快照(与默认值合并;未知 id 丢弃;空对象 = 全部默认)。
2580
2857
  * 每个命令可含多候选键位(`|` 分隔),任一命中即触发。
@@ -2586,9 +2863,7 @@ window.__ModuleLoader__.load({
2586
2863
  ...KEYBINDING_DEFAULTS,
2587
2864
  ...normalizeKeybindings(raw)
2588
2865
  };
2589
- parsed = {};
2590
- for (const id of Object.keys(current)) parsed[id] = parseChords(current[id]);
2591
- for (const listener of listeners$1) try {
2866
+ for (const listener of listeners$2) try {
2592
2867
  listener();
2593
2868
  } catch {}
2594
2869
  }
@@ -2599,27 +2874,34 @@ window.__ModuleLoader__.load({
2599
2874
  * @returns 取消订阅函数
2600
2875
  */
2601
2876
  function subscribeKeybindings(listener) {
2602
- listeners$1.add(listener);
2603
- return () => listeners$1.delete(listener);
2877
+ listeners$2.add(listener);
2878
+ return () => listeners$2.delete(listener);
2604
2879
  }
2605
2880
  /**
2606
- * 当前键位弦(未绑定/空 → null)。
2607
- * @author ddj 2026年08月26号
2881
+ * 当前命令的键位弦(运行时键位优先;未绑定/空 → null)。
2882
+ * @author ddj 2026年08月26号 / 2026年09月10号
2608
2883
  * @param id 命令 id
2609
2884
  * @returns 键位弦或 null
2610
2885
  */
2611
2886
  function chordOf(id) {
2887
+ if (runtime.has(id)) {
2888
+ const chords = runtime.get(id) ?? [];
2889
+ return chords.length ? chords.map(formatChord).join("|") : null;
2890
+ }
2612
2891
  const chord = current[id];
2613
2892
  return typeof chord === "string" && chord.trim() !== "" ? chord : null;
2614
2893
  }
2615
2894
  /**
2616
2895
  * 当前命令的解析键位集合(未绑定/非法 → 空数组;含多候选)。
2617
- * @author ddj 2026年08月26号
2896
+ * @author ddj 2026年08月26号 / 2026年09月10号
2618
2897
  * @param id 命令 id
2619
2898
  * @returns 解析键位数组(可能为空)
2620
2899
  */
2621
2900
  function bindingsOf(id) {
2622
- return parsed[id] ?? [];
2901
+ const override = runtime.get(id);
2902
+ if (override) return override;
2903
+ const chord = current[id];
2904
+ return typeof chord === "string" ? parseChords(chord) : [];
2623
2905
  }
2624
2906
  /**
2625
2907
  * 主键规范化:小写字母大写、空格归一为 Space,其余原样(与事件 e.key 对照)。
@@ -2793,19 +3075,26 @@ window.__ModuleLoader__.load({
2793
3075
  inputRef.current?.blur?.();
2794
3076
  };
2795
3077
  react.default.useEffect(() => {
3078
+ const openBox = () => {
3079
+ inputRef.current?.focus?.();
3080
+ setOpen(true);
3081
+ };
2796
3082
  const onKey = (e) => {
2797
3083
  if (matchEvent(e, bindingsOf("edrv.quickOpen"))) {
2798
3084
  e.preventDefault();
2799
3085
  e.stopPropagation();
2800
- inputRef.current?.focus?.();
2801
- setOpen(true);
3086
+ openBox();
2802
3087
  } else if (e.key === "Escape") {
2803
3088
  setOpen(false);
2804
3089
  inputRef.current?.blur?.();
2805
3090
  }
2806
3091
  };
2807
3092
  window.addEventListener("keydown", onKey, true);
2808
- return () => window.removeEventListener("keydown", onKey, true);
3093
+ window.addEventListener("edrv.command.quickOpen", openBox);
3094
+ return () => {
3095
+ window.removeEventListener("keydown", onKey, true);
3096
+ window.removeEventListener("edrv.command.quickOpen", openBox);
3097
+ };
2809
3098
  }, []);
2810
3099
  const dirText = (p) => {
2811
3100
  const base = String(p).split(/[\\/]/).pop() || String(p);
@@ -2838,6 +3127,535 @@ window.__ModuleLoader__.load({
2838
3127
  }), pop);
2839
3128
  }
2840
3129
  //#endregion
3130
+ //#region src/client/commandGlobals.ts
3131
+ /**
3132
+ * dsh-vscode-mode client — 指令系统的全局挂点名约定。
3133
+ * 单独成模块:装配层(client/index.ts)与读取方(命令栏/第三方)都要用,
3134
+ * 放这里可避免 CommandPalette ↔ index 的循环依赖。
3135
+ *
3136
+ * ⚠️ 必须挂在 window 自身(双下划线 + edrv 前缀,与既有 __edrvExtPoll 同风格)。
3137
+ * 旧实现写 window.dsh.edrvCommands——DSH 从不创建 window.dsh 命名空间(全仓库无赋值),
3138
+ * 该路径恒为死代码并导致命令栏读到空表;不要再改回气泡式挂点。
3139
+ * 作者 ddj 2026年09月10号
3140
+ */
3141
+ /** 指令注册表的全局挂点名:window[REGISTRY_GLOBAL](第三方注册与调试读取;命令栏首选模块引用)。 */
3142
+ const REGISTRY_GLOBAL = "__edrvCommands__";
3143
+ //#endregion
3144
+ //#region src/client/commandSearch.ts
3145
+ /** 排序用基础序(未声明 order 的命令排在已声明之后)。 */
3146
+ const DEFAULT_ORDER = 100;
3147
+ /** 命中字段权重(数值越小越优先)。 */
3148
+ const FIELD_WEIGHT = {
3149
+ label: 0,
3150
+ id: 1,
3151
+ category: 2
3152
+ };
3153
+ /** 小写化(非字符串按空串处理,容错外部脏数据)。 */
3154
+ function lower(text) {
3155
+ return typeof text === "string" ? text.toLocaleLowerCase("en-US") : "";
3156
+ }
3157
+ /** 建立一条命令的索引文本。 */
3158
+ function indexOf(command) {
3159
+ return {
3160
+ label: lower(command.label),
3161
+ id: lower(command.id),
3162
+ category: lower(command.category)
3163
+ };
3164
+ }
3165
+ /**
3166
+ * 命令是否命中全部 token;命中时返回最优(最小)字段权重。
3167
+ * @author ddj 2026年09月10号
3168
+ * @param fields 命令索引文本
3169
+ * @param tokens 小写查询 token
3170
+ * @returns 字段权重;未命中返回 null
3171
+ */
3172
+ function hitWeight(fields, tokens) {
3173
+ let best = null;
3174
+ for (const token of tokens) {
3175
+ let tokenWeight = null;
3176
+ for (const field of [
3177
+ "label",
3178
+ "id",
3179
+ "category"
3180
+ ]) {
3181
+ if (!fields[field].includes(token)) continue;
3182
+ const weight = FIELD_WEIGHT[field];
3183
+ if (tokenWeight === null || weight < tokenWeight) tokenWeight = weight;
3184
+ if (weight === 0) break;
3185
+ }
3186
+ if (tokenWeight === null) return null;
3187
+ if (best === null || tokenWeight < best) best = tokenWeight;
3188
+ }
3189
+ return best;
3190
+ }
3191
+ /** 目录序(order 缺省 100;非有限值同样按 100)。 */
3192
+ function orderOf$1(command) {
3193
+ const order = command.order;
3194
+ return typeof order === "number" && Number.isFinite(order) ? order : DEFAULT_ORDER;
3195
+ }
3196
+ /**
3197
+ * 过滤并按相关度排序命令(空查询返回全部,仅按目录序排序)。
3198
+ * @author ddj 2026年09月10号
3199
+ * @param commands 候选命令(调用方通常已按可用性过滤)
3200
+ * @param query 用户输入
3201
+ * @returns 命中命令数组
3202
+ */
3203
+ function filterCommands(commands, query) {
3204
+ const tokens = lower(query).split(/\s+/).filter(Boolean);
3205
+ const hits = [];
3206
+ for (const command of commands) {
3207
+ const weight = tokens.length ? hitWeight(indexOf(command), tokens) : 0;
3208
+ if (weight !== null) hits.push({
3209
+ command,
3210
+ weight
3211
+ });
3212
+ }
3213
+ return hits.map((hit, index) => ({
3214
+ hit,
3215
+ index
3216
+ })).sort((a, b) => a.hit.weight - b.hit.weight || orderOf$1(a.hit.command) - orderOf$1(b.hit.command) || a.index - b.index).map((entry) => entry.hit.command);
3217
+ }
3218
+ //#endregion
3219
+ //#region src/client/commandRegistry.ts
3220
+ /**
3221
+ * dsh-vscode-mode client — 指令注册表(命令栏与快捷键系统的唯一数据源)。
3222
+ * 镜像 sidebar/registry.ts 的注册表模式:register 返回注销器、list/subscribe 可订阅;
3223
+ * 额外提供 run(id):先做可用性判定再执行,运行体异常一律捕获上报,绝不让命令抛出到事件循环。
3224
+ * 重复 id 按「后注册者生效」覆盖,旧注销器带身份校验(不会误删新实例,见 releaseRegistry)。
3225
+ * 作者 ddj 2026年09月10号
3226
+ */
3227
+ const registryLog = log.child("commands");
3228
+ /** 校验命令定义(缺 id/label/run 抛 TypeError,早失败优于静默无效)。 */
3229
+ function assertCommand(command) {
3230
+ if (!command || typeof command.id !== "string" || !command.id) throw new TypeError("命令必须提供 id");
3231
+ if (typeof command.label !== "string" || !command.label) throw new TypeError("命令必须提供 label:" + command.id);
3232
+ if (typeof command.run !== "function") throw new TypeError("命令必须提供 run:" + command.id);
3233
+ }
3234
+ /** 目录序(order 缺省 100;非有限值同样按 100)。 */
3235
+ function orderOf(command) {
3236
+ return typeof command.order === "number" && Number.isFinite(command.order) ? command.order : 100;
3237
+ }
3238
+ /** 命令是否可用(available 缺省视为可用;判定异常按不可用处理)。 */
3239
+ function isAvailable$1(command) {
3240
+ if (typeof command.available !== "function") return true;
3241
+ try {
3242
+ return command.available() === true;
3243
+ } catch (error) {
3244
+ registryLog.warn("可用性判定异常(按不可用处理):" + command.id + " · " + String(error));
3245
+ return false;
3246
+ }
3247
+ }
3248
+ /**
3249
+ * 注销命令(仅当表中仍是本次注册的定义时移除,避免旧实例误删新实例)。
3250
+ * @author ddj 2026年09月10号
3251
+ * @param entries 命令表
3252
+ * @param notify 变化通知
3253
+ * @param command 注册时的定义
3254
+ * @returns 注销函数
3255
+ */
3256
+ function releaseRegistry(entries, notify, command) {
3257
+ let released = false;
3258
+ return () => {
3259
+ if (released) return;
3260
+ released = true;
3261
+ if (entries.get(command.id) !== command) return;
3262
+ entries.delete(command.id);
3263
+ notify();
3264
+ };
3265
+ }
3266
+ /**
3267
+ * 执行已注册命令(可用性判定 → 运行体 → 异常上报)。
3268
+ * @author ddj 2026年09月10号
3269
+ * @param command 命令定义
3270
+ * @returns 是否真正执行
3271
+ */
3272
+ function runCommand(command) {
3273
+ if (!isAvailable$1(command)) {
3274
+ registryLog.debug("命令不可用,已跳过:" + command.id);
3275
+ return false;
3276
+ }
3277
+ try {
3278
+ command.run();
3279
+ registryLog.debug("命令已执行:" + command.id);
3280
+ return true;
3281
+ } catch (error) {
3282
+ registryLog.error("命令执行失败:" + command.id + " · " + String(error));
3283
+ return false;
3284
+ }
3285
+ }
3286
+ /**
3287
+ * 创建指令注册表。
3288
+ * @author ddj 2026年09月10号
3289
+ * @returns 指令注册表
3290
+ */
3291
+ function createCommandRegistry() {
3292
+ const entries = /* @__PURE__ */ new Map();
3293
+ const listeners = /* @__PURE__ */ new Set();
3294
+ const notify = () => {
3295
+ for (const listener of listeners) try {
3296
+ listener();
3297
+ } catch (error) {
3298
+ registryLog.warn("注册表订阅回调异常:" + String(error));
3299
+ }
3300
+ };
3301
+ const list = () => [...entries.values()].sort((a, b) => orderOf(a) - orderOf(b));
3302
+ const available = () => list().filter(isAvailable$1);
3303
+ return {
3304
+ register(command) {
3305
+ assertCommand(command);
3306
+ if (entries.get(command.id) !== void 0) registryLog.warn("命令 id 重复注册(后者生效):" + command.id);
3307
+ entries.set(command.id, command);
3308
+ notify();
3309
+ return releaseRegistry(entries, notify, command);
3310
+ },
3311
+ has: (id) => entries.has(id),
3312
+ get: (id) => entries.get(id),
3313
+ list,
3314
+ available,
3315
+ isAvailable: (id) => {
3316
+ const command = entries.get(id);
3317
+ return command !== void 0 && isAvailable$1(command);
3318
+ },
3319
+ match: (query) => filterCommands(available(), query),
3320
+ run(id) {
3321
+ const command = entries.get(id);
3322
+ if (!command) {
3323
+ registryLog.warn("命令未注册:" + id);
3324
+ return false;
3325
+ }
3326
+ return runCommand(command);
3327
+ },
3328
+ subscribe(listener) {
3329
+ listeners.add(listener);
3330
+ return () => listeners.delete(listener);
3331
+ }
3332
+ };
3333
+ }
3334
+ /** 装配期存入的指令注册表(命令栏读取候选的唯一真源)。 */
3335
+ let registryTable = null;
3336
+ /** 命令栏是否展开。 */
3337
+ let opened = false;
3338
+ /** 唤起序号:每次 openCommandPalette 递增(含已打开时的重复唤起),驱动重新聚焦。 */
3339
+ let openSeq = 0;
3340
+ /** 当前真正渲染浮层的宿主令牌(null = 无宿主)。 */
3341
+ let hostToken = null;
3342
+ /** 宿主令牌版本:每次释放认领递增,驱动其余实例重试认领(宿主更替后自愈)。 */
3343
+ let hostRev = 0;
3344
+ /** 注入的命令执行器(commandBridge 装配时写入)。 */
3345
+ let runner = null;
3346
+ /** 打开命令栏前的焦点元素(关闭后归还焦点,键盘操作不中断)。 */
3347
+ let focusBack = null;
3348
+ const listeners$1 = /* @__PURE__ */ new Set();
3349
+ /** 通知全部订阅者(回调异常不影响其他订阅)。 */
3350
+ function notifyPalette() {
3351
+ for (const listener of listeners$1) try {
3352
+ listener();
3353
+ } catch {}
3354
+ }
3355
+ /**
3356
+ * 注入命令执行器(命令栏执行选中命令时调用)。
3357
+ * @author ddj 2026年09月10号
3358
+ * @param next 执行器(返回是否执行成功)
3359
+ */
3360
+ function setPaletteRunner(next) {
3361
+ runner = next;
3362
+ }
3363
+ /**
3364
+ * 执行命令栏选中的命令。
3365
+ * @author ddj 2026年09月10号
3366
+ * @param id 命令 id
3367
+ * @returns 是否执行成功(执行器未注入/命令不可用/抛异常均为 false)
3368
+ */
3369
+ function runPaletteCommand(id) {
3370
+ if (!runner) return false;
3371
+ return runner(id) === true;
3372
+ }
3373
+ /**
3374
+ * 存入指令注册表(commandBridge 装配期调用;命令栏据此读取候选)。
3375
+ * @author ddj 2026年09月10号
3376
+ * @param next 指令注册表(null = 卸载)
3377
+ */
3378
+ function setRegistryRef(next) {
3379
+ registryTable = next;
3380
+ }
3381
+ /**
3382
+ * 读取指令注册表(未装配返回 null;浮层据此回退全局/空表)。
3383
+ * @author ddj 2026年09月10号
3384
+ * @returns 指令注册表或 null
3385
+ */
3386
+ function registryRef() {
3387
+ return registryTable;
3388
+ }
3389
+ /**
3390
+ * 订阅命令栏开关变化。
3391
+ * @author ddj 2026年09月10号
3392
+ * @param listener 变化回调
3393
+ * @returns 取消订阅函数
3394
+ */
3395
+ function subscribePalette(listener) {
3396
+ listeners$1.add(listener);
3397
+ return () => listeners$1.delete(listener);
3398
+ }
3399
+ /**
3400
+ * 命令栏是否展开(useSyncExternalStore 快照)。
3401
+ * @author ddj 2026年09月10号
3402
+ * @returns 是否展开
3403
+ */
3404
+ function isPaletteOpen() {
3405
+ return opened;
3406
+ }
3407
+ /** 记录打开前的焦点元素(只在真正需要归还时记录)。 */
3408
+ function rememberFocus() {
3409
+ if (typeof document === "undefined") return;
3410
+ const active = document.activeElement;
3411
+ focusBack = active instanceof HTMLElement ? active : null;
3412
+ }
3413
+ /**
3414
+ * 打开命令栏(记录当前焦点供关闭后归还)。
3415
+ *
3416
+ * 已打开时**不是空操作**:递增 openSeq 并通知订阅者,让命令栏重新获得焦点。
3417
+ * 事故驱动:片段浮窗遮罩曾压在命令栏之上,此时命令栏处于「已打开但看不见」状态;
3418
+ * 旧实现 `if (opened) return` 让快捷键毫无反应(用户报「调不出来」)。
3419
+ * 递增 seq 使每次触发都有可观测响应(重聚焦 + 重新置顶)。
3420
+ * @author ddj 2026年09月10号
3421
+ * @param reason 唤起来源(键盘/命令/外部 API)
3422
+ */
3423
+ function openCommandPalette(reason = "keybinding") {
3424
+ if (opened) {
3425
+ openSeq += 1;
3426
+ notifyPalette();
3427
+ return;
3428
+ }
3429
+ rememberFocus();
3430
+ opened = true;
3431
+ openSeq += 1;
3432
+ notifyPalette();
3433
+ }
3434
+ /**
3435
+ * 命令栏唤起序号(每次 open 调用递增,含「已打开时的重复唤起」)。
3436
+ * useSyncExternalStore 快照用它感知「重复唤起」,从而重新聚焦输入框。
3437
+ * @author ddj 2026年09月10号
3438
+ * @returns 当前序号
3439
+ */
3440
+ function paletteOpenSeq() {
3441
+ return openSeq;
3442
+ }
3443
+ /**
3444
+ * 关闭命令栏并归还焦点。
3445
+ * @author ddj 2026年09月10号
3446
+ */
3447
+ function closeCommandPalette() {
3448
+ if (!opened) return;
3449
+ opened = false;
3450
+ notifyPalette();
3451
+ const target = focusBack;
3452
+ focusBack = null;
3453
+ if (!target || typeof target.focus !== "function") return;
3454
+ try {
3455
+ target.focus();
3456
+ } catch {}
3457
+ }
3458
+ /**
3459
+ * 认领命令栏宿主:成功返回**令牌本身**(调用方须原样持有并在卸载时传回),
3460
+ * 已被他人认领时返回 null。
3461
+ *
3462
+ * ⚠️ 必须返回 internal 存下的同一个对象:旧实现返回 boolean、由调用方自行 `{}` 造令牌,
3463
+ * 导致 `releasePaletteHost` 的身份校验恒不通过 → hostToken 永久泄漏 → 之后**任何**实例
3464
+ * 都认领失败、无人渲染浮层(表现为 Ctrl+Shift+P「完全没有反应」且永不恢复)。
3465
+ * @author ddj 2026年09月10号
3466
+ * @returns 认领令牌;null 表示已有宿主
3467
+ */
3468
+ function claimPaletteHost() {
3469
+ if (hostToken !== null) return null;
3470
+ const token = {};
3471
+ hostToken = token;
3472
+ return token;
3473
+ }
3474
+ /**
3475
+ * 释放宿主认领(仅持有者可释放,卸载顺序错乱不会顶掉新宿主)。
3476
+ * 释放成功后通知订阅者,让等待中的实例**重新尝试认领**(自愈,避免宿主更替后无人渲染)。
3477
+ * @author ddj 2026年09月10号
3478
+ * @param token 认领令牌(null = 本实例未认领)
3479
+ */
3480
+ function releasePaletteHost(token) {
3481
+ if (token === null || hostToken !== token) return;
3482
+ hostToken = null;
3483
+ hostRev += 1;
3484
+ notifyPalette();
3485
+ }
3486
+ /**
3487
+ * 宿主令牌版本(每次释放认领递增)。非宿主实例订阅它,在宿主更替后重试认领 ——
3488
+ * 否则先挂载的实例卸载后无人接管,快捷键会「完全没有反应」且永不恢复。
3489
+ * @author ddj 2026年09月10号
3490
+ * @returns 当前版本号
3491
+ */
3492
+ function paletteHostRev() {
3493
+ return hostRev;
3494
+ }
3495
+ //#endregion
3496
+ //#region src/client/ui/CommandPalette.ts
3497
+ /**
3498
+ * dsh-vscode-mode client — 命令栏浮层(Ctrl+Shift+P / F1)。
3499
+ * 订阅 commandPaletteStore 的开关状态;候选来自注入的指令注册表(window.dsh.edrvCommands),
3500
+ * 过滤/排序复用 commandSearch 纯函数;↑↓ 选择、Enter 执行、Esc 或点击遮罩关闭。
3501
+ * 浮层经 createPortal 渲染到 body,故不受编辑区三种形态(官方侧栏/better-sidebar/中央页签)影响;
3502
+ * 宿主单实例由 store 的 claimPaletteHost 保证(另一个宿主自动返回 null)。
3503
+ * 作者 ddj 2026年09月10号
3504
+ */
3505
+ /** 空注册表(宿主未注入时的安全降级:命令栏可开但无候选)。 */
3506
+ let fallbackRegistry = null;
3507
+ /**
3508
+ * 读取指令注册表:① 装配期存入的模块引用(首选,不依赖全局)
3509
+ * ② window[REGISTRY_GLOBAL] 镜像(第三方/调试场景)③ 空表降级(仅装配异常时)。
3510
+ * @author ddj 2026年09月10号
3511
+ * @returns 指令注册表
3512
+ */
3513
+ function useRegistry() {
3514
+ const injected = registryRef();
3515
+ if (injected) return injected;
3516
+ const mirrored = typeof window === "undefined" ? void 0 : window[REGISTRY_GLOBAL];
3517
+ if (mirrored) return mirrored;
3518
+ if (!fallbackRegistry) fallbackRegistry = createCommandRegistry();
3519
+ return fallbackRegistry;
3520
+ }
3521
+ /**
3522
+ * 组装候选行(先按可用性过滤,再按查询排序)。
3523
+ * @author ddj 2026年09月10号
3524
+ * @param registry 指令注册表
3525
+ * @param query 用户输入
3526
+ * @returns 候选行
3527
+ */
3528
+ function rowsOf(registry, query) {
3529
+ return filterCommands(registry.available(), query).map((command) => ({
3530
+ command,
3531
+ chord: chordOf(command.id)
3532
+ }));
3533
+ }
3534
+ /**
3535
+ * 命令栏浮层(未展开/非宿主时渲染 null)。
3536
+ * @author ddj 2026年09月10号
3537
+ * @returns 浮层 React 元素或 null
3538
+ */
3539
+ function CommandPalette() {
3540
+ const open = react.default.useSyncExternalStore(subscribePalette, isPaletteOpen);
3541
+ const openSeq = react.default.useSyncExternalStore(subscribePalette, paletteOpenSeq);
3542
+ const hostRev = react.default.useSyncExternalStore(subscribePalette, paletteHostRev);
3543
+ const registry = useRegistry();
3544
+ const [token, setToken] = react.default.useState(null);
3545
+ const [query, setQuery] = react.default.useState("");
3546
+ const [selected, setSelected] = react.default.useState(0);
3547
+ const [notice, setNotice] = react.default.useState("");
3548
+ const inputRef = react.default.useRef(null);
3549
+ const tokenRef = react.default.useRef(null);
3550
+ const host = token !== null;
3551
+ /**
3552
+ * 认领宿主:放在 effect(不在 render 期做副作用 —— StrictMode 双调用会让首次认领
3553
+ * 被判定失败而永久无人渲染)。hostRev 变化(有实例释放了宿主)时重试认领。
3554
+ *
3555
+ * ⚠️ 此处**不**做 cleanup 释放:releasePaletteHost 会通知订阅者(hostRev 变化),
3556
+ * 若在依赖 hostRev 的 effect 里释放,会「释放→通知→重跑→再释放」自激循环。
3557
+ * 释放统一交给下面的「仅卸载时」effect。
3558
+ */
3559
+ react.default.useEffect(() => {
3560
+ if (tokenRef.current) return;
3561
+ const claimed = claimPaletteHost();
3562
+ if (!claimed) return;
3563
+ tokenRef.current = claimed;
3564
+ setToken(claimed);
3565
+ }, [hostRev]);
3566
+ /** 仅卸载时释放宿主;释放后 store 通知其余实例接管(自愈)。 */
3567
+ react.default.useEffect(() => () => {
3568
+ releasePaletteHost(tokenRef.current);
3569
+ tokenRef.current = null;
3570
+ }, []);
3571
+ react.default.useEffect(() => {
3572
+ if (!open) return;
3573
+ setQuery("");
3574
+ setSelected(0);
3575
+ setNotice("");
3576
+ }, [open]);
3577
+ react.default.useEffect(() => {
3578
+ if (!open) return void 0;
3579
+ const timer = setTimeout(() => {
3580
+ const input = inputRef.current;
3581
+ if (!input) return;
3582
+ input.focus();
3583
+ input.select?.();
3584
+ }, 0);
3585
+ return () => clearTimeout(timer);
3586
+ }, [open, openSeq]);
3587
+ const rows = react.default.useMemo(() => open && host ? rowsOf(registry, query) : [], [
3588
+ open,
3589
+ host,
3590
+ registry,
3591
+ query
3592
+ ]);
3593
+ const active = rows.length ? Math.min(selected, rows.length - 1) : 0;
3594
+ react.default.useEffect(() => {
3595
+ if (!open) return;
3596
+ const node = document.querySelector(".edrv-palette-row.edrv-palette-sel");
3597
+ const scroll = node?.scrollIntoView;
3598
+ if (typeof scroll === "function") scroll.call(node, { block: "nearest" });
3599
+ }, [
3600
+ open,
3601
+ active,
3602
+ rows.length
3603
+ ]);
3604
+ /** 执行候选行(执行器内部会做可用性校验与异常上报)。 */
3605
+ const execute = (row) => {
3606
+ if (!row) return;
3607
+ closeCommandPalette();
3608
+ if (!runPaletteCommand(row.command.id)) setNotice("命令未执行:" + row.command.label);
3609
+ };
3610
+ const onKeyDown = (event) => {
3611
+ event.stopPropagation();
3612
+ if (event.key === "Escape") {
3613
+ event.preventDefault();
3614
+ closeCommandPalette();
3615
+ return;
3616
+ }
3617
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
3618
+ event.preventDefault();
3619
+ if (!rows.length) return;
3620
+ const step = event.key === "ArrowDown" ? 1 : rows.length - 1;
3621
+ setSelected((value) => (Math.min(value, rows.length - 1) + step) % rows.length);
3622
+ return;
3623
+ }
3624
+ if (event.key === "Enter") {
3625
+ event.preventDefault();
3626
+ execute(rows[active]);
3627
+ }
3628
+ };
3629
+ if (!open || !host || typeof document === "undefined") return null;
3630
+ const list = rows.length ? rows.map((row, index) => react.default.createElement("div", {
3631
+ key: row.command.id,
3632
+ className: "edrv-palette-row" + (index === active ? " edrv-palette-sel" : ""),
3633
+ onMouseEnter: () => setSelected(index),
3634
+ onClick: () => execute(row)
3635
+ }, react.default.createElement("span", { className: "edrv-palette-label" }, row.command.label), react.default.createElement("span", { className: "edrv-palette-cat" }, row.command.category), row.chord ? react.default.createElement("kbd", { className: "edrv-palette-key" }, row.chord) : null)) : react.default.createElement("div", { className: "edrv-palette-empty" }, "无匹配命令");
3636
+ const panel = react.default.createElement("div", {
3637
+ className: "edrv-palette",
3638
+ "data-edrv-palette": "1",
3639
+ onKeyDown
3640
+ }, react.default.createElement("input", {
3641
+ ref: inputRef,
3642
+ className: "edrv-palette-input",
3643
+ value: query,
3644
+ placeholder: "输入命令名称…(↑↓ 选择 · Enter 执行 · Esc 关闭)",
3645
+ spellCheck: false,
3646
+ onChange: (event) => {
3647
+ setQuery(event.target.value);
3648
+ setSelected(0);
3649
+ setNotice("");
3650
+ }
3651
+ }), react.default.createElement("div", { className: "edrv-palette-list" }, list), notice ? react.default.createElement("div", { className: "edrv-palette-notice" }, notice) : null);
3652
+ const overlay = react.default.createElement("div", { "data-edrv-view": "1" }, react.default.createElement("div", {
3653
+ className: "edrv-palette-mask",
3654
+ onClick: () => closeCommandPalette()
3655
+ }), panel);
3656
+ return (0, react_dom.createPortal)(overlay, document.body);
3657
+ }
3658
+ //#endregion
2841
3659
  //#region src/client/ui/shared.ts
2842
3660
  /**
2843
3661
  * dsh-vscode-mode client — UI 共用小工具(按钮样式/状态文案/等宽字体)。
@@ -4086,7 +4904,7 @@ window.__ModuleLoader__.load({
4086
4904
  }
4087
4905
  }
4088
4906
  const SYNC_DEBOUNCE_MS = 250;
4089
- let sessionId;
4907
+ let sessionId$1;
4090
4908
  const syncTimers = /* @__PURE__ */ new Map();
4091
4909
  let statusCache = {
4092
4910
  servers: [],
@@ -4095,8 +4913,8 @@ window.__ModuleLoader__.load({
4095
4913
  let progressListeners = /* @__PURE__ */ new Set();
4096
4914
  /** 设置当前会话(切换会话时清理旧文档跟踪由 host 端 session/disposed 兜底)。 */
4097
4915
  function setLspSession(id) {
4098
- if (sessionId === id) return;
4099
- sessionId = id;
4916
+ if (sessionId$1 === id) return;
4917
+ sessionId$1 = id;
4100
4918
  refreshStatus();
4101
4919
  }
4102
4920
  /** 订阅 LSP 状态变化(服务器进度/启动状态)。 */
@@ -4117,7 +4935,7 @@ window.__ModuleLoader__.load({
4117
4935
  if (immediate) {
4118
4936
  clearPending(path);
4119
4937
  await rpc("edrv.lsp.sync", {
4120
- sessionId,
4938
+ sessionId: sessionId$1,
4121
4939
  path,
4122
4940
  text,
4123
4941
  version: Date.now()
@@ -4128,7 +4946,7 @@ window.__ModuleLoader__.load({
4128
4946
  syncTimers.set(path, setTimeout(() => {
4129
4947
  syncTimers.delete(path);
4130
4948
  rpc("edrv.lsp.sync", {
4131
- sessionId,
4949
+ sessionId: sessionId$1,
4132
4950
  path,
4133
4951
  text,
4134
4952
  version: Date.now()
@@ -4146,7 +4964,7 @@ window.__ModuleLoader__.load({
4146
4964
  function closeDoc(path) {
4147
4965
  clearPending(path);
4148
4966
  rpc("edrv.lsp.close", {
4149
- sessionId,
4967
+ sessionId: sessionId$1,
4150
4968
  path
4151
4969
  }).catch(() => {});
4152
4970
  }
@@ -4154,7 +4972,7 @@ window.__ModuleLoader__.load({
4154
4972
  async function findDefinition(path, text, position) {
4155
4973
  await syncDoc(path, text, true);
4156
4974
  const res = await rpc("edrv.lsp.definition", {
4157
- sessionId,
4975
+ sessionId: sessionId$1,
4158
4976
  path,
4159
4977
  position: monoToLsp(position.lineNumber, position.column)
4160
4978
  }).catch(() => null);
@@ -4165,7 +4983,7 @@ window.__ModuleLoader__.load({
4165
4983
  async function findReferences(path, text, position, includeDeclaration = false) {
4166
4984
  await syncDoc(path, text, true);
4167
4985
  const res = await rpc("edrv.lsp.references", {
4168
- sessionId,
4986
+ sessionId: sessionId$1,
4169
4987
  path,
4170
4988
  position: monoToLsp(position.lineNumber, position.column),
4171
4989
  includeDeclaration
@@ -4177,7 +4995,7 @@ window.__ModuleLoader__.load({
4177
4995
  async function fetchDocumentSymbols(path, text) {
4178
4996
  await syncDoc(path, text, true);
4179
4997
  const res = await rpc("edrv.lsp.documentSymbol", {
4180
- sessionId,
4998
+ sessionId: sessionId$1,
4181
4999
  path
4182
5000
  }).catch(() => null);
4183
5001
  if (!res || !res.ok || !Array.isArray(res.symbols)) return [];
@@ -4187,7 +5005,7 @@ window.__ModuleLoader__.load({
4187
5005
  async function fetchHover(path, text, position) {
4188
5006
  await syncDoc(path, text, true);
4189
5007
  const res = await rpc("edrv.lsp.hover", {
4190
- sessionId,
5008
+ sessionId: sessionId$1,
4191
5009
  path,
4192
5010
  position: monoToLsp(position.lineNumber, position.column)
4193
5011
  }).catch(() => null);
@@ -4198,7 +5016,7 @@ window.__ModuleLoader__.load({
4198
5016
  async function fetchSemanticTokens(path, text) {
4199
5017
  await syncDoc(path, text, true);
4200
5018
  const res = await rpc("edrv.lsp.semanticTokens", {
4201
- sessionId,
5019
+ sessionId: sessionId$1,
4202
5020
  path
4203
5021
  }).catch(() => null);
4204
5022
  if (!res || !res.ok || !res.tokens || !Array.isArray(res.tokens.data)) return null;
@@ -4207,7 +5025,7 @@ window.__ModuleLoader__.load({
4207
5025
  /** 拉取服务器状态(带 5s 缓存)。 */
4208
5026
  async function refreshStatus(force = false) {
4209
5027
  if (!force && Date.now() - statusCache.at < 1500) return statusCache.servers;
4210
- const res = await rpc("edrv.lsp.status", { sessionId }).catch(() => null);
5028
+ const res = await rpc("edrv.lsp.status", { sessionId: sessionId$1 }).catch(() => null);
4211
5029
  const servers = res && res.ok && Array.isArray(res.servers) ? res.servers : [];
4212
5030
  statusCache = {
4213
5031
  servers,
@@ -4317,7 +5135,7 @@ window.__ModuleLoader__.load({
4317
5135
  tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
4318
5136
  tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS]
4319
5137
  };
4320
- let registered$1 = false;
5138
+ let registered$2 = false;
4321
5139
  const disposables = [];
4322
5140
  /** 目标文件打开并定位(复用现有 openFileAt 的 edrv:open-editor 事件通道)。 */
4323
5141
  function openAt(path, line, column) {
@@ -4334,8 +5152,8 @@ window.__ModuleLoader__.load({
4334
5152
  }
4335
5153
  /** 注册全部 Monaco LSP provider 与文档跟踪(幂等)。 */
4336
5154
  function registerLspProviders(monaco) {
4337
- if (registered$1) return;
4338
- registered$1 = true;
5155
+ if (registered$2) return;
5156
+ registered$2 = true;
4339
5157
  const attachModel = (model) => {
4340
5158
  if (!model || model.uri.scheme !== "edrv") return;
4341
5159
  const path = pathOfModel(model);
@@ -4580,7 +5398,7 @@ window.__ModuleLoader__.load({
4580
5398
  * 数据来源于语义 token(每 model+version 缓存 60s),不做逐词 LSP 查询。
4581
5399
  * 作者 ddj 2026-09-02
4582
5400
  */
4583
- const CACHE_TTL_MS$1 = 6e4;
5401
+ const CACHE_TTL_MS$2 = 6e4;
4584
5402
  /**
4585
5403
  * 给一个 Monaco 编辑器绑定 Ctrl+hover 下划线提示(幂等)。
4586
5404
  * @author ddj 2026年09月02号
@@ -4610,7 +5428,7 @@ window.__ModuleLoader__.load({
4610
5428
  const rangesOf = async (model) => {
4611
5429
  const path = pathOfModel(model);
4612
5430
  const version = model.getVersionId?.() ?? 0;
4613
- if (cache.path === path && cache.version === version && cache.ranges && Date.now() - cache.at < CACHE_TTL_MS$1) return cache.ranges;
5431
+ if (cache.path === path && cache.version === version && cache.ranges && Date.now() - cache.at < CACHE_TTL_MS$2) return cache.ranges;
4614
5432
  const res = await fetchSemanticTokens(path, model.getValue()).catch(() => null);
4615
5433
  const ranges = res && Array.isArray(res.data) ? decodeSemanticTokens(res.data) : [];
4616
5434
  cache = {
@@ -4753,12 +5571,12 @@ window.__ModuleLoader__.load({
4753
5571
  /** 缓存容量上限(LRU 超限即整体清空;键空间小,够用)。 */
4754
5572
  const CACHE_MAX = 30;
4755
5573
  /** 缓存 TTL(毫秒)。 */
4756
- const CACHE_TTL_MS = 6e4;
4757
- let registered = false;
5574
+ const CACHE_TTL_MS$1 = 6e4;
5575
+ let registered$1 = false;
4758
5576
  let seq = 0;
4759
5577
  let inFlight = null;
4760
5578
  let debounceTimer = null;
4761
- const cache = /* @__PURE__ */ new Map();
5579
+ const cache$1 = /* @__PURE__ */ new Map();
4762
5580
  /** 上次开关状态(configUpdate 后经事件刷新,关闭时立即静默)。 */
4763
5581
  let enabled = false;
4764
5582
  /** 供设置面板保存后同步开关状态(不重载页面即时生效)。 */
@@ -4767,7 +5585,7 @@ window.__ModuleLoader__.load({
4767
5585
  emitAiStatus("config", { enabled });
4768
5586
  if (!enabled) {
4769
5587
  seq += 1;
4770
- cache.clear();
5588
+ cache$1.clear();
4771
5589
  }
4772
5590
  }
4773
5591
  /** 当前开关(EditorView 装配时决定是否注册)。 */
@@ -4808,8 +5626,8 @@ window.__ModuleLoader__.load({
4808
5626
  * @param monaco window.monaco
4809
5627
  */
4810
5628
  function registerAiInline(monaco) {
4811
- if (registered || !monaco?.languages?.registerInlineCompletionsProvider) return;
4812
- registered = true;
5629
+ if (registered$1 || !monaco?.languages?.registerInlineCompletionsProvider) return;
5630
+ registered$1 = true;
4813
5631
  monaco.languages.registerInlineCompletionsProvider("*", {
4814
5632
  async provideInlineCompletions(model, position, context, token) {
4815
5633
  const t0 = Date.now();
@@ -4823,8 +5641,8 @@ window.__ModuleLoader__.load({
4823
5641
  const trimmed = trimInlineWindow(value.slice(0, offset), value.slice(offset));
4824
5642
  if (!inlineWorth(trimmed.prefix)) return { items: [] };
4825
5643
  const key = cacheKey(path, position.lineNumber, position.column, trimmed.prefix);
4826
- const hit = cache.get(key);
4827
- if (hit && Date.now() - hit.at < CACHE_TTL_MS) return hit.items.length ? { items: hit.items } : { items: [] };
5644
+ const hit = cache$1.get(key);
5645
+ if (hit && Date.now() - hit.at < CACHE_TTL_MS$1) return hit.items.length ? { items: hit.items } : { items: [] };
4828
5646
  return new Promise((resolve) => {
4829
5647
  seq += 1;
4830
5648
  const mySeq = seq;
@@ -4859,7 +5677,7 @@ window.__ModuleLoader__.load({
4859
5677
  signal: controller.signal
4860
5678
  })).json();
4861
5679
  if (mySeq !== seq || !data?.ok || typeof data.text !== "string" || !data.text) {
4862
- cache.set(key, {
5680
+ cache$1.set(key, {
4863
5681
  items: [],
4864
5682
  at: Date.now()
4865
5683
  });
@@ -4897,25 +5715,570 @@ window.__ModuleLoader__.load({
4897
5715
  freeInlineCompletions() {}
4898
5716
  });
4899
5717
  }
4900
- /** 缓存写入(超限整体清空,LRU 简化)。 */
4901
- function cacheSet(key, items) {
4902
- if (cache.size >= CACHE_MAX) cache.clear();
4903
- cache.set(key, {
4904
- items,
4905
- at: Date.now()
4906
- });
5718
+ /** 缓存写入(超限整体清空,LRU 简化)。 */
5719
+ function cacheSet(key, items) {
5720
+ if (cache$1.size >= CACHE_MAX) cache$1.clear();
5721
+ cache$1.set(key, {
5722
+ items,
5723
+ at: Date.now()
5724
+ });
5725
+ }
5726
+ /** EditorView ref 注入口(差异静默判定用当前编辑器)。 */
5727
+ let monacoRef = null;
5728
+ /** EditorView 装配后调用(注册 provider + 初始化开关)。 */
5729
+ function setupAiInline(monaco) {
5730
+ monacoRef = monaco && { activeEditor: null };
5731
+ if (!registered$1) registerAiInline(monaco);
5732
+ initAiInlineState();
5733
+ }
5734
+ /** EditorView 每次创建/切换编辑器实例时刷新引用。 */
5735
+ function trackAiEditor(editor) {
5736
+ if (monacoRef) monacoRef.activeEditor = editor;
5737
+ }
5738
+ //#endregion
5739
+ //#region src/client/snippets/provider.ts
5740
+ /**
5741
+ * dsh-vscode-mode client — 代码片段补全 provider(Monaco IntelliSense)。
5742
+ * 数据源:host snippets.entries(全局 ~/.dsh/snippets + 当前工作区 <工作区>/.dsh/snippets),
5743
+ * 条目按 model 语言过滤(language 空串 = 全语言);条目主体交给 Monaco 片段语法解析
5744
+ * (${1:占位} / $TM_FILENAME 等由 InsertAsSnippet 规则展开)。
5745
+ * 性能:条目模块级缓存 + TTL,不在每次按键打 RPC(与 ai/inlineProvider 的去抖策略异曲同工,
5746
+ * 但片段是本地小表,缓存足矣);edrv:snippets-changed(配置文件保存后)强制失效。
5747
+ * 作者 ddj 2026年09月10号
5748
+ */
5749
+ /** 条目缓存 TTL(毫秒):片段文件本就只在用户编辑时变化。 */
5750
+ const CACHE_TTL_MS = 1e4;
5751
+ /** 补全候选上限(超大片段库下防拖慢渲染)。 */
5752
+ const MAX_ITEMS = 200;
5753
+ /** 已加载条目缓存(null = 未加载/已失效)。 */
5754
+ let cache = null;
5755
+ let cacheAt = 0;
5756
+ let pending = null;
5757
+ let registered = false;
5758
+ /** 当前会话 id(补全按会话工作区叠加项目片段;EditorView 装配时注入)。 */
5759
+ let sessionId = null;
5760
+ /**
5761
+ * 设置当前会话(会话切换时更新;缓存一并失效,使项目片段跟随工作区)。
5762
+ * @author ddj 2026年09月10号
5763
+ * @param id 会话 id
5764
+ */
5765
+ function setSnippetsSession(id) {
5766
+ if (sessionId === id) return;
5767
+ sessionId = id;
5768
+ invalidateSnippets();
5769
+ }
5770
+ /**
5771
+ * 失效条目缓存(配置文件保存/会话切换后调用)。
5772
+ * @author ddj 2026年09月10号
5773
+ */
5774
+ function invalidateSnippets() {
5775
+ cache = null;
5776
+ cacheAt = 0;
5777
+ pending = null;
5778
+ }
5779
+ /**
5780
+ * 读取条目(带缓存与在飞去重);失败返回空表(补全静默降级,不打断输入)。
5781
+ * @author ddj 2026年09月10号
5782
+ * @returns 片段条目数组
5783
+ */
5784
+ async function loadEntries() {
5785
+ if (cache && Date.now() - cacheAt < CACHE_TTL_MS) return cache;
5786
+ if (pending) return pending;
5787
+ pending = rpc("snippets.entries", { sessionId }).then((res) => {
5788
+ const entries = res && res.ok && Array.isArray(res.entries) ? res.entries : [];
5789
+ cache = entries;
5790
+ cacheAt = Date.now();
5791
+ return entries;
5792
+ }).catch(() => {
5793
+ cache = [];
5794
+ cacheAt = Date.now();
5795
+ return cache;
5796
+ }).finally(() => {
5797
+ pending = null;
5798
+ });
5799
+ return pending;
5800
+ }
5801
+ /**
5802
+ * 按语言过滤条目:条目语言为空(全语言)或与 model 语言一致即命中。
5803
+ * 项目片段(scope=project)排在全局之后去重,同名同前缀时项目优先。
5804
+ * @author ddj 2026年09月10号
5805
+ * @param entries 全部条目
5806
+ * @param languageId 当前模型语言
5807
+ * @returns 生效条目(项目优先,已去重)
5808
+ */
5809
+ function entriesForLanguage(entries, languageId) {
5810
+ const lang = String(languageId ?? "").toLowerCase();
5811
+ const global = [];
5812
+ const project = [];
5813
+ for (const entry of entries) {
5814
+ const entryLang = String(entry?.language ?? "").toLowerCase();
5815
+ if (entryLang && entryLang !== lang) continue;
5816
+ if (entry?.scope === "project") project.push(entry);
5817
+ else global.push(entry);
5818
+ }
5819
+ const projectKeys = new Set(project.map((entry) => entry.key));
5820
+ return global.filter((entry) => !projectKeys.has(entry.key)).concat(project).slice(0, MAX_ITEMS);
5821
+ }
5822
+ /**
5823
+ * 注册代码片段补全 provider(幂等;Monaco 就绪后调用一次)。
5824
+ * @author ddj 2026年09月10号
5825
+ * @param monaco window.monaco
5826
+ */
5827
+ function registerSnippetProvider(monaco) {
5828
+ if (registered || !monaco?.languages?.registerCompletionItemProvider) return;
5829
+ const snippetKind = monaco.languages.CompletionItemKind?.Snippet;
5830
+ const asSnippet = monaco.languages.CompletionItemInsertTextRule?.InsertAsSnippet;
5831
+ if (typeof snippetKind !== "number" || typeof asSnippet !== "number") return;
5832
+ registered = true;
5833
+ monaco.languages.registerCompletionItemProvider("*", { async provideCompletionItems(model, position) {
5834
+ const entries = await loadEntries();
5835
+ if (!entries.length) return { suggestions: [] };
5836
+ const items = entriesForLanguage(entries, model?.getLanguageId?.());
5837
+ if (!items.length) return { suggestions: [] };
5838
+ const word = model.getWordUntilPosition(position);
5839
+ const range = {
5840
+ startLineNumber: position.lineNumber,
5841
+ endLineNumber: position.lineNumber,
5842
+ startColumn: word.startColumn,
5843
+ endColumn: word.endColumn
5844
+ };
5845
+ return { suggestions: items.map((entry) => ({
5846
+ label: entry.prefix || entry.key,
5847
+ insertText: entry.body,
5848
+ detail: (entry.scope === "project" ? "项目片段 · " : "全局片段 · ") + entry.file,
5849
+ documentation: entry.description || entry.key,
5850
+ kind: snippetKind,
5851
+ insertTextRules: asSnippet,
5852
+ filterText: entry.prefix || entry.key,
5853
+ range
5854
+ })).filter((item) => Boolean(item.label)) };
5855
+ } });
5856
+ }
5857
+ /**
5858
+ * 装配:Monaco 就绪后注册 provider(幂等)。
5859
+ * @author ddj 2026年09月10号
5860
+ * @param monaco window.monaco
5861
+ */
5862
+ function setupSnippets(monaco) {
5863
+ registerSnippetProvider(monaco);
5864
+ }
5865
+ /**
5866
+ * 读取可插入条目(「插入代码片段」命令用;含无前缀条目)。
5867
+ * @author ddj 2026年09月10号
5868
+ * @param languageId 当前模型语言(缺省返回全语言 + 该语言条目)
5869
+ * @returns 生效条目数组
5870
+ */
5871
+ async function listSnippetsFor(languageId) {
5872
+ const entries = await loadEntries();
5873
+ if (!languageId) return entries.slice(0, MAX_ITEMS);
5874
+ return entriesForLanguage(entries, languageId);
5875
+ }
5876
+ //#endregion
5877
+ //#region src/shared/snippets.ts
5878
+ /** 片段文件后缀。 */
5879
+ const SNIPPET_EXT = ".code-snippets";
5880
+ /**
5881
+ * 语言 id → 默认片段文件名(空语言回退 global)。
5882
+ * @author ddj 2026年09月10号
5883
+ * @param language 语言 id
5884
+ * @returns 文件名(如 `lua.code-snippets`)
5885
+ */
5886
+ function snippetFileNameFor(language) {
5887
+ return (String(language ?? "").trim().toLowerCase() || "global") + SNIPPET_EXT;
5888
+ }
5889
+ /**
5890
+ * 归一化新建文件名:用户输入可为裸语言名(补后缀)或完整文件名;空输入为空串。
5891
+ * @author ddj 2026年09月10号
5892
+ * @param raw 用户输入
5893
+ * @returns 归一化后的文件名
5894
+ */
5895
+ function normalizeSnippetFileName(raw) {
5896
+ const text = String(raw ?? "").trim();
5897
+ if (!text) return "";
5898
+ return text.toLowerCase().endsWith(".code-snippets") ? text : text + SNIPPET_EXT;
5899
+ }
5900
+ /**
5901
+ * 新建片段文件模板(一条示例,改 body 即可用)。
5902
+ * @author ddj 2026年09月10号
5903
+ * @param language 目标语言 id(空串 = 全局)
5904
+ * @returns JSON 文本(带尾换行)
5905
+ */
5906
+ function snippetFileTemplate(language) {
5907
+ const lang = String(language ?? "").trim().toLowerCase();
5908
+ const sample = (lang || "global") + " 示例片段";
5909
+ return JSON.stringify({ [sample]: {
5910
+ prefix: "hello",
5911
+ body: ["// " + (lang || "global") + " 片段示例", "$1"],
5912
+ description: "示例:输入 hello 后 Tab 展开"
5913
+ } }, null, 2) + "\n";
4907
5914
  }
4908
- /** EditorView ref 注入口(差异静默判定用当前编辑器)。 */
4909
- let monacoRef = null;
4910
- /** EditorView 装配后调用(注册 provider + 初始化开关)。 */
4911
- function setupAiInline(monaco) {
4912
- monacoRef = monaco && { activeEditor: null };
4913
- if (!registered) registerAiInline(monaco);
4914
- initAiInlineState();
5915
+ /**
5916
+ * 可绑定语言目录(新建片段时的语言下拉来源;与 client/monaco/loader 的 LANG_BY_EXT
5917
+ * 取值集合保持一致)。此处冗余一份纯数据,使本模块不依赖浏览器端 Monaco
5918
+ * (shared 禁 import client 侧模块);一致性由 tests/snippetLanguage.test.ts 断言兜底。
5919
+ */
5920
+ const SNIPPET_LANGUAGES = [
5921
+ "c",
5922
+ "cpp",
5923
+ "csharp",
5924
+ "css",
5925
+ "dart",
5926
+ "dockerfile",
5927
+ "go",
5928
+ "html",
5929
+ "ini",
5930
+ "java",
5931
+ "javascript",
5932
+ "json",
5933
+ "jsonc",
5934
+ "kotlin",
5935
+ "less",
5936
+ "lua",
5937
+ "markdown",
5938
+ "mdx",
5939
+ "php",
5940
+ "plaintext",
5941
+ "powershell",
5942
+ "python",
5943
+ "ruby",
5944
+ "rust",
5945
+ "scss",
5946
+ "shell",
5947
+ "sql",
5948
+ "swift",
5949
+ "typescript",
5950
+ "xml",
5951
+ "yaml"
5952
+ ];
5953
+ /** 语言 id → 展示名(仅收录写法与 id 明显不同的;未收录的原样返回)。 */
5954
+ const LANGUAGE_LABELS = {
5955
+ cpp: "C++",
5956
+ csharp: "C#",
5957
+ javascript: "JavaScript",
5958
+ typescript: "TypeScript",
5959
+ jsonc: "JSON with Comments",
5960
+ powershell: "PowerShell",
5961
+ plaintext: "纯文本"
5962
+ };
5963
+ /**
5964
+ * 语言 id 的人类可读名(用于下拉与「新建 xx 代码片段文件」文案)。
5965
+ * @author ddj 2026年09月10号
5966
+ * @param id 语言 id(空串表示全语言)
5967
+ * @returns 展示名
5968
+ */
5969
+ function languageLabelOf(id) {
5970
+ const key = String(id ?? "").trim().toLowerCase();
5971
+ if (!key) return "全语言";
5972
+ return LANGUAGE_LABELS[key] ?? key;
4915
5973
  }
4916
- /** EditorView 每次创建/切换编辑器实例时刷新引用。 */
4917
- function trackAiEditor(editor) {
4918
- if (monacoRef) monacoRef.activeEditor = editor;
5974
+ //#endregion
5975
+ //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
5976
+ const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* MCP 管理下的二级子页签:我的 MCP / 项目 MCP / MCP 市场 */\n.vsm-mcp-subhead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:-4px}\n.vsm-mcp-subtabs{display:flex;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#2e2e2e);flex:1}\n.vsm-mcp-subtabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:7px 2px 9px;cursor:pointer;font-size:13px;position:relative}\n.vsm-mcp-subtabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-subtabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-subtabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 顶层 Tab:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 统一线框面板:圆角矩形边框圈住各设置分区,提升可读性。\n 用法:<section class=\"vsm-panel\"> + 可选 <h3 class=\"vsm-panel-title\">。 */\n.vsm-panel{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,transparent);overflow:hidden}\n.vsm-panel-pad{padding:14px 16px}\n.vsm-panel-title{margin:0;padding:10px 16px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8);background:var(--dsw-alias-bg-mask-1,#ffffff0a);border-bottom:1px solid var(--dsw-alias-border-l1,#333)}\n.vsm-panel-body{padding:6px 16px}\n.vsm-panel-body>*:first-child{margin-top:8px}\n.vsm-panel-body>*:last-child{margin-bottom:10px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:14px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}\n.vsm-general-row:last-child{border-bottom:0}\n.vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性条目(面板标题改用 .vsm-panel-title) */\n.vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* keyboard shortcuts page */\n.vsm-kb-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-kb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:8px;background:var(--dsw-alias-bg-layer-1,#161616)}\n.vsm-kb-row.rec{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-kb-label{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;color:var(--dsw-alias-label-primary,#eee);font-size:13px}\n.vsm-kb-warn{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px}\n.vsm-kb-key{min-width:96px;text-align:center;padding:3px 10px;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;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-kb-key.warn{border-color:var(--dsw-alias-state-warn-primary,#e5b93f);color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-key.invalid{border-color:var(--dsw-alias-state-error-primary,#d9534f);color:var(--dsw-alias-state-error-primary,#d9534f)}\n.vsm-kb-rec{font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-kb-actions{display:flex;gap:6px}\n.vsm-kb-actions button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:4px 10px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-kb-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-kb-actions button:disabled{opacity:.55;cursor:default}\n.vsm-kb-actionsbar{display:flex;align-items:center;gap:10px;margin-top:12px}\n.vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-lsp-form input{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.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page>.vsm-panel-body>*:first-child{margin-top:0}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n/* 工作区列表分页器 */\n.vsm-perf-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:4px}\n.vsm-perf-pager-text{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-pager-actions{display:flex;gap:8px}\n.vsm-perf-ws-list{display:flex;flex-direction:column;gap:8px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.vsm-perf-ws-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);cursor:pointer;user-select:none}\n.vsm-perf-ws-head:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-perf-ws-head:focus-visible{outline:1px solid var(--dsw-alias-brand-primary,#6d55ff);outline-offset:-1px}\n.vsm-perf-ws.expanded .vsm-perf-ws-head{border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-caret{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;flex-shrink:0;transition:transform .15s;display:inline-block}\n.vsm-perf-ws-caret.open{transform:rotate(90deg)}\n.vsm-perf-ws .vsm-perf-ws-name{flex:1}\n.vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}\n.vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}\n.vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
5977
+ const tagId = "dsh-vscode-mode/mcp.css";
5978
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
5979
+ const tag = document.createElement("style");
5980
+ tag.dataset.plugin = "dsh-vscode-mode";
5981
+ tag.dataset.pluginCss = tagId;
5982
+ tag.textContent = css;
5983
+ document.head.appendChild(tag);
5984
+ }
5985
+ //#endregion
5986
+ //#region src/client/ui/SnippetsPicker.ts
5987
+ /**
5988
+ * dsh-vscode-mode client — 代码片段选择器(VS Code「Configure Snippets」形态)。
5989
+ *
5990
+ * 形态:**居中浮窗 + 二级弹窗**,复用插件原生模态样式(mcp.css 的 .vsm-mcp-dialog,
5991
+ * 与「添加 MCP」「工作区选择」同源),不自造视觉。
5992
+ * - 一级弹窗 mode='configure':按语言分组的现有片段文件列表(全局 + 当前工作区项目),
5993
+ * 点击在文件编辑界面打开;底部按「当前文件语言」新建,或新建全语言片段。
5994
+ * - 二级弹窗:新建片段文件表单(语言下拉 + 文件名),语言决定默认文件名与写入的绑定语义。
5995
+ * - 一级弹窗 mode='insert':当前语言生效条目列表,点击在光标处按片段语法展开。
5996
+ *
5997
+ * ⚠️ 浮层经 createPortal 挂 body,根节点必须带 `data-edrv-view`(与 CommandPalette /
5998
+ * ContextMenu 一致):editor.css 的浮层样式全部以 `[data-edrv-view]` 作用域限定,
5999
+ * 漏挂会导致样式整块失效、内容以裸流形式铺在页面底部。
6000
+ * 作者 ddj 2026年09月10号
6001
+ */
6002
+ /** 片段文件行的第二行摘要:语言 + 条目数 + 相对路径。 */
6003
+ function fileMeta(info) {
6004
+ return languageLabelOf(info.language) + " · " + info.count + " 条 · [" + info.relHint + info.file + "]";
6005
+ }
6006
+ /**
6007
+ * 语言下拉选项:全语言 + 语言目录(去重、保持目录序)。
6008
+ * @author ddj 2026年09月10号
6009
+ * @param languages 语言 id 目录(缺省用 shared 常量)
6010
+ * @returns 选项数组({ id, label })
6011
+ */
6012
+ function languageOptions(languages) {
6013
+ const ids = Array.isArray(languages) && languages.length ? languages : SNIPPET_LANGUAGES;
6014
+ const seen = /* @__PURE__ */ new Set();
6015
+ const out = [{
6016
+ id: "",
6017
+ label: "全语言(所有文件生效)"
6018
+ }];
6019
+ for (const raw of ids) {
6020
+ const id = String(raw ?? "").trim().toLowerCase();
6021
+ if (!id || seen.has(id)) continue;
6022
+ seen.add(id);
6023
+ out.push({
6024
+ id,
6025
+ label: languageLabelOf(id)
6026
+ });
6027
+ }
6028
+ return out;
6029
+ }
6030
+ /**
6031
+ * 片段选择器(浮窗 / 二级弹窗)。
6032
+ * @param props.mode 'configure'(管理文件)| 'insert'(插入条目)
6033
+ * @param props.sessionId 会话 id
6034
+ * @param props.cwd 当前会话工作区(项目片段分组与新建用)
6035
+ * @param props.language 当前编辑器文件绑定的语言 id(新建默认语言 / 插入过滤)
6036
+ * @param props.languages 可选语言目录(来自 monaco.languages.getLanguages;缺省用内置常量)
6037
+ * @param props.onOpenFile 在编辑界面打开片段文件回调(absPath)
6038
+ * @param props.onInsert 插入片段回调(entry)
6039
+ * @param props.onClose 关闭回调
6040
+ * @returns 浮层 React 元素
6041
+ */
6042
+ function SnippetsPicker(props) {
6043
+ const mode = props?.mode === "insert" ? "insert" : "configure";
6044
+ props?.sessionId;
6045
+ const cwd = props?.cwd;
6046
+ const currentLanguage = String(props?.language ?? "").trim().toLowerCase();
6047
+ const options = react.default.useMemo(() => languageOptions(props?.languages), [props?.languages]);
6048
+ const onOpenFile = props?.onOpenFile;
6049
+ const onInsert = props?.onInsert;
6050
+ const onClose = props?.onClose;
6051
+ const [data, setData] = react.default.useState(null);
6052
+ const [entries, setEntries] = react.default.useState(null);
6053
+ const [error, setError] = react.default.useState("");
6054
+ const [busy, setBusy] = react.default.useState(false);
6055
+ const [draft, setDraft] = react.default.useState(null);
6056
+ const aliveRef = react.default.useRef(true);
6057
+ const isInsert = mode === "insert";
6058
+ /** 拉取数据:管理模式取文件列表,插入模式取当前语言条目。 */
6059
+ const load = react.default.useCallback(() => {
6060
+ if (isInsert) {
6061
+ listSnippetsFor(currentLanguage).then((list) => {
6062
+ if (!aliveRef.current) return;
6063
+ setEntries(list);
6064
+ setError("");
6065
+ }).catch((e) => {
6066
+ if (aliveRef.current) setError("读取代码片段失败:" + String(e));
6067
+ });
6068
+ return;
6069
+ }
6070
+ rpc("snippets.list", {}).then((res) => {
6071
+ if (!aliveRef.current) return;
6072
+ if (res && res.ok) {
6073
+ setData(res);
6074
+ setError("");
6075
+ } else setError(res?.error ?? "读取代码片段失败");
6076
+ }).catch((e) => {
6077
+ if (aliveRef.current) setError("读取代码片段失败:" + String(e));
6078
+ });
6079
+ }, [isInsert, currentLanguage]);
6080
+ react.default.useEffect(() => {
6081
+ aliveRef.current = true;
6082
+ load();
6083
+ return () => {
6084
+ aliveRef.current = false;
6085
+ };
6086
+ }, [load]);
6087
+ react.default.useEffect(() => {
6088
+ const onKey = (event) => {
6089
+ if (event.key !== "Escape") return;
6090
+ if (draft) closeDraft();
6091
+ else onClose?.();
6092
+ };
6093
+ window.addEventListener("keydown", onKey, true);
6094
+ return () => window.removeEventListener("keydown", onKey, true);
6095
+ }, [draft, onClose]);
6096
+ /** 打开二级弹窗(按 scope + 语言新建)。 */
6097
+ const openDraft = (scope, language) => {
6098
+ setDraft({
6099
+ scope,
6100
+ language,
6101
+ fileName: snippetFileNameFor(language),
6102
+ edited: false
6103
+ });
6104
+ setError("");
6105
+ };
6106
+ /** 关闭二级弹窗(回到一级浮窗)。 */
6107
+ const closeDraft = () => {
6108
+ setDraft(null);
6109
+ setError("");
6110
+ };
6111
+ /** 改语言:文件名未手改过则跟随语言联动(手改过则尊重用户输入)。 */
6112
+ const pickLanguage = (language) => {
6113
+ setDraft((prev) => prev ? {
6114
+ ...prev,
6115
+ language,
6116
+ fileName: prev.edited ? prev.fileName : snippetFileNameFor(language)
6117
+ } : prev);
6118
+ };
6119
+ /** 改文件名:标记为手改,之后不再被语言联动覆盖。 */
6120
+ const editFileName = (value) => {
6121
+ setDraft((prev) => prev ? {
6122
+ ...prev,
6123
+ fileName: value,
6124
+ edited: true
6125
+ } : prev);
6126
+ };
6127
+ /**
6128
+ * 创建并打开片段文件(二级弹窗提交)。
6129
+ * @author ddj 2026年09月10号
6130
+ */
6131
+ const createFile = () => {
6132
+ if (!draft) return;
6133
+ const name = normalizeSnippetFileName(draft.fileName);
6134
+ if (!name) {
6135
+ setError("文件名不能为空");
6136
+ return;
6137
+ }
6138
+ if (draft.scope === "project" && !cwd) {
6139
+ setError("当前会话没有工作区,无法新建项目片段");
6140
+ return;
6141
+ }
6142
+ setBusy(true);
6143
+ setError("");
6144
+ rpc("snippets.save", {
6145
+ scope: draft.scope,
6146
+ workspacePath: draft.scope === "project" ? cwd : void 0,
6147
+ file: name,
6148
+ content: snippetFileTemplate(draft.language)
6149
+ }).then((res) => {
6150
+ if (!aliveRef.current) return;
6151
+ if (!res || !res.ok) {
6152
+ setError(res?.error ?? "新建失败");
6153
+ return;
6154
+ }
6155
+ invalidateSnippets();
6156
+ closeDraft();
6157
+ onOpenFile?.(res.file.absPath);
6158
+ onClose?.();
6159
+ }).catch((e) => {
6160
+ if (aliveRef.current) setError("新建失败:" + String(e));
6161
+ }).finally(() => {
6162
+ if (aliveRef.current) setBusy(false);
6163
+ });
6164
+ };
6165
+ /** 打开片段文件(在编辑界面打开,浮窗关闭)。 */
6166
+ const openFile = (info) => {
6167
+ onOpenFile?.(info.absPath);
6168
+ onClose?.();
6169
+ };
6170
+ /** 删除片段文件(confirm 后走 snippets.remove)。 */
6171
+ const removeFile = (info) => {
6172
+ if (!window.confirm("删除代码片段文件 " + info.file + "?(不可恢复)")) return;
6173
+ rpc("snippets.remove", {
6174
+ scope: info.scope,
6175
+ workspacePath: info.scope === "project" ? cwd : void 0,
6176
+ file: info.file
6177
+ }).then((res) => {
6178
+ if (!aliveRef.current) return;
6179
+ if (!res || !res.ok) {
6180
+ setError(res?.error ?? "删除失败");
6181
+ return;
6182
+ }
6183
+ invalidateSnippets();
6184
+ load();
6185
+ }).catch((e) => {
6186
+ if (aliveRef.current) setError("删除失败:" + String(e));
6187
+ });
6188
+ };
6189
+ /** 一级浮窗主体:列表 / 空态 / 错误。 */
6190
+ const renderBody = () => {
6191
+ if (error && !draft) return react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error);
6192
+ if (isInsert) {
6193
+ const list = entries ?? [];
6194
+ if (!list.length) return react.default.createElement("div", { className: "vsm-mcp-empty" }, entries === null ? "正在读取代码片段…" : "当前文件没有可用代码片段,可用「代码片段:配置代码片段」新建");
6195
+ return react.default.createElement("div", { className: "edrv-snip-list" }, list.map((entry) => react.default.createElement("button", {
6196
+ key: entry.scope + ":" + entry.file + ":" + entry.key,
6197
+ className: "edrv-snip-row",
6198
+ onClick: () => {
6199
+ onInsert?.(entry);
6200
+ onClose?.();
6201
+ }
6202
+ }, react.default.createElement("span", { className: "edrv-snip-row-main" }, react.default.createElement("span", { className: "edrv-snip-label" }, entry.prefix || entry.key), react.default.createElement("span", { className: "edrv-snip-desc" }, entry.description || entry.key)), react.default.createElement("span", { className: "edrv-snip-src" }, entry.file))));
6203
+ }
6204
+ if (!data) return react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取代码片段…");
6205
+ const project = (data.projects ?? []).find((item) => item.workspacePath === cwd) ?? null;
6206
+ /** 行按语言再按文件名排序,同语言片段聚在一起(便于按文件类型找片段)。 */
6207
+ const sortByLanguage = (list) => [...list].sort((a, b) => String(a.language).localeCompare(String(b.language)) || String(a.file).localeCompare(String(b.file)));
6208
+ const rows = (list) => sortByLanguage(list).map((info) => react.default.createElement("div", {
6209
+ key: info.scope + ":" + info.file,
6210
+ className: "edrv-snip-row"
6211
+ }, react.default.createElement("button", {
6212
+ className: "edrv-snip-row-main",
6213
+ title: info.absPath,
6214
+ onClick: () => openFile(info)
6215
+ }, react.default.createElement("span", { className: "edrv-snip-label" }, info.file), react.default.createElement("span", { className: "edrv-snip-desc" }, fileMeta(info))), info.error ? react.default.createElement("span", { className: "vsm-mcp-chip edrv-snip-warn" }, "JSON 解析失败") : null, react.default.createElement("button", {
6216
+ className: "edrv-snip-act edrv-snip-danger",
6217
+ title: "删除",
6218
+ onClick: () => removeFile(info)
6219
+ }, "⌫")));
6220
+ const globalRows = rows(data.user ?? []);
6221
+ const projectRows = project ? rows(project.files ?? []) : [];
6222
+ return react.default.createElement("div", { className: "edrv-snip-list" }, react.default.createElement("div", { className: "edrv-snip-group" }, "全局代码片段(~/.dsh/snippets/)"), globalRows.length ? globalRows : react.default.createElement("div", { className: "edrv-snip-empty" }, "还没有全局代码片段"), react.default.createElement("div", { className: "edrv-snip-group" }, "项目代码片段" + (cwd ? "(" + cwd + ")" : "")), projectRows.length ? projectRows : react.default.createElement("div", { className: "edrv-snip-empty" }, cwd ? "当前项目没有代码片段" : "当前会话没有工作区"));
6223
+ };
6224
+ /** 当前文件语言提示行(让「片段绑定到哪种文件」一目了然)。 */
6225
+ const languageBar = react.default.createElement("div", { className: "edrv-snip-langbar" }, currentLanguage ? react.default.createElement(react.default.Fragment, null, "当前文件语言:", react.default.createElement("b", null, languageLabelOf(currentLanguage)), "(" + currentLanguage + ")") : "当前没有打开文件:可新建全语言片段,或先打开文件再新建对应语言的片段");
6226
+ const dialog = react.default.createElement("div", {
6227
+ className: "edrv-snip-mask",
6228
+ onClick: () => onClose?.()
6229
+ }, react.default.createElement("div", {
6230
+ className: "vsm-mcp-dialog edrv-snip-dialog",
6231
+ onClick: (event) => event.stopPropagation()
6232
+ }, react.default.createElement("h3", null, isInsert ? "插入代码片段" : "代码片段:配置代码片段"), react.default.createElement("p", { className: "edrv-snip-hint" }, isInsert ? "仅列出对当前文件语言生效的片段(选定后在光标处展开,支持 ${1:占位} 与 $TM_FILENAME 等变量)。" : "代码片段按语言绑定:`<语言>.code-snippets` 只对该类文件生效,`global.code-snippets` 对所有文件生效。选中文件即在编辑界面打开。"), isInsert ? null : languageBar, renderBody(), isInsert ? null : react.default.createElement("div", { className: "vsm-mcp-dialog-actions" }, react.default.createElement("button", {
6233
+ disabled: busy,
6234
+ onClick: () => openDraft("user", "")
6235
+ }, "新建全语言片段…"), react.default.createElement("button", {
6236
+ className: "vsm-primary",
6237
+ disabled: busy || !currentLanguage,
6238
+ title: currentLanguage ? "" : "请先打开一个文件,或改用「新建全语言片段…」",
6239
+ onClick: () => openDraft("user", currentLanguage)
6240
+ }, currentLanguage ? "新建 " + languageLabelOf(currentLanguage) + " 片段文件…" : "新建当前语言片段…"), react.default.createElement("button", {
6241
+ disabled: busy || !cwd,
6242
+ title: cwd ? "" : "当前会话没有工作区",
6243
+ onClick: () => openDraft("project", currentLanguage)
6244
+ }, "新建项目片段文件…"))));
6245
+ const createDialog = draft ? react.default.createElement("div", {
6246
+ className: "edrv-snip-mask edrv-snip-mask-top",
6247
+ onClick: closeDraft
6248
+ }, react.default.createElement("div", {
6249
+ className: "vsm-mcp-dialog edrv-snip-dialog",
6250
+ onClick: (event) => event.stopPropagation()
6251
+ }, react.default.createElement("h3", null, draft.scope === "project" ? "新建项目代码片段文件" : "新建全局代码片段文件"), react.default.createElement("label", null, "生效语言", react.default.createElement("select", {
6252
+ value: draft.language,
6253
+ disabled: busy,
6254
+ onChange: (event) => {
6255
+ pickLanguage(event.target.value);
6256
+ setError("");
6257
+ }
6258
+ }, options.map((item) => react.default.createElement("option", {
6259
+ key: "lang-" + item.id,
6260
+ value: item.id
6261
+ }, item.label)))), react.default.createElement("label", null, "文件名", react.default.createElement("input", {
6262
+ autoFocus: true,
6263
+ spellCheck: false,
6264
+ value: draft.fileName,
6265
+ placeholder: snippetFileNameFor(draft.language),
6266
+ onChange: (event) => {
6267
+ editFileName(event.target.value);
6268
+ setError("");
6269
+ },
6270
+ onKeyDown: (event) => {
6271
+ if (event.key === "Enter") createFile();
6272
+ }
6273
+ })), react.default.createElement("div", { className: "edrv-snip-hint" }, (draft.scope === "project" ? "将写入 " + (cwd ?? "") + "/.dsh/snippets/" : "将写入 ~/.dsh/snippets/") + normalizeSnippetFileName(draft.fileName) + (draft.language ? "(仅对 " + languageLabelOf(draft.language) + " 文件生效)" : "(对所有文件生效)")), error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null, react.default.createElement("div", { className: "vsm-mcp-dialog-actions" }, react.default.createElement("button", {
6274
+ disabled: busy,
6275
+ onClick: closeDraft
6276
+ }, "取消"), react.default.createElement("button", {
6277
+ className: "vsm-primary",
6278
+ disabled: busy,
6279
+ onClick: createFile
6280
+ }, busy ? "创建中…" : "创建并打开")))) : null;
6281
+ return (0, react_dom.createPortal)(react.default.createElement("div", { "data-edrv-view": "1" }, dialog, createDialog), document.body);
4919
6282
  }
4920
6283
  //#endregion
4921
6284
  //#region src/client/ui/EditorView.ts
@@ -4928,6 +6291,22 @@ window.__ModuleLoader__.load({
4928
6291
  * 作者 ddj 2026-08-20
4929
6292
  */
4930
6293
  /**
6294
+ * 从 Monaco 语言目录取可选语言 id 列表(代码片段新建时的语言下拉来源)。
6295
+ * 旧版 Monaco / 目录不可读时返回 undefined,由调用方回落 shared 的内置常量。
6296
+ * @author ddj 2026年09月10号
6297
+ * @param monaco window.monaco(可能未加载)
6298
+ * @returns 语言 id 数组或 undefined
6299
+ */
6300
+ function snippetLanguageIds(monaco) {
6301
+ try {
6302
+ const languages = monaco?.languages?.getLanguages?.();
6303
+ if (!Array.isArray(languages) || !languages.length) return void 0;
6304
+ return languages.map((item) => item && typeof item.id === "string" ? item.id : "").filter(Boolean);
6305
+ } catch (error) {
6306
+ return;
6307
+ }
6308
+ }
6309
+ /**
4931
6310
  * 中央编辑区:文件页签(脏点/关闭/打开路径)+ Ctrl+P 搜索 + Monaco 编辑器 +
4932
6311
  * 底部差异条(DiffBox/空态)+ 全局差异下拉(DiffLauncher)+ 状态栏。
4933
6312
  * @param props.sessionId 会话 id
@@ -5017,6 +6396,10 @@ window.__ModuleLoader__.load({
5017
6396
  const navBackRef = react.default.useRef(null);
5018
6397
  const navForwardRef = react.default.useRef(null);
5019
6398
  const cycleTabRef = react.default.useRef(null);
6399
+ const rowNavColRef = react.default.useRef(null);
6400
+ const rowNavMoveRef = react.default.useRef(false);
6401
+ const activeRef = react.default.useRef(null);
6402
+ activeRef.current = active;
5020
6403
  const tabsHostRef = react.default.useRef(null);
5021
6404
  const [navTick, setNavTick] = react.default.useState(0);
5022
6405
  const hoverRegionsRef = react.default.useRef([]);
@@ -5029,6 +6412,7 @@ window.__ModuleLoader__.load({
5029
6412
  const hoverPanelRef = react.default.useRef(false);
5030
6413
  const batchBusyRef = react.default.useRef(false);
5031
6414
  const menuHandlersRef = react.default.useRef(null);
6415
+ const [snippetPicker, setSnippetPicker] = react.default.useState(null);
5032
6416
  const doSaveRef = react.default.useRef(null);
5033
6417
  const onEditRef = react.default.useRef(null);
5034
6418
  const saveViewStateRef = react.default.useRef(null);
@@ -5498,6 +6882,14 @@ window.__ModuleLoader__.load({
5498
6882
  if (aiStatusTimer.current) clearTimeout(aiStatusTimer.current);
5499
6883
  };
5500
6884
  }, []);
6885
+ react.default.useEffect(() => {
6886
+ setSnippetsSession(sessionId);
6887
+ }, [sessionId]);
6888
+ react.default.useEffect(() => {
6889
+ const onChanged = () => invalidateSnippets();
6890
+ window.addEventListener("edrv:snippets-changed", onChanged);
6891
+ return () => window.removeEventListener("edrv:snippets-changed", onChanged);
6892
+ }, []);
5501
6893
  react.default.useEffect(() => {
5502
6894
  const onOpen = (e) => {
5503
6895
  const p = e?.detail?.path;
@@ -5734,6 +7126,112 @@ window.__ModuleLoader__.load({
5734
7126
  window.addEventListener("keydown", onKey, true);
5735
7127
  return () => window.removeEventListener("keydown", onKey, true);
5736
7128
  }, []);
7129
+ /**
7130
+ * 指令系统接线:命令栏/键位/第三方派发的 `edrv.command.*` 事件落到编辑器动作。
7131
+ * 全部动作经最新闭包执行(与窗口级键位监听同源);事件名逐条字面书写,
7132
+ * 便于与指令目录(ui/commandCatalog)静态对照(tests/commands.test.ts 有断言)。
7133
+ * @author ddj 2026年09月10号
7134
+ */
7135
+ react.default.useEffect(() => {
7136
+ /** 整行上下移动:Monaco 内置 cursorUp/Down 会丢列位,故按期望列自行定位(连续移动保持同列)。 */
7137
+ const moveRow = (step) => {
7138
+ const ed = editorRef.current;
7139
+ const model = ed?.getModel?.();
7140
+ const pos = ed?.getPosition?.();
7141
+ if (!ed || !model || !pos) return;
7142
+ const col = rowNavColRef.current ?? pos.column;
7143
+ const line = Math.max(1, Math.min(model.getLineCount(), pos.lineNumber + step));
7144
+ if (line === pos.lineNumber) return;
7145
+ const maxCol = model.getLineMaxColumn(line);
7146
+ rowNavColRef.current = col;
7147
+ rowNavMoveRef.current = true;
7148
+ ed.setPosition({
7149
+ lineNumber: line,
7150
+ column: Math.max(1, Math.min(col, maxCol))
7151
+ });
7152
+ ed.revealLineInCenterIfOutsideViewport?.(line);
7153
+ };
7154
+ const handlers = [
7155
+ ["edrv.command.save", () => {
7156
+ if (editorRef.current?.getModel?.()) {
7157
+ flushSave();
7158
+ doSaveRef.current?.(false);
7159
+ }
7160
+ }],
7161
+ ["edrv.command.toggleSidebar", () => setSidebarOn((v) => !v)],
7162
+ ["edrv.command.searchInFiles", () => {
7163
+ setSidebarOn(true);
7164
+ setActivePanel("search");
7165
+ setTimeout(() => window.dispatchEvent(new CustomEvent("edrv:search-focus")), 0);
7166
+ }],
7167
+ ["edrv.command.navigateBack", () => navBackRef.current?.()],
7168
+ ["edrv.command.navigateForward", () => navForwardRef.current?.()],
7169
+ ["edrv.command.nextTab", () => cycleTabRef.current?.(1)],
7170
+ ["edrv.command.prevTab", () => cycleTabRef.current?.(-1)],
7171
+ ["edrv.command.nextEditorRow", () => moveRow(1)],
7172
+ ["edrv.command.prevEditorRow", () => moveRow(-1)],
7173
+ ["edrv.command.goToDefinition", () => {
7174
+ const ed = editorRef.current;
7175
+ if (ed) runGoToDefinition(ed);
7176
+ }],
7177
+ ["edrv.command.findReferences", () => {
7178
+ const ed = editorRef.current;
7179
+ if (ed) runFindReferences(ed);
7180
+ }],
7181
+ ["edrv.command.triggerAi", () => editorRef.current?.trigger?.("edrv-ai", "editor.action.inlineSuggest.trigger", null)],
7182
+ ["edrv.command.openInExplorer", () => {
7183
+ const path = activeRef.current;
7184
+ if (!path) {
7185
+ setStatus("无活动文件");
7186
+ return;
7187
+ }
7188
+ menuHandlersRef.current?.openInExplorer?.(path);
7189
+ }],
7190
+ ["edrv.command.configureSnippets", () => {
7191
+ closeCommandPalette();
7192
+ setSnippetPicker("configure");
7193
+ }],
7194
+ ["edrv.command.insertSnippet", () => {
7195
+ closeCommandPalette();
7196
+ setSnippetPicker("insert");
7197
+ }],
7198
+ ["edrv.command.addSelectionRef", () => {
7199
+ const sel = editorRef.current?.getSelection?.();
7200
+ const path = activeRef.current;
7201
+ if (!path) {
7202
+ setStatus("无活动文件");
7203
+ return;
7204
+ }
7205
+ if (!sel || sel.startLineNumber === sel.endLineNumber && sel.startColumn === sel.endColumn) {
7206
+ setStatus("请先选中内容再添加引用");
7207
+ return;
7208
+ }
7209
+ const range = {
7210
+ startLine: sel.startLineNumber,
7211
+ endLine: sel.endLineNumber
7212
+ };
7213
+ if (!addToConversation) {
7214
+ setStatus("添加到对话不可用");
7215
+ return;
7216
+ }
7217
+ addToConversation.appendReference(sessionId, path, range).then((o) => setStatus(statusOfAdd(o, "已添加选中内容为引用")));
7218
+ }]
7219
+ ];
7220
+ const byName = new Map(handlers);
7221
+ const onCommand = (event) => {
7222
+ const action = byName.get(event.type);
7223
+ if (!action) return;
7224
+ try {
7225
+ action();
7226
+ } catch (error) {
7227
+ dbg(sessionId, "指令 " + event.type + " 失败:" + String(error));
7228
+ }
7229
+ };
7230
+ for (const [eventName] of handlers) window.addEventListener(eventName, onCommand);
7231
+ return () => {
7232
+ for (const [eventName] of handlers) window.removeEventListener(eventName, onCommand);
7233
+ };
7234
+ }, [sessionId]);
5737
7235
  react.default.useEffect(() => {
5738
7236
  const host = tabsHostRef.current;
5739
7237
  if (!host) return;
@@ -5808,6 +7306,7 @@ window.__ModuleLoader__.load({
5808
7306
  if (!alive) return;
5809
7307
  setMonaco(m);
5810
7308
  setupAiInline(m);
7309
+ setupSnippets(m);
5811
7310
  }).catch((e) => {
5812
7311
  if (alive) {
5813
7312
  setMonacoErr(String(e?.message ?? e));
@@ -5892,6 +7391,7 @@ window.__ModuleLoader__.load({
5892
7391
  setDirtyMap((d) => Object.assign({}, d, { [active]: false }));
5893
7392
  refreshRecords();
5894
7393
  emitRefresh();
7394
+ if (/\.code-snippets$/i.test(active)) window.dispatchEvent(new CustomEvent("edrv:snippets-changed"));
5895
7395
  } else {
5896
7396
  setStatus("保存失败");
5897
7397
  setError(res?.error ? String(res.error) : "保存失败");
@@ -6021,6 +7521,8 @@ window.__ModuleLoader__.load({
6021
7521
  });
6022
7522
  ed.onDidChangeCursorPosition((e) => {
6023
7523
  setCursor("Ln " + e.position.lineNumber + ", Col " + e.position.column);
7524
+ if (rowNavMoveRef.current) rowNavMoveRef.current = false;
7525
+ else rowNavColRef.current = null;
6024
7526
  if (navCursorTimerRef.current) clearTimeout(navCursorTimerRef.current);
6025
7527
  navCursorTimerRef.current = setTimeout(() => {
6026
7528
  navCursorTimerRef.current = null;
@@ -6097,6 +7599,24 @@ window.__ModuleLoader__.load({
6097
7599
  ed.addCommand(m.KeyCode.F12, () => {
6098
7600
  runGoToDefinition(ed);
6099
7601
  });
7602
+ ed.addAction({
7603
+ id: "edrv.prevEditorRow",
7604
+ label: "上一编辑行",
7605
+ contextMenuGroupId: "1_edrv",
7606
+ run: () => window.dispatchEvent(new CustomEvent("edrv.command.prevEditorRow"))
7607
+ });
7608
+ ed.addAction({
7609
+ id: "edrv.nextEditorRow",
7610
+ label: "下一编辑行",
7611
+ contextMenuGroupId: "1_edrv",
7612
+ run: () => window.dispatchEvent(new CustomEvent("edrv.command.nextEditorRow"))
7613
+ });
7614
+ ed.addAction({
7615
+ id: "edrv.showCommands",
7616
+ label: "显示所有命令",
7617
+ contextMenuGroupId: "1_edrv",
7618
+ run: () => window.dispatchEvent(new CustomEvent("edrv.command.showCommands"))
7619
+ });
6100
7620
  bindLspEditor(ed);
6101
7621
  bindLspUnderline(ed, m);
6102
7622
  trackAiEditor(ed);
@@ -6508,6 +8028,31 @@ window.__ModuleLoader__.load({
6508
8028
  addRefToChat,
6509
8029
  openInExplorer
6510
8030
  };
8031
+ /**
8032
+ * 在光标处按片段语法展开插入(Monaco 原生 snippet 控制器解析 ${1:占位} 与 $TM_* 变量)。
8033
+ * @author ddj 2026年09月10号
8034
+ * @param entry 片段条目
8035
+ */
8036
+ const insertSnippetAtCursor = (entry) => {
8037
+ const ed = editorRef.current;
8038
+ if (!ed || !entry) {
8039
+ setStatus("无活动编辑器");
8040
+ return;
8041
+ }
8042
+ try {
8043
+ ed.focus();
8044
+ const controller = ed.getContribution?.("snippetController2");
8045
+ if (controller?.insert) controller.insert(String(entry.body ?? ""));
8046
+ else ed.executeEdits("edrv-snippet", [{
8047
+ range: ed.getSelection(),
8048
+ text: String(entry.body ?? "")
8049
+ }]);
8050
+ setStatus("已插入代码片段:" + (entry.prefix || entry.key));
8051
+ } catch (error) {
8052
+ setStatus("插入代码片段失败");
8053
+ setError("插入代码片段失败:" + String(error));
8054
+ }
8055
+ };
6511
8056
  const openFile = (path, focusDiff) => {
6512
8057
  if (!path) return;
6513
8058
  recordNav();
@@ -6746,7 +8291,7 @@ window.__ModuleLoader__.load({
6746
8291
  });
6747
8292
  }
6748
8293
  }, "重试"));
6749
- else if (!active) body = react.default.createElement("div", { className: "edrv-empty" }, react.default.createElement("div", null, "暂无打开的文件"), react.default.createElement("div", { style: { fontSize: 12 } }, "使用右上搜索框 (" + (chordOf("edrv.quickOpen") ?? "Ctrl+P") + ") 打开工作区文件;agent 修改文件后顶部会出现差异角标"));
8294
+ else if (!active) body = react.default.createElement("div", { className: "edrv-empty" }, react.default.createElement("div", null, "暂无打开的文件"), react.default.createElement("div", { style: { fontSize: 12 } }, "使用右上搜索框 (" + (chordOf("edrv.quickOpen") ?? "Ctrl+P") + ") 打开工作区文件;" + (chordOf("edrv.showCommands") ?? "Ctrl+Shift+P") + " 打开命令栏;agent 修改文件后顶部会出现差异角标"));
6750
8295
  else if (content === null && loadError) body = loadingBody("文件加载失败:" + loadError, 0, () => {
6751
8296
  setLoadError(null);
6752
8297
  setContent(null);
@@ -6901,6 +8446,19 @@ window.__ModuleLoader__.load({
6901
8446
  dismissMenus();
6902
8447
  }
6903
8448
  }, "关闭标签页")) : null;
8449
+ const snippetPickerEl = snippetPicker ? react.default.createElement(SnippetsPicker, {
8450
+ mode: snippetPicker,
8451
+ sessionId,
8452
+ cwd: cwd ?? null,
8453
+ language: active ? snippetLanguageOf(active) : "",
8454
+ languages: snippetLanguageIds(monacoRef.current),
8455
+ onOpenFile: (absPath) => {
8456
+ openFileAt(absPath, 1);
8457
+ setStatus("已打开代码片段文件");
8458
+ },
8459
+ onInsert: insertSnippetAtCursor,
8460
+ onClose: () => setSnippetPicker(null)
8461
+ }) : null;
6904
8462
  const sidebarPanels = props.sidebarPanels;
6905
8463
  const sidebarCtx = {
6906
8464
  sessionId,
@@ -6971,20 +8529,24 @@ window.__ModuleLoader__.load({
6971
8529
  background: "var(--dsw-alias-bg-base,transparent)",
6972
8530
  overflow: "hidden"
6973
8531
  };
8532
+ const paletteEl = react.default.createElement(CommandPalette, {
8533
+ key: "edrv-palette",
8534
+ sessionId
8535
+ });
6974
8536
  return layout === "side" ? react.default.createElement("div", {
6975
8537
  ref: viewRootRef,
6976
8538
  "data-edrv-view": "1",
6977
8539
  "data-edrv-layout": "side",
6978
8540
  className: "edrv-view-side",
6979
8541
  style: Object.assign({}, baseStyle, { height: "100%" })
6980
- }, editorRow, menuBackdrop, tabMenuEl) : react.default.createElement("div", {
8542
+ }, editorRow, menuBackdrop, tabMenuEl, paletteEl, snippetPickerEl) : react.default.createElement("div", {
6981
8543
  ref: viewRootRef,
6982
8544
  "data-edrv-view": "1",
6983
8545
  style: Object.assign({}, baseStyle, {
6984
8546
  height: "var(--edrv-editor-height, 100%)",
6985
8547
  maxHeight: "var(--edrv-editor-height, 100%)"
6986
8548
  })
6987
- }, editorRow, menuBackdrop, tabMenuEl);
8549
+ }, editorRow, menuBackdrop, tabMenuEl, paletteEl, snippetPickerEl);
6988
8550
  }
6989
8551
  //#endregion
6990
8552
  //#region src/client/ui/DiffBadge.ts
@@ -7337,11 +8899,15 @@ window.__ModuleLoader__.load({
7337
8899
  "data-edrv-diff-dock-shell": "1"
7338
8900
  }, content);
7339
8901
  const editorSnapshot = readDiffDock(sessionId);
8902
+ const palette = react.default.createElement(CommandPalette, {
8903
+ key: "edrv-palette",
8904
+ sessionId
8905
+ });
7340
8906
  if (editorSnapshot) {
7341
- if (editorSnapshot.mode === "editor-empty" && !editorSnapshot.fileTotal) return null;
7342
- return renderDock(react.default.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId);
8907
+ if (editorSnapshot.mode === "editor-empty" && !editorSnapshot.fileTotal) return palette;
8908
+ return react.default.createElement(react.default.Fragment, null, renderDock(react.default.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId), palette);
7343
8909
  }
7344
- if (!sessionId || !summary?.pendingFiles?.length) return null;
8910
+ if (!sessionId || !summary?.pendingFiles?.length) return palette;
7345
8911
  const paths = summary.pendingFiles.map((file) => file.path);
7346
8912
  const click = () => {
7347
8913
  const current = nextIndexBySession.get(sessionId) ?? 0;
@@ -7349,23 +8915,12 @@ window.__ModuleLoader__.load({
7349
8915
  nextIndexBySession.set(sessionId, next.index);
7350
8916
  if (next.path) openDiffView(next.path);
7351
8917
  };
7352
- return renderDock(react.default.createElement(DiffBox, {
8918
+ return react.default.createElement(react.default.Fragment, null, renderDock(react.default.createElement(DiffBox, {
7353
8919
  mode: "chat",
7354
8920
  dock: true,
7355
8921
  fileTotal: paths.length,
7356
8922
  onOpenNextFile: click
7357
- }), sessionId || "chat");
7358
- }
7359
- //#endregion
7360
- //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
7361
- const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* MCP 管理下的二级子页签:我的 MCP / 项目 MCP / MCP 市场 */\n.vsm-mcp-subhead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:-4px}\n.vsm-mcp-subtabs{display:flex;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#2e2e2e);flex:1}\n.vsm-mcp-subtabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:7px 2px 9px;cursor:pointer;font-size:13px;position:relative}\n.vsm-mcp-subtabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-subtabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-subtabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 顶层 Tab:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 统一线框面板:圆角矩形边框圈住各设置分区,提升可读性。\n 用法:<section class=\"vsm-panel\"> + 可选 <h3 class=\"vsm-panel-title\">。 */\n.vsm-panel{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,transparent);overflow:hidden}\n.vsm-panel-pad{padding:14px 16px}\n.vsm-panel-title{margin:0;padding:10px 16px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8);background:var(--dsw-alias-bg-mask-1,#ffffff0a);border-bottom:1px solid var(--dsw-alias-border-l1,#333)}\n.vsm-panel-body{padding:6px 16px}\n.vsm-panel-body>*:first-child{margin-top:8px}\n.vsm-panel-body>*:last-child{margin-bottom:10px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:14px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}\n.vsm-general-row:last-child{border-bottom:0}\n.vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性条目(面板标题改用 .vsm-panel-title) */\n.vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* keyboard shortcuts page */\n.vsm-kb-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-kb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:8px;background:var(--dsw-alias-bg-layer-1,#161616)}\n.vsm-kb-row.rec{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-kb-label{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;color:var(--dsw-alias-label-primary,#eee);font-size:13px}\n.vsm-kb-warn{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px}\n.vsm-kb-key{min-width:96px;text-align:center;padding:3px 10px;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;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-kb-key.warn{border-color:var(--dsw-alias-state-warn-primary,#e5b93f);color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-key.invalid{border-color:var(--dsw-alias-state-error-primary,#d9534f);color:var(--dsw-alias-state-error-primary,#d9534f)}\n.vsm-kb-rec{font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-kb-actions{display:flex;gap:6px}\n.vsm-kb-actions button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:4px 10px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-kb-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-kb-actions button:disabled{opacity:.55;cursor:default}\n.vsm-kb-actionsbar{display:flex;align-items:center;gap:10px;margin-top:12px}\n.vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-lsp-form input{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.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page>.vsm-panel-body>*:first-child{margin-top:0}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n/* 工作区列表分页器 */\n.vsm-perf-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:4px}\n.vsm-perf-pager-text{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-pager-actions{display:flex;gap:8px}\n.vsm-perf-ws-list{display:flex;flex-direction:column;gap:8px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.vsm-perf-ws-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);cursor:pointer;user-select:none}\n.vsm-perf-ws-head:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-perf-ws-head:focus-visible{outline:1px solid var(--dsw-alias-brand-primary,#6d55ff);outline-offset:-1px}\n.vsm-perf-ws.expanded .vsm-perf-ws-head{border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-caret{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;flex-shrink:0;transition:transform .15s;display:inline-block}\n.vsm-perf-ws-caret.open{transform:rotate(90deg)}\n.vsm-perf-ws .vsm-perf-ws-name{flex:1}\n.vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}\n.vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}\n.vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
7362
- const tagId = "dsh-vscode-mode/mcp.css";
7363
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
7364
- const tag = document.createElement("style");
7365
- tag.dataset.plugin = "dsh-vscode-mode";
7366
- tag.dataset.pluginCss = tagId;
7367
- tag.textContent = css;
7368
- document.head.appendChild(tag);
8923
+ }), sessionId || "chat"), palette);
7369
8924
  }
7370
8925
  //#endregion
7371
8926
  //#region src/client/compat.ts
@@ -8130,7 +9685,7 @@ window.__ModuleLoader__.load({
8130
9685
  onClick: () => resetRow(command.id)
8131
9686
  }, "恢复"))));
8132
9687
  });
8133
- return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "快捷键"), react.default.createElement("p", null, "配置编辑器视图的快捷键(保存 / 快速打开 / 侧边栏 / 搜索 / 后退 / 前进)。修改即时写入配置,重新聚焦编辑器后生效。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用默认键位。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("div", { className: "vsm-kb-message" }, message), react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "编辑器键位"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-kb-list" }, rows)), react.default.createElement("div", { className: "vsm-panel-body vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
9688
+ return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "快捷键"), react.default.createElement("p", null, "配置编辑器视图的快捷键(命令栏 / 保存 / 快速打开 / 侧边栏 / 搜索 / 后退前进 / 页签与编辑行导航)。修改即时写入配置,重新聚焦编辑器后生效;命令栏(默认 Ctrl+Shift+P 或 F1)可搜索并执行下列全部命令。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用默认键位。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("div", { className: "vsm-kb-message" }, message), react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "编辑器键位"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-kb-list" }, rows)), react.default.createElement("div", { className: "vsm-panel-body vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
8134
9689
  className: "vsm-primary vsm-small",
8135
9690
  disabled: unavailable || notReady || busy || snapshot?.writable === false || !dirty,
8136
9691
  onClick: save
@@ -13391,6 +14946,65 @@ window.__ModuleLoader__.load({
13391
14946
  return out;
13392
14947
  }
13393
14948
  //#endregion
14949
+ //#region src/client/commandBridge.ts
14950
+ /**
14951
+ * dsh-vscode-mode client — 指令执行桥(装配层)。
14952
+ * 职责三件:① 把内置指令目录注册进注册表;② 为「桥接派发」类指令挂窗口 capture 键位监听
14953
+ * (EditorView 原生监听的 8 条不进这里,避免双执行);③ 注入命令栏执行器。
14954
+ * 编辑器动作本身由 EditorView / QuickOpen 监听 `edrv.command.<action>` 窗口事件完成——桥不持有
14955
+ * React 状态,因此指令系统可在编辑器挂载前装配、卸载后仍安全。
14956
+ * 作者 ddj 2026年09月10号
14957
+ */
14958
+ const bridgeLog = log.child("commands");
14959
+ /** 解析事件目标(缺省 window;无 window 返回 null)。 */
14960
+ function resolveTarget(options) {
14961
+ if (options.target) return options.target;
14962
+ if (typeof window === "undefined") return null;
14963
+ return window;
14964
+ }
14965
+ /**
14966
+ * 创建指令执行桥:注册内置指令 + 桥接类指令的全局键位监听。
14967
+ * @author ddj 2026年09月10号
14968
+ * @param options 装配选项(事件目标注入)
14969
+ * @returns 指令执行桥句柄
14970
+ */
14971
+ function createCommandBridge(options = {}) {
14972
+ const registry = createCommandRegistry();
14973
+ const disposers = [];
14974
+ const palette = showCommandsDef(() => openCommandPalette("command"));
14975
+ for (const command of [...EDITOR_COMMANDS, ...BRIDGE_COMMANDS]) disposers.push(registry.register(command));
14976
+ disposers.push(registry.register(palette));
14977
+ setPaletteRunner((id) => registry.run(id));
14978
+ setRegistryRef(registry);
14979
+ const dispatched = dispatchedCommands(palette);
14980
+ const onKey = (event) => {
14981
+ for (const command of dispatched) {
14982
+ if (!matchEvent(event, bindingsOf(command.id))) continue;
14983
+ if (!registry.isAvailable(command.id)) continue;
14984
+ event.preventDefault();
14985
+ event.stopPropagation();
14986
+ registry.run(command.id);
14987
+ return;
14988
+ }
14989
+ };
14990
+ const target = resolveTarget(options);
14991
+ if (target) target.addEventListener("keydown", onKey, true);
14992
+ const dispose = () => {
14993
+ if (target) target.removeEventListener("keydown", onKey, true);
14994
+ while (disposers.length) {
14995
+ const release = disposers.pop();
14996
+ if (release) release();
14997
+ }
14998
+ setPaletteRunner(null);
14999
+ setRegistryRef(null);
15000
+ };
15001
+ bridgeLog.info("指令注册表已装配(" + registry.list().length + " 条指令,桥接键位 " + dispatched.length + " 条)");
15002
+ return {
15003
+ registry,
15004
+ dispose
15005
+ };
15006
+ }
15007
+ //#endregion
13394
15008
  //#region src/client/index.ts
13395
15009
  /**
13396
15010
  * dsh-vscode-mode client — 浏览器半入口:slot 注册 + 装配。
@@ -13418,6 +15032,30 @@ window.__ModuleLoader__.load({
13418
15032
  "conversation",
13419
15033
  "settingsScope"
13420
15034
  ];
15035
+ /** 指令桥装配幂等标记(同一 document 重复 apply 只装配一次,避免重复键位监听)。 */
15036
+ let commandsMounted = false;
15037
+ /**
15038
+ * 装配指令注册表(指令桥 + 命令栏)。
15039
+ * 指令桥不依赖已挂载编辑器(run 只派发窗口事件),故可在启动期装配;
15040
+ * 命令栏浮层的宿主由 CommandPalette 自己在编辑区树内认领。
15041
+ * @author ddj 2026年09月10号
15042
+ * @param ctx 客户端根上下文
15043
+ * @returns void
15044
+ */
15045
+ function setupCommands(ctx) {
15046
+ if (commandsMounted) return;
15047
+ commandsMounted = true;
15048
+ const bridge = createCommandBridge();
15049
+ ctx.provide(REGISTRY_GLOBAL, bridge.registry);
15050
+ const host = window;
15051
+ host[REGISTRY_GLOBAL] = bridge.registry;
15052
+ ctx.effect(() => () => {
15053
+ bridge.dispose();
15054
+ commandsMounted = false;
15055
+ if (host["__edrvCommands__"] === bridge.registry) delete host[REGISTRY_GLOBAL];
15056
+ }, "vscode-mode: command registry");
15057
+ log.info("指令系统已装配:" + bridge.registry.list().length + " 条指令,Ctrl+Shift+P 打开命令栏");
15058
+ }
13421
15059
  /**
13422
15060
  * 装配客户端:注册中央编辑区视图与 header 差异角标。
13423
15061
  * @author ddj 2026年08月20号
@@ -13425,6 +15063,7 @@ window.__ModuleLoader__.load({
13425
15063
  */
13426
15064
  function apply(ctx) {
13427
15065
  const schedule = (fn, ms) => ctx.timeout(fn, ms);
15066
+ setupCommands(ctx);
13428
15067
  const registry = createFileOpenerRegistry();
13429
15068
  const workspaces = ctx.get("workspaces");
13430
15069
  const sessions = ctx.get("sessions");