dsh-vscode-mode 0.1.52 → 0.1.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -28,6 +28,7 @@ window.__ModuleLoader__.load({
28
28
  //#endregion
29
29
  let react = require("react");
30
30
  react = __toESM(react, 1);
31
+ let react_dom_client = require("react-dom/client");
31
32
  let react_dom = require("react-dom");
32
33
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
33
34
  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-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress-message { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress { flex: 0 0 auto; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-lsp-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-lsp-status-dot.busy { background: var(--dsw-alias-state-warn-primary, #d97706); animation: edrv-lsp-pulse 1.2s ease-in-out infinite; }\n@keyframes edrv-lsp-pulse { 50% { opacity: .45; } }\n/* Ctrl+hover 可导航标识符下划线提示(绑定 Monaco inlineClassName 装饰) */\n[data-edrv-view] .edrv-nav-underline { text-decoration: underline; text-decoration-color: var(--dsw-alias-interactive-primary, #4fc1ff); text-underline-offset: 1px; cursor: pointer; }\n[data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-chip-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-chip-btn:disabled:hover { background: transparent; }\n[data-edrv-view] .edrv-mn-add-line { background: 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: 1; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }\n[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }\n[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; }\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[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid rgba(240,173,78,.45); border-radius: 6px; background: #fdf6e3; color: #b8860b; font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n/* ===== rules panel (sidebar, Codebuddy rules view) ===== */\n[data-edrv-view] .edrv-rules-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-rules-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-tab { height: 24px; padding: 0 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-tab.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-rules-new { margin-left: auto; height: 24px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-new:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-ws-line { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 auto; user-select: none; }\n[data-edrv-view] .edrv-rules-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 0 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-list { flex: 1; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-rules-row { display: flex; align-items: center; gap: 4px; padding: 6px 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; }\n[data-edrv-view] .edrv-rules-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-row.off .edrv-rules-main { opacity: .5; }\n[data-edrv-view] .edrv-rules-main { flex: 1; min-width: 0; cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-rules-line1 { display: flex; align-items: center; gap: 6px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sub { display: flex; align-items: center; gap: 5px; margin-top: 3px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sublabel { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }\n[data-edrv-view] .edrv-rules-type-always { background: rgba(15,157,88,.15); color: #0b7c46; }\n[data-edrv-view] .edrv-rules-type-auto { background: rgba(79,140,255,.16); color: #3367c9; }\n[data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }\n[data-edrv-view] .edrv-rules-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }\n[data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); display: inline-flex; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: #b9c4cc; cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }\n[data-edrv-view] .edrv-rules-switch.on { background: #7c5cff; }\n[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: #fff; transition: left .15s; }\n[data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }\n[data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }\n[data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }\n[data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-editor-ctl { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-ctl-label { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select { height: 24px; padding: 0 4px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-desc-input { flex: 1; min-width: 0; }\n[data-edrv-view] .edrv-rules-input { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-body { flex: 1; min-height: 0; resize: none; padding: 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; line-height: 1.5; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }\n\n\n\n";
@@ -377,6 +378,20 @@ window.__ModuleLoader__.load({
377
378
  window.dispatchEvent(new CustomEvent("edrv:refresh"));
378
379
  }
379
380
  /**
381
+ * 打开指定路径并定位到行列(LSP 跳转目标)。
382
+ * @author ddj 2026年08月27号
383
+ * @param path 目标路径
384
+ * @param line 目标行(1-based,可空)
385
+ * @param column 目标列(1-based,可空)
386
+ */
387
+ function emitOpenAt(path, line, column) {
388
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
389
+ path,
390
+ line,
391
+ column
392
+ } }));
393
+ }
394
+ /**
380
395
  * 拉起 DiffLauncher。
381
396
  * @author ddj 2026年08月26号
382
397
  * @param tab 可选的目标页签
@@ -6892,6 +6907,219 @@ window.__ModuleLoader__.load({
6892
6907
  }, "⟳ 刷新")), workspacePager, workspaceBlocks)), movedBody, configBody, guideBody, confirmBody);
6893
6908
  }
6894
6909
  //#endregion
6910
+ //#region src/client/ui/IntegrationSettings.ts
6911
+ /**
6912
+ * dsh-vscode-mode client — 「系统集成」设置面板(跨平台)。
6913
+ * 右键菜单:Windows=HKCU 三类键(reg)/ Linux=Nautilus 脚本 + Dolphin 服务菜单(文件写入)/
6914
+ * macOS=Automator 快速操作(一键复制配方手动创建,自动检测/移除)。
6915
+ * 深链基址来自设置 integrationBaseUrl(settings.set 持久化)。
6916
+ * Unity 集成:登记项目根 + 一键安装/更新内嵌包到 <项目>/Packages/com.dsh.editor。
6917
+ * 作者 ddj 2026-09-07
6918
+ */
6919
+ /** launcher 状态文案。 */
6920
+ function launcherText(launcher) {
6921
+ if (!launcher?.present) return "未安装";
6922
+ return launcher.compiled ? "已编译 exe(无闪窗)" : "已安装";
6923
+ }
6924
+ /** 注册条目状态文案(跨平台 entries)。 */
6925
+ function entriesText(entries) {
6926
+ if (!entries?.length) return "本平台无自动注册入口";
6927
+ return entries.map((entry) => entry.label + (entry.registered ? " ✓" : " ✗")).join(" · ");
6928
+ }
6929
+ /** macOS Automator 快速操作配方(复制到剪贴板,用户按步骤创建)。 */
6930
+ function automatorRecipe(launcherPath) {
6931
+ return [
6932
+ "macOS Finder 快速操作(Automator)配置步骤:",
6933
+ "1. Automator → 新建 → 快速操作",
6934
+ "2. 「工作流程接收当前」选:文件或文件夹;位于:任何应用程序",
6935
+ "3. 添加动作「运行 Shell 脚本」,Shell 选 /bin/bash,传递输入选「作为参数」",
6936
+ "4. 脚本内容替换为:",
6937
+ " exec \"" + launcherPath + "\" \"$@\"",
6938
+ "5. 保存为:在 DSH 文件编辑中打开",
6939
+ "完成后在 Finder 右键 → 快速操作/服务 中出现「在 DSH 文件编辑中打开」。"
6940
+ ].join("\n");
6941
+ }
6942
+ /** 单个 Unity 项目行。 */
6943
+ function ProjectRow({ project, busy, onInstall, onRemove }) {
6944
+ const versionText = project.missingDir ? "目录不存在" : project.error ? project.error : project.installedVersion ? "已装 v" + project.installedVersion + (project.upToDate ? "(最新)" : "(可更新)") : "未安装";
6945
+ const installable = !project.missingDir && !project.error;
6946
+ return react.default.createElement("div", {
6947
+ className: "vsm-general-row",
6948
+ key: project.path
6949
+ }, react.default.createElement("span", null, project.title, react.default.createElement("small", { className: "vsm-devform-note" }, " " + project.path + " · " + versionText)), react.default.createElement("div", { className: "vsm-devform-actions" }, react.default.createElement("button", {
6950
+ className: "vsm-primary vsm-small",
6951
+ disabled: !installable || busy === "install:" + project.path,
6952
+ title: project.upToDate ? "重新安装(整目录替换)" : "安装/更新到该 Unity 项目",
6953
+ onClick: () => onInstall(project.path)
6954
+ }, busy === "install:" + project.path ? "安装中…" : project.installedVersion ? project.upToDate ? "重装" : "更新" : "安装"), react.default.createElement("button", {
6955
+ disabled: busy === "remove:" + project.path,
6956
+ onClick: () => onRemove(project.path)
6957
+ }, "移除登记")));
6958
+ }
6959
+ /** 平台提示条(注册语义差异说明)。 */
6960
+ function PlatformNote({ platform, registered }) {
6961
+ if (platform === "win32") return null;
6962
+ const text = platform === "linux" ? "Linux 注册覆盖 GNOME Files(Nautilus)与 KDE Dolphin 两个主流文件管理器(文件写入即注册,无需管理员);其他文件管理器暂未支持。" : "macOS:点「复制 Automator 配方」按提示创建快速操作(约 1 分钟),创建后此处自动显示 ✓;「移除注册」仅删除引用本插件 launcher 的快速操作。";
6963
+ return react.default.createElement("small", { className: "vsm-devform-msg" }, text);
6964
+ }
6965
+ /**
6966
+ * 系统集成面板(挂在 VSCodeMode 设置「通用」页底部)。
6967
+ * @author ddj 2026年09月07号
6968
+ */
6969
+ function IntegrationSettings() {
6970
+ const settings = react.default.useContext(SettingsContext);
6971
+ const [status, setStatus] = react.default.useState(null);
6972
+ const [statusError, setStatusError] = react.default.useState("");
6973
+ const [unity, setUnity] = react.default.useState(null);
6974
+ const [error, setError] = react.default.useState("");
6975
+ const [message, setMessage] = react.default.useState("");
6976
+ const [busy, setBusy] = react.default.useState("");
6977
+ const [baseUrl, setBaseUrl] = react.default.useState("");
6978
+ const [baseDraft, setBaseDraft] = react.default.useState(null);
6979
+ const [rootDraft, setRootDraft] = react.default.useState("");
6980
+ react.default.useEffect(() => {
6981
+ let alive = true;
6982
+ Promise.all([rpc("edrv.integration.status", {}), rpc("edrv.unity.list", {})]).then(([st, ul]) => {
6983
+ if (!alive) return;
6984
+ if (st.ok) {
6985
+ setStatus(st);
6986
+ setBaseUrl(st.baseUrl);
6987
+ setStatusError("");
6988
+ } else setStatusError(String(st.error));
6989
+ if (ul.ok) setUnity(ul);
6990
+ }).catch((e) => {
6991
+ if (alive) setError(String(e));
6992
+ });
6993
+ return () => {
6994
+ alive = false;
6995
+ };
6996
+ }, []);
6997
+ const platform = status?.platform ?? "";
6998
+ const onWin = platform === "win32";
6999
+ const onLinux = platform === "linux";
7000
+ const onMac = platform === "darwin";
7001
+ const anyRegistered = (status?.entries ?? []).some((entry) => entry.registered && !entry.manual);
7002
+ const refreshUnity = () => rpc("edrv.unity.list", {}).then((result) => {
7003
+ if (result.ok) setUnity(result);
7004
+ });
7005
+ /** 注册/移除右键菜单(Windows/Linux;macOS 注册按钮替换为配方复制)。 */
7006
+ const runMenu = (key, method, done) => {
7007
+ setBusy(key);
7008
+ setError("");
7009
+ setMessage("");
7010
+ rpc(method, {}).then((result) => {
7011
+ if (!result.ok) throw new Error(result.error);
7012
+ setStatus(result);
7013
+ setMessage(done);
7014
+ }).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(""));
7015
+ };
7016
+ const register = () => runMenu("register", "edrv.integration.register", onLinux ? "已注册:在 Nautilus / Dolphin 中右键文件或文件夹即可看到「在 DSH 文件编辑中打开」(首次出现可能需重启文件管理器)。" : "右键菜单已注册:右键文件/文件夹 →「在 DSH 文件编辑中打开」。");
7017
+ const unregister = () => runMenu("unregister", "edrv.integration.unregister", "右键菜单注册已移除。");
7018
+ /** 复制 macOS Automator 配方(含 launcher 实际路径)。 */
7019
+ const copyRecipe = () => {
7020
+ const text = automatorRecipe(status?.launcher?.path ?? "$HOME/.dsh/dsh-vscode-mode/shell/dsh-open.sh");
7021
+ navigator.clipboard?.writeText(text).then(() => setMessage("配方已复制,粘贴到备忘录按步骤操作即可。")).catch(() => setMessage(text));
7022
+ };
7023
+ /** 保存深链基址(settings.set 持久化;已注册时提示重新注册刷新 launcher ini)。 */
7024
+ const saveBase = () => {
7025
+ const next = String(baseDraft ?? "").trim();
7026
+ if (!next) return;
7027
+ if (!settings?.set) {
7028
+ setError("设置服务不可用");
7029
+ return;
7030
+ }
7031
+ setBusy("base");
7032
+ setError("");
7033
+ setMessage("");
7034
+ settings.set("integrationBaseUrl", next).then(() => {
7035
+ setBaseUrl(next);
7036
+ setMessage("深链基址已保存;如已注册右键菜单,请点「注册」刷新 launcher 配置。");
7037
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
7038
+ };
7039
+ /** 登记 Unity 项目(校验 Assets/ProjectSettings)。 */
7040
+ const addProject = () => {
7041
+ setBusy("add");
7042
+ setError("");
7043
+ setMessage("");
7044
+ rpc("edrv.unity.add", { path: rootDraft.trim() }).then((result) => {
7045
+ if (!result.ok) throw new Error(result.error);
7046
+ setRootDraft("");
7047
+ setMessage("项目已登记,点「安装」把包复制进项目 Packages/ 目录。");
7048
+ return refreshUnity();
7049
+ }).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(""));
7050
+ };
7051
+ /** 一键安装/更新(整目录替换内嵌包)。 */
7052
+ const installProject = (path) => {
7053
+ setBusy("install:" + path);
7054
+ setError("");
7055
+ setMessage("");
7056
+ rpc("edrv.unity.install", { path }).then((result) => {
7057
+ if (!result.ok) throw new Error(result.error);
7058
+ const project = result.project;
7059
+ setMessage("已安装/更新:" + project.title + "(v" + project.installedVersion + ")。Unity 已打开时切回窗口即生效。");
7060
+ return refreshUnity();
7061
+ }).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(""));
7062
+ };
7063
+ /** 移除登记(不删除已安装的包目录)。 */
7064
+ const removeProject = (path) => {
7065
+ if (!window.confirm("移除该项目的登记?(不删除已安装的包目录)")) return;
7066
+ setBusy("remove:" + path);
7067
+ setError("");
7068
+ setMessage("");
7069
+ rpc("edrv.unity.remove", { path }).then(refreshUnity).catch((e) => setError(String(e))).finally(() => setBusy(""));
7070
+ };
7071
+ /** 复制包源路径(手动 Add package from disk 兜底)。 */
7072
+ const copySource = () => {
7073
+ const text = unity?.sourcePath ?? "";
7074
+ if (!text) return;
7075
+ navigator.clipboard?.writeText(text).then(() => setMessage("已复制包源路径:" + text)).catch(() => setMessage("包源路径:" + text));
7076
+ };
7077
+ return react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "系统集成"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("p", { style: { margin: "0 0 8px" } }, "把 DSH 编辑器接入系统入口:文件管理器右键菜单(Windows/Linux/macOS)+ Unity 外部脚本编辑器(UPM 内嵌包,自动发现,无需改 manifest.json)。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("small", { className: "vsm-devform-msg" }, message), status && !onWin && react.default.createElement(PlatformNote, {
7078
+ platform,
7079
+ registered: anyRegistered
7080
+ }), react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "DSH 服务地址"), react.default.createElement("input", {
7081
+ value: baseDraft ?? baseUrl,
7082
+ placeholder: "http://127.0.0.1:3080",
7083
+ onChange: (event) => setBaseDraft(event.target.value),
7084
+ onKeyDown: (event) => {
7085
+ if (event.key === "Enter") saveBase();
7086
+ }
7087
+ }), react.default.createElement("button", {
7088
+ disabled: busy === "base" || baseDraft === null,
7089
+ onClick: saveBase
7090
+ }, "保存")), react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "右键菜单 ", react.default.createElement("small", { className: "vsm-devform-note" }, status ? entriesText(status.entries) + " · launcher:" + launcherText(status.launcher) : statusError ? "状态获取失败:" + statusError : "读取中…")), react.default.createElement("div", { className: "vsm-devform-actions" }, (onWin || onLinux) && react.default.createElement("button", {
7091
+ className: "vsm-primary vsm-small",
7092
+ disabled: busy === "register",
7093
+ onClick: register
7094
+ }, busy === "register" ? "注册中…" : "注册"), onMac && react.default.createElement("button", {
7095
+ className: "vsm-primary vsm-small",
7096
+ onClick: copyRecipe
7097
+ }, "复制 Automator 配方"), status && react.default.createElement("button", {
7098
+ disabled: busy === "unregister" || !anyRegistered && !onMac,
7099
+ onClick: unregister
7100
+ }, "移除注册"))), react.default.createElement("div", {
7101
+ className: "vsm-general-row",
7102
+ style: { alignItems: "flex-start" }
7103
+ }, react.default.createElement("span", null, "Unity 一键安装 ", react.default.createElement("small", { className: "vsm-devform-note" }, unity ? "包 v" + (unity.sourceVersion ?? "?") + " · " + unity.sourcePath : "读取中…")), react.default.createElement("div", { className: "vsm-devform-actions" }, react.default.createElement("button", { onClick: copySource }, "复制包源路径"))), (unity?.projects ?? []).map((project) => react.default.createElement(ProjectRow, {
7104
+ key: project.path,
7105
+ project,
7106
+ busy,
7107
+ onInstall: installProject,
7108
+ onRemove: removeProject
7109
+ })), react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "添加 Unity 项目"), react.default.createElement("input", {
7110
+ value: rootDraft,
7111
+ placeholder: "项目根目录(含 Assets 与 ProjectSettings)",
7112
+ onChange: (event) => setRootDraft(event.target.value),
7113
+ onKeyDown: (event) => {
7114
+ if (event.key === "Enter") addProject();
7115
+ }
7116
+ }), react.default.createElement("button", {
7117
+ className: "vsm-primary vsm-small",
7118
+ disabled: busy === "add" || !rootDraft.trim(),
7119
+ onClick: addProject
7120
+ }, busy === "add" ? "校验中…" : "添加")), react.default.createElement("small", null, "安装 = 复制到 <项目>/Packages/com.dsh.editor(内嵌包);更新 = 整目录替换;卸载 = 删除该目录。Unity 打开时切回窗口自动刷新生效。插件卸载/重载时会自动清理并恢复右键菜单注册(「移除注册」后不再恢复)。")));
7121
+ }
7122
+ //#endregion
6895
7123
  //#region src/client/ui/McpSettings.ts
6896
7124
  /**
6897
7125
  * dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性。
@@ -7152,7 +7380,7 @@ window.__ModuleLoader__.load({
7152
7380
  onKeyDown: (event) => {
7153
7381
  if (event.key === "Enter") commitMinW();
7154
7382
  }
7155
- }), react.default.createElement("small", null, "180–560 px;拖拽低于该宽度自动隐藏")))));
7383
+ }), react.default.createElement("small", null, "180–560 px;拖拽低于该宽度自动隐藏")))), react.default.createElement(IntegrationSettings, null));
7156
7384
  }
7157
7385
  /** 兼容性小节:host 报告(RPC compat)+ 客户端本地适配项。 */
7158
7386
  function CompatSection({ getSummary }) {
@@ -7498,6 +7726,494 @@ window.__ModuleLoader__.load({
7498
7726
  return typeof value === "string" && value.trim() ? value.trim() : AUTO_OPEN_TOOL;
7499
7727
  }
7500
7728
  //#endregion
7729
+ //#region src/shared/externalOpen.ts
7730
+ /**
7731
+ * dsh-vscode-mode shared — 外部深链 URL 契约(Windows launcher / Unity 包 / client 共用)。
7732
+ * 形态:?<FLAG>=1&<PATHS_KEY>=<enc1>[,<enc2>…] [&<LINE_KEY>=N] [&<COLUMN_KEY>=M]
7733
+ * 每个路径独立 percent-encode(encodeURIComponent / Uri.EscapeDataString)后以 `,` 连接:
7734
+ * 编码结果不含裸逗号(Windows 路径本身不允许逗号,编码兜底其余字符),URLSearchParams
7735
+ * 取值做一次标准解码即得 `enc1,enc2`,按 `,` split 即路径,无需二次 decode。
7736
+ * Purity rule: no node/react imports(URLSearchParams 为标准全局)。
7737
+ * 作者 ddj 2026-09-07
7738
+ */
7739
+ /** 深链开关参数名(值恒为 '1')。 */
7740
+ const EDRV_OPEN_FLAG = "edrvOpen";
7741
+ /** 路径列表参数名(逗号分隔的已编码路径)。 */
7742
+ const EDRV_PATHS_KEY = "edrvPaths";
7743
+ /** 目标行参数名(1-based,可选,作用于首个路径)。 */
7744
+ const EDRV_LINE_KEY = "edrvLine";
7745
+ /** 目标列参数名(1-based,可选,作用于首个路径)。 */
7746
+ const EDRV_COLUMN_KEY = "edrvColumn";
7747
+ /** 深链参数键全集(URL 清理用)。 */
7748
+ const EDRV_PARAM_KEYS = [
7749
+ EDRV_OPEN_FLAG,
7750
+ EDRV_PATHS_KEY,
7751
+ EDRV_LINE_KEY,
7752
+ EDRV_COLUMN_KEY
7753
+ ];
7754
+ /**
7755
+ * 解析深链查询串:非深链/路径为空 → null;行列仅接受正整数,非法忽略。
7756
+ * @author ddj 2026年09月07号
7757
+ * @param search location.search(含或不含前导 ? 均可)
7758
+ * @returns 深链参数或 null
7759
+ */
7760
+ function parseOpenParams(search) {
7761
+ const params = new URLSearchParams(search ?? "");
7762
+ if (params.get("edrvOpen") !== "1") return null;
7763
+ const paths = (params.get("edrvPaths") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
7764
+ if (!paths.length) return null;
7765
+ return {
7766
+ paths,
7767
+ line: positiveIntOf(params.get(EDRV_LINE_KEY)),
7768
+ column: positiveIntOf(params.get(EDRV_COLUMN_KEY))
7769
+ };
7770
+ }
7771
+ /**
7772
+ * 正整数解析(非法/非正 → undefined)。
7773
+ * @author ddj 2026年09月07号
7774
+ * @param raw 参数原文(可空)
7775
+ * @returns 正整数或 undefined
7776
+ */
7777
+ function positiveIntOf(raw) {
7778
+ const value = Number(raw);
7779
+ return Number.isInteger(value) && value > 0 ? value : void 0;
7780
+ }
7781
+ //#endregion
7782
+ //#region src/client/chooseDialog.ts
7783
+ /**
7784
+ * dsh-vscode-mode client — 工作区选择弹窗(深链打开规则 1)。
7785
+ * 复用与「添加 MCP」一致的 vsm-mcp-modal / vsm-mcp-dialog 模态(页面居中卡片 + 遮罩 + 主题
7786
+ * token),React portal 挂 body;Esc / 遮罩点击 / 取消 → null(中止流程不改任何状态)。
7787
+ * 作者 ddj 2026-09-07
7788
+ */
7789
+ /** 选择弹窗(vsm-mcp-modal 遮罩 + vsm-mcp-dialog 卡片,Esc/遮罩点击取消)。 */
7790
+ function ChoiceModal({ title, folder, done }) {
7791
+ react.default.useEffect(() => {
7792
+ const onKey = (event) => {
7793
+ if (event.key === "Escape") done(null);
7794
+ };
7795
+ window.addEventListener("keydown", onKey);
7796
+ return () => window.removeEventListener("keydown", onKey);
7797
+ }, []);
7798
+ return react.default.createElement("div", {
7799
+ className: "vsm-mcp-modal",
7800
+ onMouseDown: (event) => {
7801
+ if (event.target === event.currentTarget) done(null);
7802
+ }
7803
+ }, react.default.createElement("div", { className: "vsm-mcp-dialog" }, react.default.createElement("h3", null, "在 DSH 中打开"), react.default.createElement("label", null, "文件夹 " + folder + " 位于已注册的工作区「" + title + "」内,选择打开方式:"), react.default.createElement("div", { className: "vsm-mcp-dialog-actions" }, react.default.createElement("button", { onClick: () => done(null) }, "取消"), react.default.createElement("button", { onClick: () => done("create") }, "新建工作区"), react.default.createElement("button", {
7804
+ className: "vsm-primary",
7805
+ onClick: () => done("recent")
7806
+ }, "使用最近的工作区"))));
7807
+ }
7808
+ /**
7809
+ * 弹出工作区选择弹窗(深链打开规则 1)。
7810
+ * @author ddj 2026年09月07号
7811
+ * @param title 匹配到的工作区标题
7812
+ * @param folder 文件夹路径
7813
+ * @returns 用户选择;null = 取消/中止
7814
+ */
7815
+ function chooseWorkspace(title, folder) {
7816
+ return new Promise((resolve) => {
7817
+ const host = document.createElement("div");
7818
+ document.body.appendChild(host);
7819
+ const root = (0, react_dom_client.createRoot)(host);
7820
+ const done = (choice) => {
7821
+ root.unmount();
7822
+ host.remove();
7823
+ resolve(choice);
7824
+ };
7825
+ root.render(react.default.createElement(ChoiceModal, {
7826
+ title,
7827
+ folder,
7828
+ done
7829
+ }));
7830
+ });
7831
+ }
7832
+ //#endregion
7833
+ //#region src/client/openFlow.ts
7834
+ /**
7835
+ * dsh-vscode-mode client — 深链打开规则引擎(资源管理器右键 / Unity 外部编辑器落地)。
7836
+ * 规则:文件夹 ⊂ 某已注册工作区 → 页面内弹窗二选一(使用最近的工作区 / 新建工作区);
7837
+ * 文件夹不在任何工作区 → 直达最近对话所在工作区新增;文件 → 最近对话 + 编辑器展开
7838
+ * (无任何会话 → 以文件父目录注册工作区引导创建)。首路径驱动规则,其余路径:文件进
7839
+ * 编辑器、文件夹补引用。纯函数可单测;编排依赖经 OpenDeps 注入(mock 可测)。
7840
+ * 作者 ddj 2026-09-07
7841
+ */
7842
+ /** 等列表/输入就绪的节奏。 */
7843
+ const READY_TIMEOUT_MS = 15e3;
7844
+ const READY_TICK_MS = 300;
7845
+ const REF_RETRY_MAX = 20;
7846
+ const REF_RETRY_MS = 300;
7847
+ const REST_STEP_MS = 150;
7848
+ const LINE_AT_DELAY_MS = 200;
7849
+ /**
7850
+ * 路径规范化:`\→/`、小写、去尾分隔符(Windows 大小写不敏感前缀匹配用)。
7851
+ * @author ddj 2026年09月07号
7852
+ */
7853
+ function normPath(p) {
7854
+ return String(p ?? "").replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
7855
+ }
7856
+ /**
7857
+ * 目录包含判定:child === root 或 child 位于 root 之下。
7858
+ * @author ddj 2026年09月07号
7859
+ */
7860
+ function containsPath(root, child) {
7861
+ const r = normPath(root);
7862
+ const c = normPath(child);
7863
+ return c === r || c.startsWith(r + "/");
7864
+ }
7865
+ /**
7866
+ * 父目录(文件引导创建工作区用;根路径回退自身)。
7867
+ * @author ddj 2026年09月07号
7868
+ */
7869
+ function dirnameOf(p) {
7870
+ const norm = String(p ?? "").replace(/[\\/]+$/, "");
7871
+ const cut = Math.max(norm.lastIndexOf("/"), norm.lastIndexOf("\\"));
7872
+ return cut > 0 ? norm.slice(0, cut) : norm;
7873
+ }
7874
+ /**
7875
+ * 最近一次对话:排除子代理行,updatedAt 最新优先。
7876
+ * @author ddj 2026年09月07号
7877
+ */
7878
+ function recentSession(byId, ids) {
7879
+ return (ids ?? Object.keys(byId)).map((id) => byId[id]).filter((row) => row && row.origin !== "subagent").sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0))[0];
7880
+ }
7881
+ /**
7882
+ * 文件夹命中的已注册工作区(前缀包含),updatedAt 新者优先。
7883
+ * @author ddj 2026年09月07号
7884
+ */
7885
+ function matchWorkspaces(items, folder) {
7886
+ return items.filter((ws) => containsPath(ws.path, folder));
7887
+ }
7888
+ /**
7889
+ * 规则分派:目录+命中工作区 → choose;目录未命中 → folderNew(以该文件夹为根注册新工作区,
7890
+ * 用户确认:一律取右键的文件夹本身);文件有会话 → fileRecent;文件无会话 → bootstrap;
7891
+ * 路径缺失 → abort。
7892
+ * @author ddj 2026年09月08号
7893
+ */
7894
+ function pickRule(kind, matched, hasSession) {
7895
+ if (kind === "missing") return "abort";
7896
+ if (kind === "directory") return matched ? "choose" : "folderNew";
7897
+ return hasSession ? "fileRecent" : "bootstrap";
7898
+ }
7899
+ /**
7900
+ * 深链打开编排入口:等列表就绪 → 判型 → 规则分派 → 编排 create/open/引用/编辑器。
7901
+ * @author ddj 2026年09月07号
7902
+ * @param ctx 客户端根上下文(sessions/workspaces 服务)
7903
+ * @param params 深链参数(首路径驱动规则)
7904
+ * @param overrides 依赖覆盖(测试注入)
7905
+ */
7906
+ async function openDeepLink(ctx, params, overrides = {}) {
7907
+ const deps = realDeps(ctx, overrides);
7908
+ if (!await waitListsReady(deps)) {
7909
+ deps.notify("DSH 会话列表未就绪,请稍后重试");
7910
+ return;
7911
+ }
7912
+ const primary = params.paths[0];
7913
+ const kind = await deps.stat(primary).catch(() => "missing");
7914
+ const sess = deps.sessions();
7915
+ const rule = pickRule(kind, matchWorkspaces(deps.workspaces().items, primary).length > 0, Boolean(recentSession(sess.byId, sess.ids)));
7916
+ try {
7917
+ if (rule === "abort") {
7918
+ deps.notify("路径不存在:" + primary);
7919
+ return;
7920
+ }
7921
+ if (rule === "choose") return await runChoose(deps, primary, params);
7922
+ if (rule === "folderNew") return await runFolderNew(deps, primary, params);
7923
+ if (rule === "fileRecent") return await runFileRecent(deps, primary, params);
7924
+ return await runFileBootstrap(deps, primary, params);
7925
+ } catch (error) {
7926
+ deps.notify("打开失败:" + String(error?.message ?? error));
7927
+ }
7928
+ }
7929
+ /** 等待会话与工作区列表就绪(上限 15s;超时按当前态判定)。 */
7930
+ async function waitListsReady(deps) {
7931
+ for (let waited = 0; waited < READY_TIMEOUT_MS; waited += READY_TICK_MS) {
7932
+ if (deps.sessions().ready && deps.workspaces().ready) return true;
7933
+ await sleep(deps, READY_TICK_MS);
7934
+ }
7935
+ return deps.sessions().ready && deps.workspaces().ready;
7936
+ }
7937
+ /** 规则 1:命中工作区 → 弹窗分派(取消即中止)。 */
7938
+ async function runChoose(deps, folder, params) {
7939
+ const best = bestMatch(deps, matchWorkspaces(deps.workspaces().items, folder));
7940
+ const choice = await deps.choose(best?.title ?? "工作区", folder);
7941
+ if (choice === null) return;
7942
+ if (choice === "create") return runFolderNew(deps, folder, params);
7943
+ const sessionId = await spawnInWorkspace(deps, (best ?? deps.workspaces().items[0]).workspaceId, void 0);
7944
+ await settleRef(deps, sessionId, folder, "folder");
7945
+ deps.openEditor(null);
7946
+ await openRest(deps, sessionId, params, 1);
7947
+ }
7948
+ /** 规则 3a:文件 → 所属工作区优先(有会话打开其最近会话;无会话新建 + 文件引用);无匹配 → 最近对话。 */
7949
+ async function runFileRecent(deps, file, params) {
7950
+ const ws = pickFileWorkspace(deps, file);
7951
+ if (ws) {
7952
+ const anchor = recentSessionIn(deps, ws);
7953
+ if (anchor) {
7954
+ deps.openSession(anchor.id);
7955
+ deps.openEditor(file, params.line, params.column);
7956
+ await openRest(deps, anchor.id, params, 1);
7957
+ return;
7958
+ }
7959
+ const sessionId = await spawnInWorkspace(deps, ws.workspaceId, void 0);
7960
+ await settleRef(deps, sessionId, file, "file");
7961
+ deps.openEditor(file, params.line, params.column);
7962
+ await openRest(deps, sessionId, params, 1);
7963
+ return;
7964
+ }
7965
+ const anchor = recentSession(deps.sessions().byId, deps.sessions().ids);
7966
+ if (!anchor) return runFileBootstrap(deps, file, params);
7967
+ deps.openSession(anchor.id);
7968
+ deps.openEditor(file, params.line, params.column);
7969
+ await openRest(deps, anchor.id, params, 1);
7970
+ }
7971
+ /**
7972
+ * 文件所属工作区:前缀命中的工作区里,优先取有会话且最近活跃的;都无会话取路径最具体的。
7973
+ * @author ddj 2026年09月08号
7974
+ */
7975
+ function pickFileWorkspace(deps, file) {
7976
+ const matches = matchWorkspaces(deps.workspaces().items, file);
7977
+ if (!matches.length) return void 0;
7978
+ const withSessions = matches.filter((ws) => (ws.sessionIds ?? []).some((id) => Boolean(deps.sessions().byId[id])));
7979
+ if (!withSessions.length) return [...matches].sort((a, b) => normPath(b.path).length - normPath(a.path).length)[0];
7980
+ return withSessions.sort((a, b) => latestSessionAt(deps, b) - latestSessionAt(deps, a))[0];
7981
+ }
7982
+ /** 工作区内会话的最近活跃时间(无成员 → 0)。 */
7983
+ function latestSessionAt(deps, ws) {
7984
+ const byId = deps.sessions().byId;
7985
+ return (ws.sessionIds ?? []).reduce((max, id) => Math.max(max, byId[id]?.updatedAt ?? 0), 0);
7986
+ }
7987
+ /** 工作区内最近一次会话(排除子代理与不在列表中的陈旧行)。 */
7988
+ function recentSessionIn(deps, ws) {
7989
+ const byId = deps.sessions().byId;
7990
+ return (ws.sessionIds ?? []).map((id) => byId[id]).filter((row) => row && row.origin !== "subagent").sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0))[0];
7991
+ }
7992
+ /** 规则 3b:文件 + 无任何会话 → 父目录注册工作区 + 新对话 + 文件引用 + 编辑器打开。 */
7993
+ async function runFileBootstrap(deps, file, params) {
7994
+ const sessionId = await spawnInWorkspace(deps, (await deps.createWorkspace(dirnameOf(file))).workspaceId, void 0);
7995
+ await settleRef(deps, sessionId, file, "file");
7996
+ deps.openEditor(file, params.line, params.column);
7997
+ await openRest(deps, sessionId, params, 1);
7998
+ }
7999
+ /** 规则 1.2:以文件夹注册新工作区 + 新对话 + 文件夹引用 + 编辑页。 */
8000
+ async function runFolderNew(deps, folder, params) {
8001
+ const sessionId = await spawnInWorkspace(deps, (await deps.createWorkspace(folder)).workspaceId, void 0);
8002
+ await settleRef(deps, sessionId, folder, "folder");
8003
+ deps.openEditor(null);
8004
+ await openRest(deps, sessionId, params, 1);
8005
+ }
8006
+ /** 在指定工作区(或回退 cwd)新建对话并置为当前;返回新会话 id。 */
8007
+ async function spawnInWorkspace(deps, workspaceId, fallbackCwd) {
8008
+ const sessionId = await deps.createSession(workspaceId ? { workspaceId } : fallbackCwd ? { cwd: fallbackCwd } : {});
8009
+ deps.openSession(sessionId);
8010
+ return sessionId;
8011
+ }
8012
+ /** 命中工作区集中「最近」的一个(其成员会话 updatedAt 最新者优先)。 */
8013
+ function bestMatch(deps, matches) {
8014
+ const byId = deps.sessions().byId;
8015
+ const score = (ws) => {
8016
+ const sessionLatest = (ws.sessionIds ?? []).reduce((max, id) => Math.max(max, byId[id]?.updatedAt ?? 0), 0);
8017
+ return Math.max(sessionLatest, Date.parse(ws.updatedAt ?? "") || 0);
8018
+ };
8019
+ return [...matches].sort((a, b) => score(b) - score(a))[0];
8020
+ }
8021
+ /** 引用插入轮询:输入门面就绪前每 300ms 重试(appendReference 忙态已降级视为成功)。 */
8022
+ async function settleRef(deps, sessionId, path, appearance) {
8023
+ for (let attempt = 0; attempt < REF_RETRY_MAX; attempt++) {
8024
+ if (await deps.reference(sessionId, path, appearance)) return;
8025
+ await sleep(deps, REF_RETRY_MS);
8026
+ }
8027
+ }
8028
+ /** 其余路径:文件进编辑器(步进),文件夹补文件夹引用。 */
8029
+ async function openRest(deps, sessionId, params, from) {
8030
+ const rest = params.paths.slice(from);
8031
+ for (const [index, path] of rest.entries()) {
8032
+ const kind = await deps.stat(path).catch(() => "missing");
8033
+ if (kind === "file") deps.schedule(() => deps.openEditor(path), REST_STEP_MS * (index + 1));
8034
+ else if (kind === "directory" && sessionId) await settleRef(deps, sessionId, path, "folder");
8035
+ }
8036
+ }
8037
+ /** 可注入 sleep。 */
8038
+ function sleep(deps, ms) {
8039
+ return new Promise((resolve) => deps.schedule(resolve, ms));
8040
+ }
8041
+ /** 真实依赖装配(全部来自 ctx 注入服务 + 既有 RPC/弹窗/编辑器入口)。 */
8042
+ function realDeps(ctx, overrides = {}) {
8043
+ const service = (name) => {
8044
+ const get = ctx?.get;
8045
+ return typeof get === "function" ? get.call(ctx, name) : void 0;
8046
+ };
8047
+ const add = createAddToConversation(ctx);
8048
+ return {
8049
+ stat: async (path) => {
8050
+ const result = await rpc("edrv.externalStat", { path });
8051
+ return result.ok ? result.kind : "missing";
8052
+ },
8053
+ workspaces: () => {
8054
+ const snap = service("workspaces")?.list?.getSnapshot?.() ?? {};
8055
+ return {
8056
+ items: snap.items ?? [],
8057
+ ready: snap.phase === "ready"
8058
+ };
8059
+ },
8060
+ sessions: () => {
8061
+ const snap = service("sessions")?.list?.getSnapshot?.() ?? {};
8062
+ return {
8063
+ ids: snap.ids ?? [],
8064
+ byId: snap.byId ?? {},
8065
+ ready: snap.phase !== "pending"
8066
+ };
8067
+ },
8068
+ createSession: async (opts) => await service("sessions").create(opts),
8069
+ createWorkspace: async (path) => {
8070
+ const ws = await service("workspaces").create({ path });
8071
+ return {
8072
+ workspaceId: ws.workspaceId,
8073
+ title: ws.title
8074
+ };
8075
+ },
8076
+ openSession: (id) => service("sessions").open(id),
8077
+ reference: async (sessionId, path, appearance) => {
8078
+ return await add.appendReference(sessionId, path, void 0, appearance) !== "unavailable";
8079
+ },
8080
+ choose: (title, folder) => chooseWorkspace(title, folder),
8081
+ openEditor: (path, line, column) => {
8082
+ if (path == null) {
8083
+ openEditorView(null);
8084
+ return;
8085
+ }
8086
+ openEditorView(path);
8087
+ if (line != null) setTimeout(() => emitOpenAt(path, line, column), LINE_AT_DELAY_MS);
8088
+ },
8089
+ schedule: (fn, ms) => setTimeout(fn, ms),
8090
+ notify: (text) => toastDom(text),
8091
+ ...overrides
8092
+ };
8093
+ }
8094
+ //#endregion
8095
+ //#region src/client/externalOpen.ts
8096
+ /**
8097
+ * dsh-vscode-mode client — 外部深链落地(Windows 右键菜单 / Unity 外部编辑器)。
8098
+ * 启动时解析 location.search 的 edrvOpen 深链:跨源 referrer 守卫防外部网页诱导;
8099
+ * 处理完剥离 URL 参数防刷新重开;执行委托 openFlow 打开规则引擎(工作区/对话智能路由)。
8100
+ * 另挂 3s 移交轮询(兼活跃心跳):launcher 投递的打开请求由已打开页面就地执行,避免重复开新页。
8101
+ * 作者 ddj 2026-09-07 / 2026-09-08
8102
+ */
8103
+ /** toast 自隐时长。 */
8104
+ const TOAST_MS = 5e3;
8105
+ /** 深链移交轮询周期(兼活跃心跳)。 */
8106
+ const HANDOFF_POLL_MS = 3e3;
8107
+ /**
8108
+ * 装配外部深链:深链参数缺失时仅保持移交轮询(零额外开销)。
8109
+ * @author ddj 2026年09月07号
8110
+ * @param ctx 客户端根上下文(sessions/workspaces 服务)
8111
+ * @param options 可注入选项
8112
+ */
8113
+ function setupExtOpen(ctx, options = {}) {
8114
+ startPresencePoll(ctx);
8115
+ const params = parseOpenParams(options.search ?? (typeof location !== "undefined" ? location.search : ""));
8116
+ if (!params) return;
8117
+ const referrer = options.referrer ?? (typeof document !== "undefined" ? document.referrer : "");
8118
+ if (!referrerAllowed(referrer, options.origin ?? (typeof location !== "undefined" ? location.origin : ""))) {
8119
+ console.warn("[dsh-vscode-mode] 已忽略跨源深链请求(referrer=" + referrer + ")");
8120
+ return;
8121
+ }
8122
+ stripCurrentUrl();
8123
+ openDeepLink(ctx, params).catch((error) => {
8124
+ console.warn("[dsh-vscode-mode] 深链打开失败:" + String(error));
8125
+ toastDom("深链打开失败:" + String(error?.message ?? error));
8126
+ });
8127
+ }
8128
+ /**
8129
+ * 深链移交轮询:每 3s 向 host 领取待打开请求(兼活跃心跳,launcher 据此决定是否开新页)。
8130
+ * 页面级单例(window 标记防 HMR 重复);领取到即执行打开规则。
8131
+ * @author ddj 2026年09月08号
8132
+ * @param ctx 客户端根上下文
8133
+ */
8134
+ function startPresencePoll(ctx) {
8135
+ if (typeof window === "undefined") return;
8136
+ const marker = window;
8137
+ if (marker.__edrvExtPoll) return;
8138
+ marker.__edrvExtPoll = true;
8139
+ let executing = false;
8140
+ window.setInterval(() => {
8141
+ if (executing) return;
8142
+ executing = true;
8143
+ rpc("edrv.external.pending", {}).then(async (result) => {
8144
+ if (!result.ok || !result.open) return;
8145
+ await openDeepLink(ctx, {
8146
+ paths: result.open.paths,
8147
+ line: result.open.line,
8148
+ column: result.open.column
8149
+ });
8150
+ }).catch(() => {}).finally(() => {
8151
+ executing = false;
8152
+ });
8153
+ }, HANDOFF_POLL_MS);
8154
+ }
8155
+ /**
8156
+ * 跨源守卫:referrer 为空(launcher/Unity 直开)或同源 → 允许。
8157
+ * @author ddj 2026年09月07号
8158
+ * @param referrer 文档 referrer
8159
+ * @param origin 当前页面源
8160
+ * @returns 是否允许处理深链
8161
+ */
8162
+ function referrerAllowed(referrer, origin) {
8163
+ if (!referrer) return true;
8164
+ try {
8165
+ return new URL(referrer).origin === origin;
8166
+ } catch {
8167
+ return false;
8168
+ }
8169
+ }
8170
+ /**
8171
+ * 剥离 URL 中的深链参数(保留其余参数与 hash,防刷新重开)。
8172
+ * @author ddj 2026年09月07号
8173
+ * @param href 完整 URL
8174
+ * @returns 清理后的 URL(pathname+search+hash)
8175
+ */
8176
+ function stripOpenHref(href) {
8177
+ const url = new URL(href);
8178
+ for (const key of EDRV_PARAM_KEYS) url.searchParams.delete(key);
8179
+ return url.pathname + url.search + url.hash;
8180
+ }
8181
+ /** 清理当前页面 URL(best-effort,失败不阻塞打开)。 */
8182
+ function stripCurrentUrl() {
8183
+ try {
8184
+ if (typeof location === "undefined" || typeof history === "undefined") return;
8185
+ const next = stripOpenHref(location.href);
8186
+ if (next !== location.pathname + location.search + location.hash) history.replaceState(null, "", next);
8187
+ } catch {}
8188
+ }
8189
+ /**
8190
+ * 轻量 toast(编辑器未挂载也能提示;5s 自隐;openFlow 通知与错误呈现共用)。
8191
+ * @author ddj 2026年09月07号
8192
+ * @param text 提示文本
8193
+ */
8194
+ function toastDom(text) {
8195
+ if (typeof document === "undefined") return;
8196
+ const toast = document.createElement("div");
8197
+ toast.textContent = text;
8198
+ toast.setAttribute("style", [
8199
+ "position:fixed",
8200
+ "left:50%",
8201
+ "bottom:28px",
8202
+ "transform:translateX(-50%)",
8203
+ "z-index:99999",
8204
+ "padding:8px 14px",
8205
+ "border-radius:8px",
8206
+ "max-width:80vw",
8207
+ "background:#2a2a2a",
8208
+ "color:#ddd",
8209
+ "border:1px solid #444",
8210
+ "font-size:12px",
8211
+ "box-shadow:0 4px 16px rgba(0,0,0,.35)"
8212
+ ].join(";"));
8213
+ document.body?.appendChild(toast);
8214
+ setTimeout(() => toast.remove(), TOAST_MS);
8215
+ }
8216
+ //#endregion
7501
8217
  //#region src/client/ui/SideEditorTab.ts
7502
8218
  /**
7503
8219
  * dsh-vscode-mode client — SideEditorTab:betterSidebar「文件编辑」Tab 的组件包装。
@@ -10232,6 +10948,7 @@ window.__ModuleLoader__.load({
10232
10948
  const registry = createFileOpenerRegistry();
10233
10949
  const workspaces = ctx.get("workspaces");
10234
10950
  const sessions = ctx.get("sessions");
10951
+ setupExtOpen(ctx);
10235
10952
  const monacoList = sessions?.list;
10236
10953
  if (typeof window !== "undefined" && monacoList && typeof monacoList.subscribe === "function") {
10237
10954
  const list = monacoList;