dsh-vscode-mode 0.1.27 → 0.1.29

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
@@ -30,7 +30,7 @@ window.__ModuleLoader__.load({
30
30
  react = __toESM(react, 1);
31
31
  let react_dom = require("react-dom");
32
32
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
33
- const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):\n 在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }\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-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-brand-primary, #4f8cff); flex-shrink: 0; }\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: 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: #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-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-mn-add-line { background: rgba(15,157,88,.10); }\n[data-edrv-view] .edrv-mn-gutter-add { color: #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: #fdeaea; border-top: 1px solid rgba(217,83,79,.35); border-bottom: 1px solid rgba(217,83,79,.35); box-shadow: inset 0 0 0 1px 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: #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: #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-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: #e8f8e8; color: #0f7a45; border: 1px solid rgba(15,157,88,.4); }\n[data-edrv-view] .edrv-pill-undo { background: #fdeaea; color: #c0392b; border: 1px solid rgba(217,83,79,.4); }\n[data-edrv-view] .edrv-pill-ghost { background: #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: 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: 0; 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; }\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: #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: #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 rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; 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";
33
+ const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):\n 在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }\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-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-brand-primary, #4f8cff); flex-shrink: 0; }\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: 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: #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-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-mn-add-line { background: rgba(15,157,88,.10); }\n[data-edrv-view] .edrv-mn-gutter-add { color: #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: #fdeaea; border-top: 1px solid rgba(217,83,79,.35); border-bottom: 1px solid rgba(217,83,79,.35); box-shadow: inset 0 0 0 1px 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: #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: #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-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: #e8f8e8; color: #0f7a45; border: 1px solid rgba(15,157,88,.4); }\n[data-edrv-view] .edrv-pill-undo { background: #fdeaea; color: #c0392b; border: 1px solid rgba(217,83,79,.4); }\n[data-edrv-view] .edrv-pill-ghost { background: #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: 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: 0; 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; }\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: #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: #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 rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; 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: #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 rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\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";
34
34
  const tagId$1 = "dsh-vscode-mode/editor.css";
35
35
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
36
36
  const tag = document.createElement("style");
@@ -1003,11 +1003,226 @@ window.__ModuleLoader__.load({
1003
1003
  return regions;
1004
1004
  }
1005
1005
  //#endregion
1006
+ //#region src/shared/keybindings.ts
1007
+ /**
1008
+ * dsh-vscode-mode shared — 快捷键命令目录与默认键位(双面契约)。
1009
+ * 纯数据模块:host(settings schema 默认值)与 client(执行匹配/设置页)共用。
1010
+ * 键位格式:修饰符 + 主键,`+` 连接,如 `Ctrl+Shift+F`;空串 = 未绑定。
1011
+ * 作者 ddj 2026年08月26号
1012
+ */
1013
+ /** 命令 id → 默认键位。命令目录以此为准,新增命令只需加一项。 */
1014
+ const KEYBINDING_DEFAULTS = {
1015
+ "edrv.save": "Ctrl+S",
1016
+ "edrv.quickOpen": "Ctrl+P",
1017
+ "edrv.toggleSidebar": "Ctrl+B",
1018
+ "edrv.searchInFiles": "Ctrl+Shift+F"
1019
+ };
1020
+ /**
1021
+ * 返回默认键位的独立副本(调用方修改不污染目录)。
1022
+ * @author ddj 2026年08月26号
1023
+ * @returns 默认键位映射
1024
+ */
1025
+ function defaultKeybindings() {
1026
+ return { ...KEYBINDING_DEFAULTS };
1027
+ }
1028
+ /**
1029
+ * 规整外部键位数据:只保留已知命令 id 且为字符串的值,未知 id 丢弃。
1030
+ * 防止用户文档/旧版本写入的未知命令污染执行匹配。
1031
+ * @author ddj 2026年08月26号
1032
+ * @param raw 原始设置值
1033
+ * @returns 规整后的键位映射(缺省项不补默认值,由调用方按需合并)
1034
+ */
1035
+ function normalizeKeybindings(raw) {
1036
+ const out = {};
1037
+ if (!raw || typeof raw !== "object") return out;
1038
+ for (const [id, chord] of Object.entries(raw)) if (id in KEYBINDING_DEFAULTS && typeof chord === "string") out[id] = chord;
1039
+ return out;
1040
+ }
1041
+ //#endregion
1042
+ //#region src/client/keybindings.ts
1043
+ /**
1044
+ * dsh-vscode-mode client — 快捷键配置模块(解析/匹配/状态同步)。
1045
+ * 纯逻辑(parseChord/formatChord/matchEvent/chordFromEvent/normalizeKey)不依赖 DOM,可单测;
1046
+ * 模块状态由 settings 订阅驱动(client/index.ts 调 keybindingsApply)。
1047
+ * 键位语义:Ctrl 与 Cmd 互认(延续现有 Ctrl+P/Ctrl+B 捕获行为)。
1048
+ * 作者 ddj 2026年08月26号
1049
+ */
1050
+ /** 命令目录(设置页展示标签;执行按目录序先匹配先执行,冲突键位确定性)。 */
1051
+ const COMMANDS = [
1052
+ {
1053
+ id: "edrv.save",
1054
+ label: "保存文件"
1055
+ },
1056
+ {
1057
+ id: "edrv.quickOpen",
1058
+ label: "快速打开文件"
1059
+ },
1060
+ {
1061
+ id: "edrv.toggleSidebar",
1062
+ label: "切换侧边栏"
1063
+ },
1064
+ {
1065
+ id: "edrv.searchInFiles",
1066
+ label: "在工作区中搜索"
1067
+ }
1068
+ ];
1069
+ const MODIFIERS = {
1070
+ ctrl: "ctrl",
1071
+ cmd: "meta",
1072
+ meta: "meta",
1073
+ shift: "shift",
1074
+ alt: "alt"
1075
+ };
1076
+ let current = { ...KEYBINDING_DEFAULTS };
1077
+ let parsed = {};
1078
+ const listeners$1 = /* @__PURE__ */ new Set();
1079
+ /**
1080
+ * 应用设置快照(与默认值合并;未知 id 丢弃;空对象 = 全部默认)。
1081
+ * @author ddj 2026年08月26号
1082
+ * @param raw 设置 scope 的 keybindings 字段
1083
+ */
1084
+ function keybindingsApply(raw) {
1085
+ current = {
1086
+ ...KEYBINDING_DEFAULTS,
1087
+ ...normalizeKeybindings(raw)
1088
+ };
1089
+ parsed = {};
1090
+ for (const id of Object.keys(current)) parsed[id] = parseChord(current[id]);
1091
+ for (const listener of listeners$1) try {
1092
+ listener();
1093
+ } catch {}
1094
+ }
1095
+ /**
1096
+ * 订阅键位配置变化。
1097
+ * @author ddj 2026年08月26号
1098
+ * @param listener 变化回调
1099
+ * @returns 取消订阅函数
1100
+ */
1101
+ function subscribeKeybindings(listener) {
1102
+ listeners$1.add(listener);
1103
+ return () => listeners$1.delete(listener);
1104
+ }
1105
+ /**
1106
+ * 当前键位弦(未绑定/空 → null)。
1107
+ * @author ddj 2026年08月26号
1108
+ * @param id 命令 id
1109
+ * @returns 键位弦或 null
1110
+ */
1111
+ function chordOf(id) {
1112
+ const chord = current[id];
1113
+ return typeof chord === "string" && chord.trim() !== "" ? chord : null;
1114
+ }
1115
+ /**
1116
+ * 当前命令的解析键位(未绑定/非法 → null)。
1117
+ * @author ddj 2026年08月26号
1118
+ * @param id 命令 id
1119
+ * @returns 解析键位或 null
1120
+ */
1121
+ function bindingOf(id) {
1122
+ return parsed[id] ?? null;
1123
+ }
1124
+ /**
1125
+ * 主键规范化:小写字母大写、空格归一为 Space,其余原样(与事件 e.key 对照)。
1126
+ * @author ddj 2026年08月26号
1127
+ * @param key 事件主键
1128
+ * @returns 规范化主键
1129
+ */
1130
+ function normalizeKey(key) {
1131
+ const value = String(key ?? "");
1132
+ if (value === " ") return "Space";
1133
+ if (value.length === 1 && value >= "a" && value <= "z") return value.toUpperCase();
1134
+ return value;
1135
+ }
1136
+ /**
1137
+ * 解析键位弦(如 `Ctrl+Shift+F`;纯修饰键/重复主键/空 → null)。
1138
+ * @author ddj 2026年08月26号
1139
+ * @param chord 键位弦
1140
+ * @returns 解析键位或 null
1141
+ */
1142
+ function parseChord(chord) {
1143
+ const parts = String(chord ?? "").split("+").map((part) => part.trim()).filter(Boolean);
1144
+ if (!parts.length) return null;
1145
+ const binding = {
1146
+ ctrl: false,
1147
+ shift: false,
1148
+ alt: false,
1149
+ meta: false,
1150
+ key: ""
1151
+ };
1152
+ for (const part of parts) {
1153
+ const modifier = MODIFIERS[part.toLocaleLowerCase("en-US")];
1154
+ if (modifier) {
1155
+ binding[modifier] = true;
1156
+ continue;
1157
+ }
1158
+ if (binding.key) return null;
1159
+ binding.key = normalizeKey(part);
1160
+ }
1161
+ return binding.key ? binding : null;
1162
+ }
1163
+ /**
1164
+ * 格式化键位(Ctrl/Cmd/Shift/Alt + 主键;meta 显示 Cmd)。
1165
+ * @author ddj 2026年08月26号
1166
+ * @param binding 解析键位
1167
+ * @returns 键位弦
1168
+ */
1169
+ function formatChord(binding) {
1170
+ const parts = [];
1171
+ if (binding.ctrl) parts.push("Ctrl");
1172
+ if (binding.meta) parts.push("Cmd");
1173
+ if (binding.shift) parts.push("Shift");
1174
+ if (binding.alt) parts.push("Alt");
1175
+ parts.push(binding.key);
1176
+ return parts.join("+");
1177
+ }
1178
+ /**
1179
+ * 事件是否命中键位(Ctrl 与 Cmd 互认)。
1180
+ * @author ddj 2026年08月26号
1181
+ * @param e 键盘事件(鸭子类型,便于单测)
1182
+ * @param binding 解析键位(null = 未绑定,永不命中)
1183
+ * @returns 是否命中
1184
+ */
1185
+ function matchEvent(e, binding) {
1186
+ if (!binding) return false;
1187
+ if ((e.ctrlKey || e.metaKey) !== (binding.ctrl || binding.meta)) return false;
1188
+ if (Boolean(e.shiftKey) !== binding.shift) return false;
1189
+ if (Boolean(e.altKey) !== binding.alt) return false;
1190
+ return normalizeKey(e.key ?? "") === binding.key;
1191
+ }
1192
+ /**
1193
+ * 从键盘事件构造键位弦(纯修饰键 → null,供设置页录制)。
1194
+ * @author ddj 2026年08月26号
1195
+ * @param e 键盘事件
1196
+ * @returns 键位弦或 null
1197
+ */
1198
+ function chordFromEvent(e) {
1199
+ const key = normalizeKey(e.key ?? "");
1200
+ if (!key || key === "Control" || key === "Shift" || key === "Alt" || key === "Meta") return null;
1201
+ return formatChord({
1202
+ ctrl: Boolean(e.ctrlKey),
1203
+ shift: Boolean(e.shiftKey),
1204
+ alt: Boolean(e.altKey),
1205
+ meta: Boolean(e.metaKey),
1206
+ key
1207
+ });
1208
+ }
1209
+ /**
1210
+ * 键位版本 hook:配置变化时返回新版本(tooltip/占位文案随键位刷新)。
1211
+ * @author ddj 2026年08月26号
1212
+ * @returns 当前版本号
1213
+ */
1214
+ function useKeybindingsVersion() {
1215
+ const [version, setVersion] = react.default.useState(0);
1216
+ react.default.useEffect(() => subscribeKeybindings(() => setVersion((v) => v + 1)), []);
1217
+ return version;
1218
+ }
1219
+ //#endregion
1006
1220
  //#region src/client/ui/QuickOpen.ts
1007
1221
  /**
1008
1222
  * dsh-vscode-mode client — QuickOpen:顶部搜索框(Ctrl+P 打开文件)。
1223
+ * 快捷键随配置(edrv.quickOpen),占位文案同步当前键位。
1009
1224
  * 迁移自原 src/client/index.ts 的 QuickOpen,语义不改。
1010
- * 作者 ddj 2026-08-20
1225
+ * 作者 ddj 2026-08-20 / 2026-08-26
1011
1226
  */
1012
1227
  /**
1013
1228
  * 快速打开搜索框:Ctrl+P 聚焦,输入 ≥2 字符经 edrv.searchFiles 搜索,
@@ -1024,6 +1239,7 @@ window.__ModuleLoader__.load({
1024
1239
  const inputRef = react.default.useRef(null);
1025
1240
  const seq = react.default.useRef(0);
1026
1241
  const timer = react.default.useRef(null);
1242
+ useKeybindingsVersion();
1027
1243
  const search = (query) => {
1028
1244
  const s = ++seq.current;
1029
1245
  const clean = String(query || "").trim();
@@ -1056,7 +1272,7 @@ window.__ModuleLoader__.load({
1056
1272
  };
1057
1273
  react.default.useEffect(() => {
1058
1274
  const onKey = (e) => {
1059
- if ((e.ctrlKey || e.metaKey) && String(e.key).toLowerCase() === "p") {
1275
+ if (matchEvent(e, bindingOf("edrv.quickOpen"))) {
1060
1276
  e.preventDefault();
1061
1277
  e.stopPropagation();
1062
1278
  inputRef.current?.focus?.();
@@ -1087,7 +1303,7 @@ window.__ModuleLoader__.load({
1087
1303
  return react.default.createElement("div", { className: "edrv-search-wrap" }, react.default.createElement("input", {
1088
1304
  ref: inputRef,
1089
1305
  className: "edrv-search",
1090
- placeholder: "搜索文件 (Ctrl+P)",
1306
+ placeholder: "搜索文件 (" + (chordOf("edrv.quickOpen") ?? "Ctrl+P") + ")",
1091
1307
  value: q,
1092
1308
  onChange: (e) => onChange(e.target.value),
1093
1309
  onFocus: () => {
@@ -1755,6 +1971,7 @@ window.__ModuleLoader__.load({
1755
1971
  const [sidebarW, setSidebarW] = react.default.useState(240);
1756
1972
  const [activePanel, setActivePanel] = react.default.useState("explorer");
1757
1973
  const [focusRequest, setFocusRequest] = react.default.useState(0);
1974
+ useKeybindingsVersion();
1758
1975
  const [hintDismissed, setHintDismissed] = react.default.useState(() => {
1759
1976
  try {
1760
1977
  return localStorage.getItem("edrv.side-hint-dismissed") === "1";
@@ -1782,6 +1999,7 @@ window.__ModuleLoader__.load({
1782
1999
  const hoverPanelRef = react.default.useRef(false);
1783
2000
  const batchBusyRef = react.default.useRef(false);
1784
2001
  const menuHandlersRef = react.default.useRef(null);
2002
+ const doSaveRef = react.default.useRef(null);
1785
2003
  const diffRendererRef = react.default.useRef(null);
1786
2004
  const layoutRef = react.default.useRef(layout);
1787
2005
  layoutRef.current = layout;
@@ -2110,11 +2328,34 @@ window.__ModuleLoader__.load({
2110
2328
  ]);
2111
2329
  react.default.useEffect(() => {
2112
2330
  const onKey = (e) => {
2113
- if ((e.ctrlKey || e.metaKey) && String(e.key).toLowerCase() === "b") {
2114
- e.preventDefault();
2115
- e.stopPropagation();
2116
- setSidebarOn((v) => !v);
2117
- }
2331
+ if (!matchEvent(e, bindingOf("edrv.toggleSidebar"))) return;
2332
+ e.preventDefault();
2333
+ e.stopPropagation();
2334
+ setSidebarOn((v) => !v);
2335
+ };
2336
+ window.addEventListener("keydown", onKey, true);
2337
+ return () => window.removeEventListener("keydown", onKey, true);
2338
+ }, []);
2339
+ react.default.useEffect(() => {
2340
+ const onKey = (e) => {
2341
+ if (!matchEvent(e, bindingOf("edrv.save"))) return;
2342
+ if (!editorRef.current?.getModel?.()) return;
2343
+ e.preventDefault();
2344
+ e.stopPropagation();
2345
+ flushSave();
2346
+ doSaveRef.current(false);
2347
+ };
2348
+ window.addEventListener("keydown", onKey, true);
2349
+ return () => window.removeEventListener("keydown", onKey, true);
2350
+ }, []);
2351
+ react.default.useEffect(() => {
2352
+ const onKey = (e) => {
2353
+ if (!matchEvent(e, bindingOf("edrv.searchInFiles"))) return;
2354
+ e.preventDefault();
2355
+ e.stopPropagation();
2356
+ setSidebarOn(true);
2357
+ setActivePanel("search");
2358
+ setTimeout(() => window.dispatchEvent(new CustomEvent("edrv:search-focus")), 0);
2118
2359
  };
2119
2360
  window.addEventListener("keydown", onKey, true);
2120
2361
  return () => window.removeEventListener("keydown", onKey, true);
@@ -2213,6 +2454,7 @@ window.__ModuleLoader__.load({
2213
2454
  setError("保存异常:" + String(e));
2214
2455
  });
2215
2456
  };
2457
+ doSaveRef.current = doSave;
2216
2458
  const onEdit = () => {
2217
2459
  if (!editorRef.current || !active) return;
2218
2460
  setDirtyMap((d) => Object.assign({}, d, { [active]: true }));
@@ -2297,10 +2539,6 @@ window.__ModuleLoader__.load({
2297
2539
  cursorBlinking: "smooth",
2298
2540
  padding: { top: side ? 6 : 8 }
2299
2541
  });
2300
- ed.addCommand(m.KeyMod.CtrlCmd | m.KeyCode.KeyS, () => {
2301
- flushSave();
2302
- doSave(false);
2303
- });
2304
2542
  ed.onDidChangeModelContent(() => {
2305
2543
  if (!ed.getModel() || programmaticRef.current) return;
2306
2544
  onEdit();
@@ -2404,9 +2642,20 @@ window.__ModuleLoader__.load({
2404
2642
  react.default.useEffect(() => {
2405
2643
  const pf = pendingFocusRef.current;
2406
2644
  if (!pf || pf.path !== active || content === null) return;
2407
- const target = pf.region || pendingRegions[0];
2408
2645
  const ed = editorRef.current;
2409
- if (!target || !ed) return;
2646
+ if (!ed) return;
2647
+ if (pf.line != null) {
2648
+ pendingFocusRef.current = null;
2649
+ ed.revealLineInCenter(Math.max(1, pf.line));
2650
+ ed.setPosition({
2651
+ lineNumber: Math.max(1, pf.line),
2652
+ column: Math.max(1, pf.column ?? 1)
2653
+ });
2654
+ ed.focus();
2655
+ return;
2656
+ }
2657
+ const target = pf.region || pendingRegions[0];
2658
+ if (!target) return;
2410
2659
  pendingFocusRef.current = null;
2411
2660
  ed.revealLineInCenter(Math.max(1, target.start ?? 1));
2412
2661
  ed.setPosition({
@@ -2691,6 +2940,24 @@ window.__ModuleLoader__.load({
2691
2940
  region: null
2692
2941
  };
2693
2942
  };
2943
+ /**
2944
+ * 打开文件并跳转到指定行列(搜索面板命中跳转)。
2945
+ * @author ddj 2026年08月26号
2946
+ * @param path 文件路径
2947
+ * @param line 目标行(缺省仅打开不跳转)
2948
+ * @param column 目标列(缺省 1)
2949
+ */
2950
+ const openFileAt = (path, line, column) => {
2951
+ if (!path) return;
2952
+ addTab(path, true);
2953
+ pendingFocusRef.current = {
2954
+ path,
2955
+ region: null,
2956
+ line: line ?? null,
2957
+ column: column ?? 1
2958
+ };
2959
+ setFocusRequest((value) => value + 1);
2960
+ };
2694
2961
  const openPath = () => {
2695
2962
  const p = (pathDraft || "").trim();
2696
2963
  if (!p) return;
@@ -2768,7 +3035,7 @@ window.__ModuleLoader__.load({
2768
3035
  const pathBar = react.default.createElement("div", {
2769
3036
  className: "edrv-pathbar",
2770
3037
  title: active || ""
2771
- }, react.default.createElement("span", { className: "edrv-pb-name" }, active ? String(active).split(/[\\/]/).pop() : "未打开文件"), react.default.createElement("span", { className: "edrv-pb-full" }, active || "使用右上搜索框 (Ctrl+P) 打开文件"), active && langOf(active) ? react.default.createElement("span", { className: "edrv-pb-meta" }, langOf(active)) : null, cursor ? react.default.createElement("span", { className: "edrv-pb-meta" }, cursor) : null, status ? react.default.createElement("span", { className: "edrv-pb-meta edrv-pb-status" }, status) : null);
3038
+ }, react.default.createElement("span", { className: "edrv-pb-name" }, active ? String(active).split(/[\\/]/).pop() : "未打开文件"), react.default.createElement("span", { className: "edrv-pb-full" }, active || "使用右上搜索框 (" + (chordOf("edrv.quickOpen") ?? "Ctrl+P") + ") 打开文件"), active && langOf(active) ? react.default.createElement("span", { className: "edrv-pb-meta" }, langOf(active)) : null, cursor ? react.default.createElement("span", { className: "edrv-pb-meta" }, cursor) : null, status ? react.default.createElement("span", { className: "edrv-pb-meta edrv-pb-status" }, status) : null);
2772
3039
  const tabRow = react.default.createElement("div", { style: {
2773
3040
  display: "flex",
2774
3041
  alignItems: "center",
@@ -2780,7 +3047,7 @@ window.__ModuleLoader__.load({
2780
3047
  onClick: () => setLauncherOpen((o) => !o)
2781
3048
  }, "⚠ 差异 " + sum.totalFiles + " 文件") : null, react.default.createElement("button", {
2782
3049
  className: "edrv-chip-btn" + (sidebarOn ? " edrv-chip-on" : ""),
2783
- title: "切换侧边栏 (Ctrl+B)",
3050
+ title: "切换侧边栏 (" + (chordOf("edrv.toggleSidebar") ?? "Ctrl+B") + ")",
2784
3051
  onClick: () => setSidebarOn((v) => !v)
2785
3052
  }, "☰"), react.default.createElement("button", {
2786
3053
  className: "edrv-chip-btn",
@@ -2828,7 +3095,7 @@ window.__ModuleLoader__.load({
2828
3095
  });
2829
3096
  }
2830
3097
  }, "重试"));
2831
- else if (!active) body = react.default.createElement("div", { className: "edrv-empty" }, react.default.createElement("div", null, "暂无打开的文件"), react.default.createElement("div", { style: { fontSize: 12 } }, "使用右上搜索框 (Ctrl+P) 打开工作区文件;agent 修改文件后顶部会出现差异角标"));
3098
+ 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 修改文件后顶部会出现差异角标"));
2832
3099
  else if (content === null && loadError) body = loadingBody("文件加载失败:" + loadError, 0, () => {
2833
3100
  setLoadError(null);
2834
3101
  setContent(null);
@@ -2979,6 +3246,7 @@ window.__ModuleLoader__.load({
2979
3246
  const sidebarCtx = {
2980
3247
  sessionId,
2981
3248
  openFile: (p) => openFile(p, false),
3249
+ openFileAt,
2982
3250
  activePath: active,
2983
3251
  pendingByPath,
2984
3252
  sum: {
@@ -3360,7 +3628,7 @@ window.__ModuleLoader__.load({
3360
3628
  }
3361
3629
  //#endregion
3362
3630
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
3363
- 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/* 子 Tab:我的 MCP / 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.vsm-general-page{display:flex;flex-direction:column;gap:10px;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 0 8px;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 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/* 兼容性小节 */\n.vsm-compat-h3{margin:16px 0 2px;font-size:14px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8)}\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";
3631
+ 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/* 子 Tab:我的 MCP / 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.vsm-general-page{display:flex;flex-direction:column;gap:10px;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 0 8px;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 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/* 兼容性小节 */\n.vsm-compat-h3{margin:16px 0 2px;font-size:14px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8)}\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";
3364
3632
  const tagId = "dsh-vscode-mode/mcp.css";
3365
3633
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
3366
3634
  const tag = document.createElement("style");
@@ -3409,17 +3677,20 @@ window.__ModuleLoader__.load({
3409
3677
  * @param ctx 客户端上下文(slots 服务)
3410
3678
  * @param spec slot 描述
3411
3679
  * @param render 渲染函数
3680
+ * @returns 注销函数(未注册返回 null;形态切换时用于卸载旧 slot)
3412
3681
  */
3413
3682
  function registerSlotSafely(ctx, spec, render) {
3414
3683
  const slots = ctx?.slots;
3415
3684
  if (!slots || typeof slots.inject !== "function" || typeof slots.register !== "function") {
3416
3685
  console.warn("[dsh-vscode-mode] slots 服务不可用,跳过 slot " + spec.name);
3417
- return;
3686
+ return null;
3418
3687
  }
3419
3688
  try {
3420
- slots.inject(spec.name, () => slots.register(spec, render));
3689
+ const disposer = slots.inject(spec.name, () => slots.register(spec, render));
3690
+ return typeof disposer === "function" ? disposer : null;
3421
3691
  } catch (error) {
3422
3692
  console.warn("[dsh-vscode-mode] slot " + spec.name + " 注册失败(" + String(error) + "),已跳过");
3693
+ return null;
3423
3694
  }
3424
3695
  }
3425
3696
  /**
@@ -3582,11 +3853,185 @@ window.__ModuleLoader__.load({
3582
3853
  */
3583
3854
  const SettingsContext = react.default.createContext(void 0);
3584
3855
  //#endregion
3856
+ //#region src/client/ui/KeybindingsPanel.ts
3857
+ /**
3858
+ * dsh-vscode-mode client — VSCodeMode 设置区「快捷键」管理页。
3859
+ * 命令目录来自 client/keybindings 的 COMMANDS;当前键位读 SettingsContext scope;
3860
+ * 编辑:行内录制(捕获下一次组合键),Esc 取消、Backspace/Delete 清空(未绑定);
3861
+ * 草稿 + 显式保存(scope.set('keybindings', draft));行级恢复 + 页级「恢复默认」;
3862
+ * 冲突键位(两命令同键位)标红警告;scope 外部变更且无未保存修改时回同步草稿。
3863
+ * 作者 ddj 2026-08-26
3864
+ */
3865
+ /**
3866
+ * 草稿化:默认值 + 设置值合并,空串归一为 null(未绑定)。
3867
+ * @param raw 设置 scope 的 keybindings 字段
3868
+ * @returns 草稿映射(id → 键位弦或 null)
3869
+ */
3870
+ function draftOf(raw) {
3871
+ const merged = {
3872
+ ...defaultKeybindings(),
3873
+ ...normalizeKeybindings(raw)
3874
+ };
3875
+ const out = {};
3876
+ for (const [id, chord] of Object.entries(merged)) out[id] = chord === "" ? null : chord;
3877
+ return out;
3878
+ }
3879
+ /**
3880
+ * 存库化:null(未绑定)转空串。
3881
+ * @param draft 草稿映射
3882
+ * @returns 可存储的键位映射
3883
+ */
3884
+ function storeOf(draft) {
3885
+ const out = {};
3886
+ for (const [id, chord] of Object.entries(draft)) out[id] = chord === null ? "" : chord;
3887
+ return out;
3888
+ }
3889
+ /**
3890
+ * 冲突检测:同一键位被多个命令占用 → 返回冲突命令 id 集合。
3891
+ * @param draft 草稿映射
3892
+ * @returns 冲突命令 id 集合
3893
+ */
3894
+ function conflictsOf(draft) {
3895
+ const byChord = /* @__PURE__ */ new Map();
3896
+ for (const [id, chord] of Object.entries(draft)) {
3897
+ if (!chord) continue;
3898
+ const list = byChord.get(chord) ?? [];
3899
+ list.push(id);
3900
+ byChord.set(chord, list);
3901
+ }
3902
+ const out = /* @__PURE__ */ new Set();
3903
+ for (const list of byChord.values()) if (list.length > 1) for (const id of list) out.add(id);
3904
+ return out;
3905
+ }
3906
+ /**
3907
+ * 快捷键管理页主体。
3908
+ */
3909
+ function KeybindingsPanel() {
3910
+ const settings = react.default.useContext(SettingsContext);
3911
+ const snapshot = settings?.getSnapshot?.();
3912
+ const unavailable = !settings || snapshot?.status === "unavailable";
3913
+ const notReady = snapshot?.status !== "ready";
3914
+ const [draft, setDraft] = react.default.useState(() => draftOf(snapshot?.value?.keybindings));
3915
+ const [recording, setRecording] = react.default.useState(null);
3916
+ const [dirty, setDirty] = react.default.useState(false);
3917
+ const [busy, setBusy] = react.default.useState(false);
3918
+ const [error, setError] = react.default.useState("");
3919
+ const [message, setMessage] = react.default.useState("");
3920
+ const dirtyRef = react.default.useRef(false);
3921
+ dirtyRef.current = dirty;
3922
+ const lastKeyRef = react.default.useRef(JSON.stringify(draft));
3923
+ const snapshotValue = snapshot?.value?.keybindings;
3924
+ react.default.useEffect(() => {
3925
+ if (dirtyRef.current) return;
3926
+ const next = draftOf(snapshotValue);
3927
+ const key = JSON.stringify(next);
3928
+ if (key === lastKeyRef.current) return;
3929
+ lastKeyRef.current = key;
3930
+ setDraft(next);
3931
+ }, [snapshotValue]);
3932
+ react.default.useEffect(() => {
3933
+ if (!recording) return;
3934
+ const onKey = (e) => {
3935
+ e.preventDefault();
3936
+ e.stopPropagation();
3937
+ if (e.key === "Escape") {
3938
+ setRecording(null);
3939
+ return;
3940
+ }
3941
+ if (e.key === "Backspace" || e.key === "Delete") {
3942
+ setDraft((prev) => Object.assign({}, prev, { [recording]: null }));
3943
+ setRecording(null);
3944
+ setDirty(true);
3945
+ return;
3946
+ }
3947
+ const chord = chordFromEvent(e);
3948
+ if (!chord) return;
3949
+ setDraft((prev) => Object.assign({}, prev, { [recording]: chord }));
3950
+ setRecording(null);
3951
+ setDirty(true);
3952
+ };
3953
+ window.addEventListener("keydown", onKey, true);
3954
+ return () => window.removeEventListener("keydown", onKey, true);
3955
+ }, [recording]);
3956
+ const conflicts = conflictsOf(draft);
3957
+ const save = () => {
3958
+ if (!settings?.set) {
3959
+ setError("设置服务不可用");
3960
+ return;
3961
+ }
3962
+ setBusy(true);
3963
+ setError("");
3964
+ setMessage("");
3965
+ settings.set("keybindings", storeOf(draft)).then(() => {
3966
+ setDirty(false);
3967
+ lastKeyRef.current = JSON.stringify(draft);
3968
+ setMessage("已保存");
3969
+ }).catch((e) => setError(String(e))).finally(() => setBusy(false));
3970
+ };
3971
+ /**
3972
+ * 恢复全部默认(确认后直接保存,一步到位)。
3973
+ */
3974
+ const resetAll = () => {
3975
+ if (!settings?.set) {
3976
+ setError("设置服务不可用");
3977
+ return;
3978
+ }
3979
+ if (!window.confirm("恢复全部快捷键为默认值?")) return;
3980
+ setBusy(true);
3981
+ setError("");
3982
+ setMessage("");
3983
+ settings.set("keybindings", { ...defaultKeybindings() }).then(() => {
3984
+ const next = draftOf({});
3985
+ setDraft(next);
3986
+ lastKeyRef.current = JSON.stringify(next);
3987
+ setDirty(false);
3988
+ setMessage("已恢复默认");
3989
+ }).catch((e) => setError(String(e))).finally(() => setBusy(false));
3990
+ };
3991
+ /** 行级恢复该项默认值(进草稿,随保存提交)。 */
3992
+ const resetRow = (id) => {
3993
+ setDraft((prev) => Object.assign({}, prev, { [id]: KEYBINDING_DEFAULTS[id] ?? null }));
3994
+ setDirty(true);
3995
+ };
3996
+ const rows = COMMANDS.map((command) => {
3997
+ const chord = draft[command.id] ?? null;
3998
+ const conflict = chord !== null && conflicts.has(command.id);
3999
+ const invalid = chord !== null && parseChord(chord) === null;
4000
+ const recordingRow = recording === command.id;
4001
+ let display;
4002
+ if (recordingRow) display = react.default.createElement("span", { className: "vsm-kb-rec" }, "按下组合键…(Esc 取消 / Backspace 清除)");
4003
+ else display = react.default.createElement("kbd", { className: "vsm-kb-key" + (conflict ? " warn" : "") + (invalid ? " invalid" : "") }, chord ?? "未绑定");
4004
+ return react.default.createElement("div", {
4005
+ key: command.id,
4006
+ className: "vsm-kb-row" + (recordingRow ? " rec" : "")
4007
+ }, react.default.createElement("div", { className: "vsm-kb-label" }, react.default.createElement("span", null, command.label), conflict && react.default.createElement("small", { className: "vsm-kb-warn" }, "与另一命令冲突"), invalid && react.default.createElement("small", { className: "vsm-kb-warn" }, "键位格式无效")), display, react.default.createElement("div", { className: "vsm-kb-actions" }, recordingRow ? react.default.createElement("button", { onClick: () => setRecording(null) }, "取消") : react.default.createElement(react.default.Fragment, null, react.default.createElement("button", {
4008
+ disabled: unavailable || notReady || busy || snapshot?.writable === false,
4009
+ onClick: () => {
4010
+ setRecording(command.id);
4011
+ setMessage("");
4012
+ }
4013
+ }, "编辑"), react.default.createElement("button", {
4014
+ disabled: busy,
4015
+ onClick: () => resetRow(command.id)
4016
+ }, "恢复"))));
4017
+ });
4018
+ 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("div", { className: "vsm-kb-list" }, rows), react.default.createElement("div", { className: "vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
4019
+ className: "vsm-primary vsm-small",
4020
+ disabled: unavailable || notReady || busy || snapshot?.writable === false || !dirty,
4021
+ onClick: save
4022
+ }, busy ? "保存中…" : "保存"), react.default.createElement("button", {
4023
+ className: "vsm-small",
4024
+ disabled: unavailable || notReady || busy || snapshot?.writable === false,
4025
+ onClick: resetAll
4026
+ }, "恢复默认")));
4027
+ }
4028
+ //#endregion
3585
4029
  //#region src/client/ui/McpSettings.ts
3586
4030
  /**
3587
- * dsh-vscode-mode client — VSCodeMode 设置区内的 MCP 管理。
3588
- * 子 Tab:我的 MCP(profile 全局)/ 项目 MCP(各项目 .mcp.json)/ MCP 市场(占位)。
3589
- * 作者 ddj 2026年08月22号
4031
+ * dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理。
4032
+ * 子 Tab:通用(文件打开工具/开发形态)/ 快捷键(编辑器键位配置,可重置)/
4033
+ * 我的 MCP(profile 全局)/ 项目 MCP(各项目 .mcp.json)/ MCP 市场(占位)/ 兼容性。
4034
+ * 作者 ddj 2026年08月22号 / 2026年08月26号
3590
4035
  */
3591
4036
  const EMPTY = {
3592
4037
  serverName: "",
@@ -3957,6 +4402,7 @@ window.__ModuleLoader__.load({
3957
4402
  let body;
3958
4403
  if (loading) body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取 MCP 服务…");
3959
4404
  else if (tab === "general") body = react.default.createElement(GeneralSettings, { registry: openerRegistry });
4405
+ else if (tab === "keys") body = react.default.createElement(KeybindingsPanel, null);
3960
4406
  else if (tab === "mine") body = react.default.createElement("div", null, servers.length ? servers.map((s) => react.default.createElement(ServerCard, {
3961
4407
  key: s.id,
3962
4408
  server: s,
@@ -3992,6 +4438,9 @@ window.__ModuleLoader__.load({
3992
4438
  className: tab === "general" ? "active" : "",
3993
4439
  onClick: () => setTab("general")
3994
4440
  }, "通用"), react.default.createElement("button", {
4441
+ className: tab === "keys" ? "active" : "",
4442
+ onClick: () => setTab("keys")
4443
+ }, "快捷键"), react.default.createElement("button", {
3995
4444
  className: tab === "mine" ? "active" : "",
3996
4445
  onClick: () => setTab("mine")
3997
4446
  }, "我的 MCP"), react.default.createElement("button", {
@@ -4796,11 +5245,343 @@ window.__ModuleLoader__.load({
4796
5245
  }) : null);
4797
5246
  }
4798
5247
  //#endregion
4799
- //#region src/client/sidebar/panels/index.ts
5248
+ //#region src/client/sidebar/panels/SearchPanel.ts
4800
5249
  /**
4801
- * dsh-vscode-mode client — 「文件管理」面板定义(注册表一项)。
5250
+ * dsh-vscode-mode client — 侧边栏「搜索」面板(VSCode 搜索视图形态)。
5251
+ * 内容搜索:edrv.searchContent RPC;结果按文件分组展示命中行(行号 + 高亮 + 文件计数),
5252
+ * 点击经 ctx.openFileAt 打开文件并跳转行列。
5253
+ * 选项:大小写/全词/正则;折叠区(⚙ 齿轮展开):包含的文件(📖 toggle = 仅当前打开文件)
5254
+ * 与排除的文件(⚙ toggle = 启用排除),模式为逗号分隔 rg glob;无搜索按钮,防抖 + 回车触发。
4802
5255
  * 作者 ddj 2026-08-26
4803
5256
  */
5257
+ const DEBOUNCE_MS = 250;
5258
+ const INCLUDE_PLACEHOLDER = "例如 *.ts, src/**/include";
5259
+ /**
5260
+ * 逗号切分 glob 列表(trim + 去空)。
5261
+ * @param text 用户输入的逗号分隔模式
5262
+ * @returns glob 数组
5263
+ */
5264
+ function splitGlobs(text) {
5265
+ return String(text ?? "").split(",").map((item) => item.trim()).filter(Boolean);
5266
+ }
5267
+ /**
5268
+ * 命中行文本三段切分(before/命中/after,列为 1-based UTF-16)。
5269
+ * @param text 行文本
5270
+ * @param start 起始列
5271
+ * @param end 结束列
5272
+ * @returns 三段文本
5273
+ */
5274
+ function splitHit(text, start, end) {
5275
+ const s = Math.max(0, (start | 0) - 1);
5276
+ const e = Math.max(s, end | 0);
5277
+ return {
5278
+ before: String(text).slice(0, s),
5279
+ hit: String(text).slice(s, e),
5280
+ after: String(text).slice(e)
5281
+ };
5282
+ }
5283
+ /**
5284
+ * 搜索面板主体。
5285
+ * @param props.ctx 面板共享上下文(sessionId/openFileAt/activePath)
5286
+ */
5287
+ function SearchPanel(props) {
5288
+ const ctx = props?.ctx;
5289
+ const sessionId = ctx?.sessionId;
5290
+ const openFileAt = ctx?.openFileAt ?? (() => {});
5291
+ const activePath = ctx?.activePath ?? null;
5292
+ const [query, setQuery] = react.default.useState("");
5293
+ const [matchCase, setMatchCase] = react.default.useState(false);
5294
+ const [wholeWord, setWholeWord] = react.default.useState(false);
5295
+ const [regex, setRegex] = react.default.useState(false);
5296
+ const [includeText, setIncludeText] = react.default.useState("");
5297
+ const [excludeText, setExcludeText] = react.default.useState("");
5298
+ const [onlyActive, setOnlyActive] = react.default.useState(false);
5299
+ const [excludeOn, setExcludeOn] = react.default.useState(false);
5300
+ const [sectionOpen, setSectionOpen] = react.default.useState(false);
5301
+ const [status, setStatus] = react.default.useState("idle");
5302
+ const [error, setError] = react.default.useState("");
5303
+ const [matches, setMatches] = react.default.useState(null);
5304
+ const [truncated, setTruncated] = react.default.useState(false);
5305
+ const [collapsed, setCollapsed] = react.default.useState({});
5306
+ const seqRef = react.default.useRef(0);
5307
+ const timerRef = react.default.useRef(null);
5308
+ const inputRef = react.default.useRef(null);
5309
+ const queryRef = react.default.useRef("");
5310
+ queryRef.current = query;
5311
+ const activePathRef = react.default.useRef(activePath);
5312
+ activePathRef.current = activePath;
5313
+ const requestRef = react.default.useRef({
5314
+ matchCase,
5315
+ wholeWord,
5316
+ regex,
5317
+ include: [],
5318
+ exclude: []
5319
+ });
5320
+ requestRef.current = {
5321
+ matchCase,
5322
+ wholeWord,
5323
+ regex,
5324
+ include: onlyActive ? activePath ? [activePath] : [] : splitGlobs(includeText),
5325
+ exclude: excludeOn ? splitGlobs(excludeText) : []
5326
+ };
5327
+ react.default.useEffect(() => {
5328
+ if (!sessionId) return;
5329
+ try {
5330
+ const saved = JSON.parse(localStorage.getItem("edrv.search.v1." + String(sessionId)) || "null");
5331
+ if (saved && typeof saved === "object") {
5332
+ if (typeof saved.query === "string") setQuery(saved.query);
5333
+ if (typeof saved.matchCase === "boolean") setMatchCase(saved.matchCase);
5334
+ if (typeof saved.wholeWord === "boolean") setWholeWord(saved.wholeWord);
5335
+ if (typeof saved.regex === "boolean") setRegex(saved.regex);
5336
+ if (typeof saved.includeText === "string") setIncludeText(saved.includeText);
5337
+ if (typeof saved.excludeText === "string") setExcludeText(saved.excludeText);
5338
+ if (typeof saved.onlyActive === "boolean") setOnlyActive(saved.onlyActive);
5339
+ if (typeof saved.excludeOn === "boolean") setExcludeOn(saved.excludeOn);
5340
+ if (typeof saved.sectionOpen === "boolean") setSectionOpen(saved.sectionOpen);
5341
+ }
5342
+ } catch (e) {}
5343
+ }, [sessionId]);
5344
+ react.default.useEffect(() => {
5345
+ if (!sessionId) return;
5346
+ try {
5347
+ localStorage.setItem("edrv.search.v1." + String(sessionId), JSON.stringify({
5348
+ query,
5349
+ matchCase,
5350
+ wholeWord,
5351
+ regex,
5352
+ includeText,
5353
+ excludeText,
5354
+ onlyActive,
5355
+ excludeOn,
5356
+ sectionOpen
5357
+ }));
5358
+ } catch (e) {}
5359
+ }, [
5360
+ sessionId,
5361
+ query,
5362
+ matchCase,
5363
+ wholeWord,
5364
+ regex,
5365
+ includeText,
5366
+ excludeText,
5367
+ onlyActive,
5368
+ excludeOn,
5369
+ sectionOpen
5370
+ ]);
5371
+ /**
5372
+ * 执行内容搜索(请求参数从 ref 读最新值)。
5373
+ * @param text 搜索词
5374
+ */
5375
+ const runSearch = (text) => {
5376
+ const s = ++seqRef.current;
5377
+ const clean = String(text ?? "").trim();
5378
+ if (clean.length < 2) {
5379
+ setMatches(null);
5380
+ setStatus("idle");
5381
+ return;
5382
+ }
5383
+ const req = requestRef.current;
5384
+ setStatus("searching");
5385
+ setError("");
5386
+ rpc("edrv.searchContent", {
5387
+ sessionId,
5388
+ query: clean,
5389
+ matchCase: req.matchCase,
5390
+ wholeWord: req.wholeWord,
5391
+ regex: req.regex,
5392
+ include: req.include,
5393
+ exclude: req.exclude
5394
+ }).then((res) => {
5395
+ if (s !== seqRef.current) return;
5396
+ if (res && res.ok) {
5397
+ setMatches(Array.isArray(res.matches) ? res.matches : []);
5398
+ setTruncated(res.truncated === true);
5399
+ setStatus("done");
5400
+ } else {
5401
+ setError(res?.error ? String(res.error) : "搜索失败");
5402
+ setMatches(null);
5403
+ setStatus("error");
5404
+ }
5405
+ }).catch((e) => {
5406
+ if (s !== seqRef.current) return;
5407
+ setError("搜索异常:" + String(e));
5408
+ setMatches(null);
5409
+ setStatus("error");
5410
+ });
5411
+ };
5412
+ /**
5413
+ * 输入变化:防抖触发搜索。
5414
+ * @param value 新输入
5415
+ */
5416
+ const onChange = (value) => {
5417
+ setQuery(value);
5418
+ if (timerRef.current) clearTimeout(timerRef.current);
5419
+ timerRef.current = setTimeout(() => runSearch(value), DEBOUNCE_MS);
5420
+ };
5421
+ /**
5422
+ * 输入框通用防抖重搜(包含/排除模式输入)。
5423
+ * @param setter 状态写入函数
5424
+ * @param value 新值
5425
+ */
5426
+ const onChangeDebounced = (setter, value) => {
5427
+ setter(value);
5428
+ if (timerRef.current) clearTimeout(timerRef.current);
5429
+ timerRef.current = setTimeout(() => runSearch(queryRef.current), DEBOUNCE_MS);
5430
+ };
5431
+ /**
5432
+ * 立即重搜(查询 ≥2 字符时);先同步 ref 避免本次调用读到旧请求参数。
5433
+ * @param next 新的请求参数字段
5434
+ */
5435
+ const rerun = (next) => {
5436
+ requestRef.current = Object.assign({}, requestRef.current, next);
5437
+ if (timerRef.current) clearTimeout(timerRef.current);
5438
+ if (String(queryRef.current).trim().length >= 2) runSearch(queryRef.current);
5439
+ };
5440
+ /** 选项开关切换(大小写/全词/正则/书本/设置):更新状态 + 立即重搜。 */
5441
+ const toggleOpt = (key, value) => {
5442
+ if (key === "matchCase") setMatchCase(value);
5443
+ if (key === "wholeWord") setWholeWord(value);
5444
+ if (key === "regex") setRegex(value);
5445
+ if (key === "onlyActive") setOnlyActive(value);
5446
+ if (key === "excludeOn") setExcludeOn(value);
5447
+ const base = {
5448
+ matchCase,
5449
+ wholeWord,
5450
+ regex,
5451
+ onlyActive,
5452
+ excludeOn
5453
+ };
5454
+ base[key] = value;
5455
+ rerun({
5456
+ matchCase: base.matchCase,
5457
+ wholeWord: base.wholeWord,
5458
+ regex: base.regex,
5459
+ include: base.onlyActive ? activePathRef.current ? [activePathRef.current] : [] : splitGlobs(includeText),
5460
+ exclude: base.excludeOn ? splitGlobs(excludeText) : []
5461
+ });
5462
+ };
5463
+ react.default.useEffect(() => {
5464
+ const onFocus = () => inputRef.current?.focus?.();
5465
+ window.addEventListener("edrv:search-focus", onFocus);
5466
+ return () => window.removeEventListener("edrv:search-focus", onFocus);
5467
+ }, []);
5468
+ const groups = react.default.useMemo(() => {
5469
+ if (!Array.isArray(matches)) return [];
5470
+ const map = /* @__PURE__ */ new Map();
5471
+ for (const m of matches) {
5472
+ let list = map.get(m.path);
5473
+ if (!list) {
5474
+ list = [];
5475
+ map.set(m.path, list);
5476
+ }
5477
+ list.push(m);
5478
+ }
5479
+ return [...map.entries()].map(([path, list]) => ({
5480
+ path,
5481
+ count: list.length,
5482
+ matches: list
5483
+ }));
5484
+ }, [matches]);
5485
+ const totalFiles = groups.length;
5486
+ const totalMatches = Array.isArray(matches) ? matches.length : 0;
5487
+ const toggleGroup = (path) => setCollapsed((prev) => Object.assign({}, prev, { [path]: prev[path] === true ? false : true }));
5488
+ const optsBar = react.default.createElement("div", { className: "edrv-search-opts" }, react.default.createElement("button", {
5489
+ className: "edrv-search-opt" + (matchCase ? " on" : ""),
5490
+ title: "区分大小写",
5491
+ "aria-pressed": matchCase,
5492
+ onClick: () => toggleOpt("matchCase", !matchCase)
5493
+ }, "Aa"), react.default.createElement("button", {
5494
+ className: "edrv-search-opt" + (wholeWord ? " on" : ""),
5495
+ title: "全字匹配",
5496
+ "aria-pressed": wholeWord,
5497
+ onClick: () => toggleOpt("wholeWord", !wholeWord)
5498
+ }, "Ab"), react.default.createElement("button", {
5499
+ className: "edrv-search-opt" + (regex ? " on" : ""),
5500
+ title: "使用正则表达式",
5501
+ "aria-pressed": regex,
5502
+ onClick: () => toggleOpt("regex", !regex)
5503
+ }, ".*"), react.default.createElement("span", { className: "edrv-search-spacer" }), react.default.createElement("button", {
5504
+ className: "edrv-search-opt" + (sectionOpen ? " on" : ""),
5505
+ title: sectionOpen ? "收起文件过滤" : "文件过滤(包含/排除)",
5506
+ "aria-pressed": sectionOpen,
5507
+ onClick: () => setSectionOpen((v) => !v)
5508
+ }, "⚙"));
5509
+ const filterRows = sectionOpen ? react.default.createElement("div", { className: "edrv-search-rows" }, react.default.createElement("div", { className: "edrv-search-row" }, react.default.createElement("input", {
5510
+ className: "edrv-search-field" + (onlyActive ? " dim" : ""),
5511
+ placeholder: INCLUDE_PLACEHOLDER,
5512
+ value: includeText,
5513
+ disabled: onlyActive,
5514
+ title: onlyActive ? "已启用「仅当前打开文件」,忽略此输入" : "",
5515
+ onChange: (e) => onChangeDebounced(setIncludeText, e.target.value)
5516
+ }), react.default.createElement("button", {
5517
+ className: "edrv-search-toggle" + (onlyActive ? " on" : ""),
5518
+ title: activePath ? "仅在当前打开的文件中搜索" : "无打开的文件(需先打开文件)",
5519
+ "aria-pressed": onlyActive,
5520
+ disabled: !activePath,
5521
+ onClick: () => toggleOpt("onlyActive", !onlyActive)
5522
+ }, "📖")), react.default.createElement("div", { className: "edrv-search-row" }, react.default.createElement("input", {
5523
+ className: "edrv-search-field" + (excludeOn ? "" : " dim"),
5524
+ placeholder: INCLUDE_PLACEHOLDER,
5525
+ value: excludeText,
5526
+ disabled: !excludeOn,
5527
+ title: excludeOn ? "" : "排除功能未启用,先点击右侧按钮开启",
5528
+ onChange: (e) => onChangeDebounced(setExcludeText, e.target.value)
5529
+ }), react.default.createElement("button", {
5530
+ className: "edrv-search-toggle" + (excludeOn ? " on" : ""),
5531
+ title: "启用排除文件(忽略下方模式的命中)",
5532
+ "aria-pressed": excludeOn,
5533
+ onClick: () => toggleOpt("excludeOn", !excludeOn)
5534
+ }, "⚙"))) : null;
5535
+ const summary = status === "done" ? react.default.createElement("div", { className: "edrv-search-summary" }, totalFiles > 0 ? totalFiles + " 个文件 · " + totalMatches + " 处匹配" + (truncated ? "(已截断)" : "") : "无匹配结果") : null;
5536
+ const body = (() => {
5537
+ if (status === "searching") return react.default.createElement("div", { className: "edrv-search-empty" }, "搜索中…");
5538
+ if (status === "error") return react.default.createElement("div", { className: "edrv-search-error" }, error);
5539
+ if (status === "done" && !groups.length) return react.default.createElement("div", { className: "edrv-search-empty" }, "无匹配结果");
5540
+ if (status !== "done") return react.default.createElement("div", { className: "edrv-search-empty" }, "输入至少 2 个字符开始搜索");
5541
+ return react.default.createElement("div", { className: "edrv-search-results" }, groups.map((group) => {
5542
+ const open = collapsed[group.path] !== true;
5543
+ return react.default.createElement("div", {
5544
+ key: group.path,
5545
+ className: "edrv-search-group"
5546
+ }, react.default.createElement("div", {
5547
+ className: "edrv-search-file",
5548
+ title: group.path,
5549
+ onClick: () => toggleGroup(group.path)
5550
+ }, react.default.createElement("span", { className: "edrv-search-chev" }, open ? "▾" : "▸"), react.default.createElement("span", { className: "edrv-search-fname" }, String(group.path).split(/[\\/]/).pop() || group.path), react.default.createElement("span", { className: "edrv-search-fdir" }, String(group.path).split(/[\\/]/).slice(0, -1).join("/")), react.default.createElement("span", { className: "edrv-search-fcount" }, group.count)), open ? group.matches.map((m, i) => {
5551
+ const parts = splitHit(m.text, m.startColumn, m.endColumn);
5552
+ return react.default.createElement("div", {
5553
+ key: m.path + ":" + m.line + ":" + i,
5554
+ className: "edrv-search-line" + (i % 2 ? " alt" : ""),
5555
+ title: group.path + ":" + m.line,
5556
+ onClick: () => openFileAt(m.path, m.line, m.startColumn)
5557
+ }, react.default.createElement("span", { className: "edrv-search-ln" }, m.line), react.default.createElement("span", { className: "edrv-search-text" }, react.default.createElement("span", null, parts.before), react.default.createElement("mark", { className: "edrv-search-hit" }, parts.hit), react.default.createElement("span", null, parts.after)));
5558
+ }) : null);
5559
+ }));
5560
+ })();
5561
+ return react.default.createElement("div", { className: "edrv-search-panel" }, react.default.createElement("div", { className: "edrv-search-qrow" }, react.default.createElement("input", {
5562
+ ref: inputRef,
5563
+ className: "edrv-search-input",
5564
+ autoFocus: true,
5565
+ autoComplete: "off",
5566
+ spellCheck: false,
5567
+ placeholder: "搜索",
5568
+ value: query,
5569
+ onChange: (e) => onChange(e.target.value),
5570
+ onKeyDown: (e) => {
5571
+ if (e.key === "Enter") {
5572
+ if (timerRef.current) clearTimeout(timerRef.current);
5573
+ runSearch(queryRef.current);
5574
+ }
5575
+ if (e.key === "Escape") inputRef.current?.blur?.();
5576
+ }
5577
+ })), optsBar, filterRows, summary, body);
5578
+ }
5579
+ //#endregion
5580
+ //#region src/client/sidebar/panels/index.ts
5581
+ /**
5582
+ * dsh-vscode-mode client — 侧边栏面板定义(文件管理 + 搜索)。
5583
+ * 作者 ddj 2026-08-26 / 2026-08-26
5584
+ */
4804
5585
  /**
4805
5586
  * 构造文件管理面板定义。
4806
5587
  * @author ddj 2026年08月26号
@@ -4816,6 +5597,20 @@ window.__ModuleLoader__.load({
4816
5597
  render: (ctx) => react.default.createElement(FileExplorer, { ctx })
4817
5598
  };
4818
5599
  }
5600
+ /**
5601
+ * 构造搜索面板定义。
5602
+ * @author ddj 2026年08月30号
5603
+ * @returns 面板定义(无徽标;活动栏图标 🔍)
5604
+ */
5605
+ function createSearchPanel() {
5606
+ return {
5607
+ id: "search",
5608
+ title: "搜索",
5609
+ icon: "🔍",
5610
+ order: 15,
5611
+ render: (ctx) => react.default.createElement(SearchPanel, { ctx })
5612
+ };
5613
+ }
4819
5614
  //#endregion
4820
5615
  //#region src/client/sidebar/menuItems.ts
4821
5616
  /**
@@ -5630,7 +6425,7 @@ window.__ModuleLoader__.load({
5630
6425
  const settings = binder.scope;
5631
6426
  let selected = autoValue("auto");
5632
6427
  /** 可选探测 betterSidebar 服务(不进 inject:缺失会让插件停靠等待,杀死回退路径)。 */
5633
- const sideService = detectSidebarService(ctx);
6428
+ let sideService = detectSidebarService(ctx);
5634
6429
  /** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
5635
6430
  const addToConversation = createAddToConversation(ctx);
5636
6431
  /** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
@@ -5661,6 +6456,7 @@ window.__ModuleLoader__.load({
5661
6456
  const sidebarPanels = createSidebarPanelRegistry();
5662
6457
  ctx.provide("edrvSidebarPanels", sidebarPanels);
5663
6458
  ctx.effect(() => sidebarPanels.register(createFilePanel()), "vscode-mode: sidebar panel");
6459
+ ctx.effect(() => sidebarPanels.register(createSearchPanel()), "vscode-mode: sidebar panel search");
5664
6460
  const fileMenuItems = createTreeMenuRegistry();
5665
6461
  ctx.provide("edrvFileContextMenuItems", fileMenuItems);
5666
6462
  for (const item of createDefaultFileMenuItems()) ctx.effect(() => fileMenuItems.register(item), "vscode-mode: file context menu item " + item.id);
@@ -5689,6 +6485,16 @@ window.__ModuleLoader__.load({
5689
6485
  sync();
5690
6486
  return settings.subscribe(sync);
5691
6487
  }, "vscode-mode: file opener setting sync");
6488
+ ctx.effect(() => {
6489
+ if (!settings) return void 0;
6490
+ const sync = () => {
6491
+ const snapshot = settings.getSnapshot();
6492
+ if (snapshot.status === "loading") return;
6493
+ keybindingsApply(snapshot.value?.keybindings);
6494
+ };
6495
+ sync();
6496
+ return settings.subscribe(sync);
6497
+ }, "vscode-mode: keybindings setting sync");
5692
6498
  if (workspaces?.openPath) ctx.effect(() => installOpenPathRouter({
5693
6499
  workspaces,
5694
6500
  registry,
@@ -5704,7 +6510,7 @@ window.__ModuleLoader__.load({
5704
6510
  logger: (message) => console.warn("[dsh-vscode-mode] " + message)
5705
6511
  }), "vscode-mode: file link routing");
5706
6512
  const registerLegacyTab = () => {
5707
- registerSlotSafely(ctx, {
6513
+ return registerSlotSafely(ctx, {
5708
6514
  name: "conversation.view",
5709
6515
  id: "edrv-editor",
5710
6516
  order: 5,
@@ -5720,30 +6526,60 @@ window.__ModuleLoader__.load({
5720
6526
  fileMenuItems
5721
6527
  })));
5722
6528
  };
5723
- if (sideService) ctx.effect(() => installSideEditor({
5724
- service: sideService,
5725
- renderTab: (props) => react.default.createElement(SideEditorTab, Object.assign({}, props, {
5726
- schedule,
5727
- addToConversation,
5728
- sidebarPanels,
5729
- outlineSources,
5730
- fileMenuItems
5731
- })),
5732
- activeSession: () => {
5733
- const snapshot = sessions?.list?.getSnapshot?.();
5734
- const sessionId = snapshot?.current;
5735
- if (!sessionId) return void 0;
5736
- return {
5737
- sessionId,
5738
- cwd: snapshot?.byId?.[sessionId]?.cwd
5739
- };
5740
- },
5741
- registerLegacyFallback: registerLegacyTab
5742
- }), "vscode-mode: sidebar editor tab");
5743
- else {
5744
- setEnsureSideEditor(null);
5745
- registerLegacyTab();
5746
- }
6529
+ let sideDisposer = null;
6530
+ let legacyDisposer = null;
6531
+ const applySideForm = () => {
6532
+ if (sideService) {
6533
+ if (sideDisposer !== null) return;
6534
+ if (legacyDisposer !== null) {
6535
+ legacyDisposer();
6536
+ legacyDisposer = null;
6537
+ }
6538
+ sideDisposer = ctx.effect(() => installSideEditor({
6539
+ service: sideService,
6540
+ renderTab: (props) => react.default.createElement(SideEditorTab, Object.assign({}, props, {
6541
+ schedule,
6542
+ addToConversation,
6543
+ sidebarPanels,
6544
+ outlineSources,
6545
+ fileMenuItems
6546
+ })),
6547
+ activeSession: () => {
6548
+ const snapshot = sessions?.list?.getSnapshot?.();
6549
+ const sessionId = snapshot?.current;
6550
+ if (!sessionId) return void 0;
6551
+ return {
6552
+ sessionId,
6553
+ cwd: snapshot?.byId?.[sessionId]?.cwd
6554
+ };
6555
+ },
6556
+ registerLegacyFallback: registerLegacyTab
6557
+ }), "vscode-mode: sidebar editor tab");
6558
+ } else {
6559
+ if (legacyDisposer !== null) return;
6560
+ if (sideDisposer !== null) {
6561
+ sideDisposer();
6562
+ sideDisposer = null;
6563
+ }
6564
+ setEnsureSideEditor(null);
6565
+ legacyDisposer = registerLegacyTab();
6566
+ }
6567
+ };
6568
+ applySideForm();
6569
+ let sideRetries = 0;
6570
+ const retrySideService = () => {
6571
+ if (sideService !== void 0 || sideRetries >= 15) return;
6572
+ sideRetries += 1;
6573
+ schedule(() => {
6574
+ if (sideService !== void 0) return;
6575
+ sideService = detectSidebarService(ctx);
6576
+ if (sideService !== void 0) {
6577
+ console.info("[dsh-vscode-mode] 检测到 dsh-better-sidebar,切换侧边栏编辑形态");
6578
+ applySideForm();
6579
+ } else retrySideService();
6580
+ }, 2e3);
6581
+ };
6582
+ retrySideService();
5747
6583
  registerSlotSafely(ctx, {
5748
6584
  name: "conversation.input.dock",
5749
6585
  id: "edrv-diff-dock",