dsh-vscode-mode 0.1.17 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -7
- package/lib/client.js +1261 -280
- package/lib/client.js.map +1 -1
- package/lib/index.js +169 -23
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/addToConversation.ts +211 -0
- package/src/client/diffDock.ts +52 -0
- package/src/client/diffDockStore.ts +87 -0
- package/src/client/editorLayout.ts +19 -0
- package/src/client/events.ts +40 -11
- package/src/client/index.ts +48 -5
- package/src/client/sidebar/SidebarView.ts +73 -0
- package/src/client/sidebar/panels/FileExplorer.ts +161 -0
- package/src/client/sidebar/panels/index.ts +24 -0
- package/src/client/sidebar/registry.ts +52 -0
- package/src/client/sidebar/types.ts +31 -0
- package/src/client/styles/editor.css +127 -5
- package/src/client/ui/ConversationDiffDock.ts +84 -0
- package/src/client/ui/DiffBox.ts +78 -29
- package/src/client/ui/EditorView.ts +375 -68
- package/src/rpc.ts +84 -18
- package/src/shared/rpc.ts +29 -1
- package/src/store.ts +6 -2
- package/src/tree.ts +62 -0
package/lib/client.js
CHANGED
|
@@ -29,7 +29,7 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
let react = require("react");
|
|
30
30
|
react = __toESM(react, 1);
|
|
31
31
|
//#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
|
|
32
|
-
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] { --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-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; min-height: 0; position: relative; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 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: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 16px rgba(31,41,51,.12); overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px; 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[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";
|
|
32
|
+
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-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; }\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-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[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";
|
|
33
33
|
const tagId$1 = "dsh-vscode-mode/editor.css";
|
|
34
34
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
35
35
|
const tag = document.createElement("style");
|
|
@@ -82,28 +82,53 @@ window.__ModuleLoader__.load({
|
|
|
82
82
|
* 作者 ddj 2026-08-20
|
|
83
83
|
*/
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
* @author ddj 2026年08月
|
|
87
|
-
* @param path 要打开的路径(可空)
|
|
85
|
+
* 选择中央「文件编辑」页签(DOM 级,无需 store actions)。
|
|
86
|
+
* @author ddj 2026年08月26号
|
|
88
87
|
*/
|
|
89
|
-
function
|
|
88
|
+
function selectEditorTab() {
|
|
90
89
|
const tabs = Array.from(document.querySelectorAll("div[role=\"tablist\"] button[role=\"tab\"]"));
|
|
91
|
-
for (const
|
|
92
|
-
|
|
90
|
+
for (const tab of tabs) if (tab.textContent && tab.textContent.includes("文件编辑")) {
|
|
91
|
+
tab.click();
|
|
93
92
|
break;
|
|
94
93
|
}
|
|
95
|
-
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 打开中央「文件编辑」页签。
|
|
97
|
+
* @author ddj 2026年08月26号
|
|
98
|
+
* @param path 要打开的路径(可空)
|
|
99
|
+
*/
|
|
100
|
+
function openEditorView(path) {
|
|
101
|
+
selectEditorTab();
|
|
102
|
+
const target = path ?? null;
|
|
96
103
|
setTimeout(() => {
|
|
97
|
-
window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: { path:
|
|
104
|
+
window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: { path: target } }));
|
|
105
|
+
}, 80);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 打开文件编辑页并聚焦指定文件的首个差异。
|
|
109
|
+
* @author ddj 2026年08月26号
|
|
110
|
+
* @param path 待聚焦的差异文件路径
|
|
111
|
+
*/
|
|
112
|
+
function openDiffView(path) {
|
|
113
|
+
selectEditorTab();
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
|
|
116
|
+
path,
|
|
117
|
+
focusDiff: true
|
|
118
|
+
} }));
|
|
98
119
|
}, 80);
|
|
99
120
|
}
|
|
100
121
|
/** 差异状态变化:触发角标/DiffBox 重拉。 */
|
|
101
122
|
function emitRefresh() {
|
|
102
123
|
window.dispatchEvent(new CustomEvent("edrv:refresh"));
|
|
103
124
|
}
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
|
|
125
|
+
/**
|
|
126
|
+
* 拉起 DiffLauncher。
|
|
127
|
+
* @author ddj 2026年08月26号
|
|
128
|
+
* @param tab 可选的目标页签
|
|
129
|
+
*/
|
|
130
|
+
function emitShowLauncher(tab) {
|
|
131
|
+
window.dispatchEvent(new CustomEvent("edrv:show-launcher", { detail: { tab } }));
|
|
107
132
|
}
|
|
108
133
|
//#endregion
|
|
109
134
|
//#region src/client/monaco/loader.ts
|
|
@@ -863,199 +888,6 @@ window.__ModuleLoader__.load({
|
|
|
863
888
|
lineHeight: 1.55
|
|
864
889
|
};
|
|
865
890
|
//#endregion
|
|
866
|
-
//#region src/client/ui/DiffBox.ts
|
|
867
|
-
/**
|
|
868
|
-
* dsh-vscode-mode client — DiffBox:每个文件底部紧凑差异条(per-file hunk + ⋮ 二级菜单)。
|
|
869
|
-
* 迁移自原 src/client/index.ts 的 DiffBox,语义不改。
|
|
870
|
-
* 作者 ddj 2026-08-20
|
|
871
|
-
*/
|
|
872
|
-
/**
|
|
873
|
-
* 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
|
|
874
|
-
* (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
|
|
875
|
-
*/
|
|
876
|
-
function DiffBox(props) {
|
|
877
|
-
const { pendingRegions, staleRegions, onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles, allPendingCount, onRollback, onJump, otherFiles, onOpenOther, onOpenLauncher, onRefresh, activePath, diffIdx, diffTotal, fileIdx, fileTotal, onPrevDiff, onNextDiff, onPrevFile, onNextFile } = props;
|
|
878
|
-
const [expanded, setExpanded] = react.default.useState(false);
|
|
879
|
-
const [menuOpen, setMenuOpen] = react.default.useState(false);
|
|
880
|
-
const canAct = pendingRegions.length > 0;
|
|
881
|
-
const base = String(activePath || "").split(/[\\/]/).pop() || "";
|
|
882
|
-
let bodyEl = null;
|
|
883
|
-
if (expanded) {
|
|
884
|
-
const rows = pendingRegions.map((r) => {
|
|
885
|
-
const key = callIdAttr(r.callId, r.idx);
|
|
886
|
-
return react.default.createElement("div", {
|
|
887
|
-
key,
|
|
888
|
-
className: "edrv-diffrow",
|
|
889
|
-
"data-edrv-hunk": key,
|
|
890
|
-
title: "跳转到 L" + (r.start ?? "?"),
|
|
891
|
-
onClick: () => onJump(r)
|
|
892
|
-
}, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + "-" + (r.end ?? "?")), r.oldLines.length ? react.default.createElement("span", { className: "edrv-diffrow-o" }, "-" + r.oldLines.length) : null, r.newLines.length ? react.default.createElement("span", { className: "edrv-diffrow-n" }, "+" + r.newLines.length) : null, r.create ? react.default.createElement("span", { className: "edrv-diffrow-tag" }, "新建") : null, react.default.createElement("span", { style: { flex: 1 } }));
|
|
893
|
-
});
|
|
894
|
-
const stale = staleRegions.map((r) => react.default.createElement("div", {
|
|
895
|
-
key: "stale" + callIdAttr(r.callId, r.idx),
|
|
896
|
-
className: "edrv-diffrow edrv-diffrow-stale",
|
|
897
|
-
title: "差异无法定位(文件可能已被手动修改)"
|
|
898
|
-
}, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + " 无法定位"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, "冲突/需刷新"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, badgeOf(r.status))));
|
|
899
|
-
let othersEl = null;
|
|
900
|
-
if (otherFiles.length) othersEl = react.default.createElement("div", { className: "edrv-diffbox-others" }, react.default.createElement("span", { className: "edrv-diffrow-tag" }, "其他差异文件 (" + otherFiles.length + "):"), otherFiles.map((f) => react.default.createElement("button", {
|
|
901
|
-
key: f.path,
|
|
902
|
-
className: "edrv-diffrow-file",
|
|
903
|
-
title: f.path,
|
|
904
|
-
onClick: () => onOpenOther(f.path)
|
|
905
|
-
}, String(f.path).split(/[\\/]/).pop() + " (" + f.pending + ")")));
|
|
906
|
-
bodyEl = react.default.createElement("div", { className: "edrv-diffbar-body" }, ...rows, ...stale, othersEl);
|
|
907
|
-
}
|
|
908
|
-
const menu = menuOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
|
|
909
|
-
style: {
|
|
910
|
-
position: "fixed",
|
|
911
|
-
inset: 0,
|
|
912
|
-
zIndex: 44
|
|
913
|
-
},
|
|
914
|
-
onClick: () => setMenuOpen(false)
|
|
915
|
-
}), react.default.createElement("div", { className: "edrv-diffbar-menu" }, react.default.createElement("button", {
|
|
916
|
-
className: "edrv-diffmenu-item",
|
|
917
|
-
disabled: !allPendingCount,
|
|
918
|
-
onClick: () => {
|
|
919
|
-
onAcceptAllFiles();
|
|
920
|
-
setMenuOpen(false);
|
|
921
|
-
}
|
|
922
|
-
}, "✓ Keep All(全部文件采纳)"), react.default.createElement("button", {
|
|
923
|
-
className: "edrv-diffmenu-item danger",
|
|
924
|
-
disabled: !allPendingCount,
|
|
925
|
-
onClick: () => {
|
|
926
|
-
onUndoAllFiles();
|
|
927
|
-
setMenuOpen(false);
|
|
928
|
-
}
|
|
929
|
-
}, "↩ Undo All(全部文件不采纳)"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
|
|
930
|
-
className: "edrv-diffmenu-item",
|
|
931
|
-
onClick: () => {
|
|
932
|
-
onOpenLauncher("pending");
|
|
933
|
-
setMenuOpen(false);
|
|
934
|
-
}
|
|
935
|
-
}, "🗂 差异总览"), react.default.createElement("button", {
|
|
936
|
-
className: "edrv-diffmenu-item",
|
|
937
|
-
onClick: () => {
|
|
938
|
-
onOpenLauncher("archive");
|
|
939
|
-
setMenuOpen(false);
|
|
940
|
-
}
|
|
941
|
-
}, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
|
|
942
|
-
className: "edrv-diffmenu-item",
|
|
943
|
-
disabled: !canAct,
|
|
944
|
-
onClick: () => {
|
|
945
|
-
onRollback();
|
|
946
|
-
setMenuOpen(false);
|
|
947
|
-
}
|
|
948
|
-
}, "⟲ 回滚文件"), react.default.createElement("button", {
|
|
949
|
-
className: "edrv-diffmenu-item",
|
|
950
|
-
onClick: () => {
|
|
951
|
-
onRefresh();
|
|
952
|
-
setMenuOpen(false);
|
|
953
|
-
}
|
|
954
|
-
}, "⟳ 刷新"))) : null;
|
|
955
|
-
return react.default.createElement("div", {
|
|
956
|
-
className: "edrv-diffbar",
|
|
957
|
-
"data-edrv-diffbox": "1"
|
|
958
|
-
}, react.default.createElement("div", { className: "edrv-diffbar-row" }, react.default.createElement("button", {
|
|
959
|
-
className: "edrv-pill edrv-pill-ghost",
|
|
960
|
-
title: "上一个差异",
|
|
961
|
-
disabled: !canAct,
|
|
962
|
-
onClick: onPrevDiff
|
|
963
|
-
}, "↑"), react.default.createElement("span", {
|
|
964
|
-
className: "edrv-diffbar-count",
|
|
965
|
-
title: "当前文件内差异位置(点击展开/收起差异列表)",
|
|
966
|
-
onClick: () => setExpanded((v) => !v)
|
|
967
|
-
}, diffTotal ? diffIdx + 1 + "/" + diffTotal : "0/0"), react.default.createElement("button", {
|
|
968
|
-
className: "edrv-pill edrv-pill-ghost",
|
|
969
|
-
title: "下一个差异",
|
|
970
|
-
disabled: !canAct,
|
|
971
|
-
onClick: onNextDiff
|
|
972
|
-
}, "↓"), react.default.createElement("button", {
|
|
973
|
-
className: "edrv-pill edrv-pill-ghost",
|
|
974
|
-
title: "上一个差异文件",
|
|
975
|
-
disabled: !fileTotal,
|
|
976
|
-
onClick: onPrevFile
|
|
977
|
-
}, "←"), react.default.createElement("span", {
|
|
978
|
-
className: "edrv-diffbar-count edrv-count-file",
|
|
979
|
-
title: "差异文件位置"
|
|
980
|
-
}, fileTotal ? fileIdx + 1 + "/" + fileTotal + " 文件" : "0/0 文件"), react.default.createElement("button", {
|
|
981
|
-
className: "edrv-pill edrv-pill-ghost",
|
|
982
|
-
title: "下一个差异文件",
|
|
983
|
-
disabled: !fileTotal,
|
|
984
|
-
onClick: onNextFile
|
|
985
|
-
}, "→"), react.default.createElement("span", {
|
|
986
|
-
className: "edrv-diffbar-file",
|
|
987
|
-
title: activePath || ""
|
|
988
|
-
}, base), react.default.createElement("button", {
|
|
989
|
-
className: "edrv-pill edrv-pill-keep",
|
|
990
|
-
title: "采纳当前文件的全部差异",
|
|
991
|
-
disabled: !canAct,
|
|
992
|
-
onClick: onAcceptFile
|
|
993
|
-
}, "✓ Keep"), react.default.createElement("button", {
|
|
994
|
-
className: "edrv-pill edrv-pill-undo",
|
|
995
|
-
title: "不采纳当前文件的全部差异(回滚)",
|
|
996
|
-
disabled: !canAct,
|
|
997
|
-
onClick: onUndoFile
|
|
998
|
-
}, "↩ Undo"), react.default.createElement("button", {
|
|
999
|
-
className: "edrv-pill edrv-pill-ghost",
|
|
1000
|
-
title: "更多操作",
|
|
1001
|
-
onClick: () => setMenuOpen((v) => !v)
|
|
1002
|
-
}, "⋮")), bodyEl, menu);
|
|
1003
|
-
}
|
|
1004
|
-
//#endregion
|
|
1005
|
-
//#region src/client/ui/DiffBarEmpty.ts
|
|
1006
|
-
/**
|
|
1007
|
-
* dsh-vscode-mode client — DiffBarEmpty:无文件打开 / 当前文件无差异但有差异文件时的空态浮窗。
|
|
1008
|
-
* 迁移自原 src/client/index.ts 的 DiffBarEmpty,语义不改。
|
|
1009
|
-
* 作者 ddj 2026-08-20
|
|
1010
|
-
*/
|
|
1011
|
-
/**
|
|
1012
|
-
* 空态差异条:提示当前状态 + 查看下一个差异文件 + ⋮(总览/归档/刷新)。
|
|
1013
|
-
*/
|
|
1014
|
-
function DiffBarEmpty(props) {
|
|
1015
|
-
const { sum, active, staleCount, onNextFile, onOpenLauncher, onRefresh } = props;
|
|
1016
|
-
const [menuOpen, setMenuOpen] = react.default.useState(false);
|
|
1017
|
-
const n = sum?.totalFiles ?? 0;
|
|
1018
|
-
const tip = active ? staleCount > 0 ? "当前文件有 " + staleCount + " 处差异无法定位(可能已被后续修改影响)" : "当前文件无待处理差异" : "未打开文件";
|
|
1019
|
-
const menu = menuOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
|
|
1020
|
-
style: {
|
|
1021
|
-
position: "fixed",
|
|
1022
|
-
inset: 0,
|
|
1023
|
-
zIndex: 44
|
|
1024
|
-
},
|
|
1025
|
-
onClick: () => setMenuOpen(false)
|
|
1026
|
-
}), react.default.createElement("div", { className: "edrv-diffbar-menu" }, react.default.createElement("button", {
|
|
1027
|
-
className: "edrv-diffmenu-item",
|
|
1028
|
-
onClick: () => {
|
|
1029
|
-
onOpenLauncher("pending");
|
|
1030
|
-
setMenuOpen(false);
|
|
1031
|
-
}
|
|
1032
|
-
}, "🗂 差异总览"), react.default.createElement("button", {
|
|
1033
|
-
className: "edrv-diffmenu-item",
|
|
1034
|
-
onClick: () => {
|
|
1035
|
-
onOpenLauncher("archive");
|
|
1036
|
-
setMenuOpen(false);
|
|
1037
|
-
}
|
|
1038
|
-
}, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
|
|
1039
|
-
className: "edrv-diffmenu-item",
|
|
1040
|
-
onClick: () => {
|
|
1041
|
-
onRefresh();
|
|
1042
|
-
setMenuOpen(false);
|
|
1043
|
-
}
|
|
1044
|
-
}, "⟳ 刷新"))) : null;
|
|
1045
|
-
return react.default.createElement("div", {
|
|
1046
|
-
className: "edrv-diffbar",
|
|
1047
|
-
"data-edrv-diffbox": "1"
|
|
1048
|
-
}, react.default.createElement("div", { className: "edrv-diffbar-row" }, react.default.createElement("span", { className: "edrv-diffbar-count" }, "⚠ 差异 " + n + " 文件"), react.default.createElement("span", { className: "edrv-diffbar-file" }, tip), react.default.createElement("button", {
|
|
1049
|
-
className: "edrv-pill edrv-pill-keep",
|
|
1050
|
-
title: "打开并跳转到下一个差异文件",
|
|
1051
|
-
onClick: onNextFile
|
|
1052
|
-
}, "查看下一个差异文件"), react.default.createElement("button", {
|
|
1053
|
-
className: "edrv-pill edrv-pill-ghost",
|
|
1054
|
-
title: "更多操作",
|
|
1055
|
-
onClick: () => setMenuOpen((v) => !v)
|
|
1056
|
-
}, "⋮")), menu);
|
|
1057
|
-
}
|
|
1058
|
-
//#endregion
|
|
1059
891
|
//#region src/client/ui/DiffLauncher.ts
|
|
1060
892
|
/**
|
|
1061
893
|
* dsh-vscode-mode client — DiffLauncher:全局差异总览 + 归档 圆角下拉。
|
|
@@ -1288,6 +1120,213 @@ window.__ModuleLoader__.load({
|
|
|
1288
1120
|
}, "✕ 关闭")), react.default.createElement("div", { className: "edrv-launch-body" }, body));
|
|
1289
1121
|
}
|
|
1290
1122
|
//#endregion
|
|
1123
|
+
//#region src/client/sidebar/SidebarView.ts
|
|
1124
|
+
/**
|
|
1125
|
+
* dsh-vscode-mode client — 侧边栏容器(活动栏 + 面板区 + 拖拽调宽)。
|
|
1126
|
+
* 面板列表来自注册表(活动栏图标/徽标/激活态),渲染当前激活面板的 render(ctx);
|
|
1127
|
+
* 右侧手柄拖拽调宽(clamp 180–560),底部按钮收起侧边栏。
|
|
1128
|
+
* 作者 ddj 2026-08-26
|
|
1129
|
+
*/
|
|
1130
|
+
const W_MIN = 180;
|
|
1131
|
+
const W_MAX = 560;
|
|
1132
|
+
/**
|
|
1133
|
+
* 侧边栏容器。
|
|
1134
|
+
* @param props.registry 面板注册表(list() 提供面板顺序)
|
|
1135
|
+
* @param props.ctx 面板共享上下文
|
|
1136
|
+
* @param props.activePanel 当前激活面板 id
|
|
1137
|
+
* @param props.onActive 激活面板变化回调
|
|
1138
|
+
* @param props.width 面板区宽度
|
|
1139
|
+
* @param props.onWidth 宽度变化回调
|
|
1140
|
+
* @param props.onHide 收起侧边栏回调
|
|
1141
|
+
*/
|
|
1142
|
+
function SidebarView(props) {
|
|
1143
|
+
const panels = props.registry?.list?.() ?? [];
|
|
1144
|
+
const ctx = props.ctx;
|
|
1145
|
+
const activePanel = props.activePanel;
|
|
1146
|
+
const onActive = props.onActive;
|
|
1147
|
+
const width = props.width;
|
|
1148
|
+
const onWidth = props.onWidth;
|
|
1149
|
+
const onHide = props.onHide;
|
|
1150
|
+
const active = panels.find((p) => p.id === activePanel) || panels[0];
|
|
1151
|
+
const onPointerDown = (e) => {
|
|
1152
|
+
e.preventDefault();
|
|
1153
|
+
const startX = e.clientX;
|
|
1154
|
+
const startW = width;
|
|
1155
|
+
const onMove = (ev) => {
|
|
1156
|
+
const w = Math.max(W_MIN, Math.min(W_MAX, startW + (ev.clientX - startX)));
|
|
1157
|
+
onWidth(w);
|
|
1158
|
+
};
|
|
1159
|
+
const onUp = () => {
|
|
1160
|
+
window.removeEventListener("pointermove", onMove);
|
|
1161
|
+
window.removeEventListener("pointerup", onUp);
|
|
1162
|
+
};
|
|
1163
|
+
window.addEventListener("pointermove", onMove);
|
|
1164
|
+
window.addEventListener("pointerup", onUp);
|
|
1165
|
+
};
|
|
1166
|
+
const rail = react.default.createElement("div", { className: "edrv-rail" }, react.default.createElement("div", { style: {
|
|
1167
|
+
flex: 1,
|
|
1168
|
+
display: "flex",
|
|
1169
|
+
flexDirection: "column",
|
|
1170
|
+
gap: 2
|
|
1171
|
+
} }, panels.map((p) => {
|
|
1172
|
+
const count = typeof p.badge === "function" ? p.badge(ctx) : null;
|
|
1173
|
+
return react.default.createElement("button", {
|
|
1174
|
+
key: p.id,
|
|
1175
|
+
className: "edrv-rail-btn" + (active && p.id === active.id ? " edrv-rail-on" : ""),
|
|
1176
|
+
title: p.title,
|
|
1177
|
+
onClick: () => onActive(p.id)
|
|
1178
|
+
}, react.default.createElement("span", { className: "edrv-rail-icon" }, p.icon), count > 0 ? react.default.createElement("span", { className: "edrv-rail-badge" }, String(count)) : null);
|
|
1179
|
+
})), react.default.createElement("button", {
|
|
1180
|
+
className: "edrv-rail-btn",
|
|
1181
|
+
title: "隐藏侧边栏 (Ctrl+B)",
|
|
1182
|
+
onClick: onHide
|
|
1183
|
+
}, "◀"));
|
|
1184
|
+
return react.default.createElement("div", {
|
|
1185
|
+
className: "edrv-sidebar",
|
|
1186
|
+
style: { width }
|
|
1187
|
+
}, rail, react.default.createElement("div", { className: "edrv-side-body" }, active ? active.render(ctx) : null), react.default.createElement("div", {
|
|
1188
|
+
className: "edrv-side-resize",
|
|
1189
|
+
onPointerDown
|
|
1190
|
+
}));
|
|
1191
|
+
}
|
|
1192
|
+
//#endregion
|
|
1193
|
+
//#region src/client/diffDockStore.ts
|
|
1194
|
+
const values = /* @__PURE__ */ new Map();
|
|
1195
|
+
const sources = /* @__PURE__ */ new Map();
|
|
1196
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1197
|
+
/** 通知指定会话的 dock 订阅者。 */
|
|
1198
|
+
function notifyDiff(sessionId) {
|
|
1199
|
+
for (const listener of listeners.get(sessionId) ?? []) listener();
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* 发布指定会话的最新编辑器差异上下文。
|
|
1203
|
+
* @author ddj 2026年08月26号
|
|
1204
|
+
* @param sessionId 会话 id
|
|
1205
|
+
* @param snapshot 完整 DiffBox 操作上下文
|
|
1206
|
+
* @param source 发布者令牌,用于卸载时防止误清理新实例
|
|
1207
|
+
*/
|
|
1208
|
+
function publishDiffDock(sessionId, snapshot, source) {
|
|
1209
|
+
values.set(sessionId, snapshot);
|
|
1210
|
+
if (source) sources.set(sessionId, source);
|
|
1211
|
+
else sources.delete(sessionId);
|
|
1212
|
+
notifyDiff(sessionId);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* 清除指定会话的差异上下文。
|
|
1216
|
+
* @author ddj 2026年08月26号
|
|
1217
|
+
* @param sessionId 会话 id
|
|
1218
|
+
* @param source 可选发布者令牌;不匹配时忽略清理
|
|
1219
|
+
*/
|
|
1220
|
+
function clearDiffDock(sessionId, source) {
|
|
1221
|
+
if (source && sources.get(sessionId) !== source) return;
|
|
1222
|
+
values.delete(sessionId);
|
|
1223
|
+
sources.delete(sessionId);
|
|
1224
|
+
notifyDiff(sessionId);
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* 读取指定会话的最新上下文。
|
|
1228
|
+
* @author ddj 2026年08月26号
|
|
1229
|
+
* @param sessionId 会话 id
|
|
1230
|
+
* @returns 最新上下文或 null
|
|
1231
|
+
*/
|
|
1232
|
+
function readDiffDock(sessionId) {
|
|
1233
|
+
if (!sessionId) return null;
|
|
1234
|
+
return values.get(sessionId) ?? null;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* 订阅指定会话的上下文变化。
|
|
1238
|
+
* @author ddj 2026年08月26号
|
|
1239
|
+
* @param sessionId 会话 id
|
|
1240
|
+
* @param listener 变化回调
|
|
1241
|
+
* @returns 取消订阅函数
|
|
1242
|
+
*/
|
|
1243
|
+
function subscribeDiffDock(sessionId, listener) {
|
|
1244
|
+
let bucket = listeners.get(sessionId);
|
|
1245
|
+
if (!bucket) {
|
|
1246
|
+
bucket = /* @__PURE__ */ new Set();
|
|
1247
|
+
listeners.set(sessionId, bucket);
|
|
1248
|
+
}
|
|
1249
|
+
bucket.add(listener);
|
|
1250
|
+
return () => {
|
|
1251
|
+
bucket.delete(listener);
|
|
1252
|
+
if (!bucket.size) listeners.delete(sessionId);
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/client/diffDock.ts
|
|
1257
|
+
/**
|
|
1258
|
+
* dsh-vscode-mode client — 对话差异 dock 纯函数。
|
|
1259
|
+
* 只负责差异文件轮转和文案,状态查询仍由组件复用现有 edrv.list/summarize。
|
|
1260
|
+
* 作者 ddj 2026-08-26
|
|
1261
|
+
*/
|
|
1262
|
+
/**
|
|
1263
|
+
* 从稳定差异文件列表中取下一个路径,并返回下一次索引。
|
|
1264
|
+
* @author ddj 2026年08月26号
|
|
1265
|
+
* @param paths 待处理差异文件路径
|
|
1266
|
+
* @param index 当前轮转索引
|
|
1267
|
+
* @returns 下一个路径与归一化后的下一索引
|
|
1268
|
+
*/
|
|
1269
|
+
function nextDiffPath(paths, index) {
|
|
1270
|
+
if (!paths.length) return {
|
|
1271
|
+
path: null,
|
|
1272
|
+
index: 0
|
|
1273
|
+
};
|
|
1274
|
+
const at = (Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0) % paths.length;
|
|
1275
|
+
return {
|
|
1276
|
+
path: paths[at] ?? null,
|
|
1277
|
+
index: (at + 1) % paths.length
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* 生成对话差异 dock 的单按钮文案。
|
|
1282
|
+
* @author ddj 2026年08月26号
|
|
1283
|
+
* @param count 待处理差异文件数量
|
|
1284
|
+
* @returns 展示文案
|
|
1285
|
+
*/
|
|
1286
|
+
function diffDockText(count) {
|
|
1287
|
+
return "差异 " + Math.max(0, Math.floor(count)) + " 个文件 · 查看下一个";
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* 根据活动文件决定编辑页 dock 形态,加载期间保持 editor 结构稳定。
|
|
1291
|
+
* @author ddj 2026年08月26号
|
|
1292
|
+
* @param activePath 当前活动文件路径
|
|
1293
|
+
* @returns 编辑态或无文件空态
|
|
1294
|
+
*/
|
|
1295
|
+
function editorDockMode(activePath) {
|
|
1296
|
+
return activePath ? "editor" : "editor-empty";
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* 获取切换文件期间稳定展示的文件内差异数量。
|
|
1300
|
+
* @author ddj 2026年08月26号
|
|
1301
|
+
* @param ready 当前文件内容是否已加载完成
|
|
1302
|
+
* @param actualTotal 内容就绪后的精确差异数
|
|
1303
|
+
* @param fallbackTotal 内容加载期间的文件摘要差异数
|
|
1304
|
+
* @returns 非负整数差异数
|
|
1305
|
+
*/
|
|
1306
|
+
function displayDiffTotal(ready, actualTotal, fallbackTotal) {
|
|
1307
|
+
const value = ready ? actualTotal : fallbackTotal;
|
|
1308
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
1309
|
+
}
|
|
1310
|
+
//#endregion
|
|
1311
|
+
//#region src/client/editorLayout.ts
|
|
1312
|
+
/**
|
|
1313
|
+
* dsh-vscode-mode client — 编辑区与原生 composer 的几何边界计算。
|
|
1314
|
+
* @author ddj 2026年08月26号
|
|
1315
|
+
*/
|
|
1316
|
+
/**
|
|
1317
|
+
* 计算编辑根节点在当前会话滚动区内、composer 上方可使用的高度。
|
|
1318
|
+
* @author ddj 2026年08月26号
|
|
1319
|
+
* @param rootTop 编辑根节点顶边
|
|
1320
|
+
* @param scrollBottom 会话滚动区可视底边
|
|
1321
|
+
* @param composerTop composer 顶边;缺失时回退滚动区底边
|
|
1322
|
+
* @returns 非负可用高度
|
|
1323
|
+
*/
|
|
1324
|
+
function editorHeight(rootTop, scrollBottom, composerTop) {
|
|
1325
|
+
const root = Number.isFinite(rootTop) ? rootTop : 0;
|
|
1326
|
+
const scroll = Number.isFinite(scrollBottom) ? scrollBottom : root;
|
|
1327
|
+
return Math.max(0, Math.min(scroll, Number.isFinite(composerTop) ? composerTop : scroll) - root);
|
|
1328
|
+
}
|
|
1329
|
+
//#endregion
|
|
1291
1330
|
//#region src/client/ui/EditorView.ts
|
|
1292
1331
|
/**
|
|
1293
1332
|
* dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
|
|
@@ -1304,6 +1343,7 @@ window.__ModuleLoader__.load({
|
|
|
1304
1343
|
function EditorView(props) {
|
|
1305
1344
|
const sessionId = props?.sessionId;
|
|
1306
1345
|
const schedule = props.schedule;
|
|
1346
|
+
const addToConversation = props.addToConversation;
|
|
1307
1347
|
const [monaco, setMonaco] = react.default.useState(null);
|
|
1308
1348
|
const [monacoErr, setMonacoErr] = react.default.useState(null);
|
|
1309
1349
|
const [records, setRecords] = react.default.useState({});
|
|
@@ -1311,6 +1351,7 @@ window.__ModuleLoader__.load({
|
|
|
1311
1351
|
const [active, setActive] = react.default.useState(null);
|
|
1312
1352
|
const [dirtyMap, setDirtyMap] = react.default.useState({});
|
|
1313
1353
|
const [content, setContent] = react.default.useState(null);
|
|
1354
|
+
const [contentPath, setContentPath] = react.default.useState(null);
|
|
1314
1355
|
const [status, setStatus] = react.default.useState("");
|
|
1315
1356
|
const [error, setError] = react.default.useState(null);
|
|
1316
1357
|
const [loadError, setLoadError] = react.default.useState(null);
|
|
@@ -1326,7 +1367,13 @@ window.__ModuleLoader__.load({
|
|
|
1326
1367
|
const [hoverAct, setHoverAct] = react.default.useState(null);
|
|
1327
1368
|
const [diffIdx, setDiffIdx] = react.default.useState(0);
|
|
1328
1369
|
const [fileIdx, setFileIdx] = react.default.useState(0);
|
|
1370
|
+
const [tabMenu, setTabMenu] = react.default.useState(null);
|
|
1371
|
+
const [sidebarOn, setSidebarOn] = react.default.useState(true);
|
|
1372
|
+
const [sidebarW, setSidebarW] = react.default.useState(240);
|
|
1373
|
+
const [activePanel, setActivePanel] = react.default.useState("explorer");
|
|
1374
|
+
const [focusRequest, setFocusRequest] = react.default.useState(0);
|
|
1329
1375
|
const editorRef = react.default.useRef(null);
|
|
1376
|
+
const viewRootRef = react.default.useRef(null);
|
|
1330
1377
|
const monacoRef = react.default.useRef(null);
|
|
1331
1378
|
const modelsRef = react.default.useRef(/* @__PURE__ */ new Map());
|
|
1332
1379
|
const saveTimerRef = react.default.useRef(null);
|
|
@@ -1343,6 +1390,7 @@ window.__ModuleLoader__.load({
|
|
|
1343
1390
|
const hoverEditorRef = react.default.useRef(false);
|
|
1344
1391
|
const hoverPanelRef = react.default.useRef(false);
|
|
1345
1392
|
const batchBusyRef = react.default.useRef(false);
|
|
1393
|
+
const menuHandlersRef = react.default.useRef(null);
|
|
1346
1394
|
const diffRendererRef = react.default.useRef(null);
|
|
1347
1395
|
if (!diffRendererRef.current) diffRendererRef.current = createDiffRenderer((sid, t) => dbg(sid, t));
|
|
1348
1396
|
const currentRecords = react.default.useMemo(() => {
|
|
@@ -1350,7 +1398,13 @@ window.__ModuleLoader__.load({
|
|
|
1350
1398
|
for (const rec of Object.values(records)) if (rec.path === active && rec.archived !== true) list.push(rec);
|
|
1351
1399
|
return list;
|
|
1352
1400
|
}, [records, active]);
|
|
1353
|
-
const
|
|
1401
|
+
const contentReady = content !== null && contentPath === active;
|
|
1402
|
+
const regions = react.default.useMemo(() => diffRegions(currentRecords, contentReady ? content : null).filter((r) => !r.superseded), [
|
|
1403
|
+
currentRecords,
|
|
1404
|
+
content,
|
|
1405
|
+
contentPath,
|
|
1406
|
+
active
|
|
1407
|
+
]);
|
|
1354
1408
|
const pendingRegions = react.default.useMemo(() => regions.filter((r) => r.status === ST.PENDING && !r.stale), [regions]);
|
|
1355
1409
|
const staleRegions = react.default.useMemo(() => regions.filter((r) => r.status === ST.PENDING && r.stale), [regions]);
|
|
1356
1410
|
hoverRegionsRef.current = pendingRegions;
|
|
@@ -1381,9 +1435,12 @@ window.__ModuleLoader__.load({
|
|
|
1381
1435
|
return next;
|
|
1382
1436
|
});
|
|
1383
1437
|
};
|
|
1384
|
-
const refreshRecords = () => {
|
|
1438
|
+
const refreshRecords = (skipStale) => {
|
|
1385
1439
|
if (!sessionId) return;
|
|
1386
|
-
rpc("edrv.list", {
|
|
1440
|
+
rpc("edrv.list", {
|
|
1441
|
+
sessionId,
|
|
1442
|
+
...skipStale ? { skipStale: true } : {}
|
|
1443
|
+
}).then((res) => {
|
|
1387
1444
|
if (!res || !res.ok || !Array.isArray(res.records)) return;
|
|
1388
1445
|
const map = {};
|
|
1389
1446
|
for (const r of res.records) {
|
|
@@ -1407,6 +1464,7 @@ window.__ModuleLoader__.load({
|
|
|
1407
1464
|
if (seq !== loadSeqRef.current || path !== active) return;
|
|
1408
1465
|
if (res && res.ok) {
|
|
1409
1466
|
setContent(res.content);
|
|
1467
|
+
setContentPath(path);
|
|
1410
1468
|
setLoadStage((prev) => ({
|
|
1411
1469
|
progress: Math.max(84, prev.progress),
|
|
1412
1470
|
message: "文件已读取,准备创建编辑器…"
|
|
@@ -1440,9 +1498,21 @@ window.__ModuleLoader__.load({
|
|
|
1440
1498
|
react.default.useEffect(() => {
|
|
1441
1499
|
const onOpen = (e) => {
|
|
1442
1500
|
const p = e?.detail?.path;
|
|
1443
|
-
if (p)
|
|
1501
|
+
if (!p) return;
|
|
1502
|
+
if (e?.detail?.focusDiff === true) {
|
|
1503
|
+
pendingFocusRef.current = {
|
|
1504
|
+
path: p,
|
|
1505
|
+
region: null
|
|
1506
|
+
};
|
|
1507
|
+
setFocusRequest((value) => value + 1);
|
|
1508
|
+
}
|
|
1509
|
+
addTab(p, true);
|
|
1510
|
+
};
|
|
1511
|
+
const onShowLauncher = (event) => {
|
|
1512
|
+
const tab = event?.detail?.tab;
|
|
1513
|
+
if (tab === "pending" || tab === "archive") setLauncherTab(tab);
|
|
1514
|
+
setLauncherOpen(true);
|
|
1444
1515
|
};
|
|
1445
|
-
const onShowLauncher = () => setLauncherOpen(true);
|
|
1446
1516
|
window.addEventListener("edrv:open-editor", onOpen);
|
|
1447
1517
|
window.addEventListener("edrv:show-launcher", onShowLauncher);
|
|
1448
1518
|
return () => {
|
|
@@ -1450,6 +1520,58 @@ window.__ModuleLoader__.load({
|
|
|
1450
1520
|
window.removeEventListener("edrv:show-launcher", onShowLauncher);
|
|
1451
1521
|
};
|
|
1452
1522
|
}, [sessionId]);
|
|
1523
|
+
const dockSourceRef = react.default.useRef({});
|
|
1524
|
+
react.default.useEffect(() => () => clearDiffDock(sessionId, dockSourceRef.current), [sessionId]);
|
|
1525
|
+
react.default.useLayoutEffect(() => {
|
|
1526
|
+
const root = viewRootRef.current;
|
|
1527
|
+
const scroll = root?.closest?.("[data-conversation-scroll]");
|
|
1528
|
+
if (!root || !scroll) return;
|
|
1529
|
+
let seat = null;
|
|
1530
|
+
let frame = null;
|
|
1531
|
+
let resizeObserver = null;
|
|
1532
|
+
const update = () => {
|
|
1533
|
+
frame = null;
|
|
1534
|
+
const rootRect = root.getBoundingClientRect();
|
|
1535
|
+
const scrollRect = scroll.getBoundingClientRect();
|
|
1536
|
+
const seatRect = seat?.getBoundingClientRect?.();
|
|
1537
|
+
const height = editorHeight(rootRect.top, scrollRect.bottom, seatRect?.top);
|
|
1538
|
+
root.style.setProperty("--edrv-editor-height", height + "px");
|
|
1539
|
+
};
|
|
1540
|
+
const scheduleUpdate = () => {
|
|
1541
|
+
if (frame !== null) return;
|
|
1542
|
+
frame = requestAnimationFrame(update);
|
|
1543
|
+
};
|
|
1544
|
+
const attachSeat = () => {
|
|
1545
|
+
const next = scroll.querySelector?.("[data-composer-seat]");
|
|
1546
|
+
if (next === seat) {
|
|
1547
|
+
scheduleUpdate();
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
resizeObserver?.disconnect();
|
|
1551
|
+
seat = next;
|
|
1552
|
+
resizeObserver = null;
|
|
1553
|
+
if (seat && typeof ResizeObserver === "function") {
|
|
1554
|
+
resizeObserver = new ResizeObserver(scheduleUpdate);
|
|
1555
|
+
resizeObserver.observe(seat);
|
|
1556
|
+
}
|
|
1557
|
+
scheduleUpdate();
|
|
1558
|
+
};
|
|
1559
|
+
attachSeat();
|
|
1560
|
+
const mutationObserver = typeof MutationObserver === "function" ? new MutationObserver(attachSeat) : null;
|
|
1561
|
+
mutationObserver?.observe(scroll, { childList: true });
|
|
1562
|
+
const scrollObserver = typeof ResizeObserver === "function" ? new ResizeObserver(scheduleUpdate) : null;
|
|
1563
|
+
scrollObserver?.observe(scroll);
|
|
1564
|
+
window.addEventListener("resize", scheduleUpdate);
|
|
1565
|
+
scheduleUpdate();
|
|
1566
|
+
return () => {
|
|
1567
|
+
if (frame !== null) cancelAnimationFrame(frame);
|
|
1568
|
+
resizeObserver?.disconnect();
|
|
1569
|
+
scrollObserver?.disconnect();
|
|
1570
|
+
mutationObserver?.disconnect();
|
|
1571
|
+
window.removeEventListener("resize", scheduleUpdate);
|
|
1572
|
+
root.style.removeProperty("--edrv-editor-height");
|
|
1573
|
+
};
|
|
1574
|
+
}, [sessionId]);
|
|
1453
1575
|
react.default.useEffect(() => {
|
|
1454
1576
|
if (bootRef.current || !sessionId) return;
|
|
1455
1577
|
bootRef.current = true;
|
|
@@ -1477,9 +1599,53 @@ window.__ModuleLoader__.load({
|
|
|
1477
1599
|
active,
|
|
1478
1600
|
sessionId
|
|
1479
1601
|
]);
|
|
1602
|
+
react.default.useEffect(() => {
|
|
1603
|
+
if (!sessionId) return;
|
|
1604
|
+
try {
|
|
1605
|
+
const raw = localStorage.getItem("edrv.sidebar." + String(sessionId));
|
|
1606
|
+
if (raw) {
|
|
1607
|
+
const saved = JSON.parse(raw);
|
|
1608
|
+
if (typeof saved.on === "boolean") setSidebarOn(saved.on);
|
|
1609
|
+
if (typeof saved.width === "number") setSidebarW(Math.max(180, Math.min(560, saved.width)));
|
|
1610
|
+
if (typeof saved.panel === "string") setActivePanel(saved.panel);
|
|
1611
|
+
}
|
|
1612
|
+
} catch (e) {}
|
|
1613
|
+
}, [sessionId]);
|
|
1614
|
+
react.default.useEffect(() => {
|
|
1615
|
+
if (!sessionId) return;
|
|
1616
|
+
try {
|
|
1617
|
+
localStorage.setItem("edrv.sidebar." + String(sessionId), JSON.stringify({
|
|
1618
|
+
on: sidebarOn,
|
|
1619
|
+
width: sidebarW,
|
|
1620
|
+
panel: activePanel
|
|
1621
|
+
}));
|
|
1622
|
+
} catch (e) {}
|
|
1623
|
+
}, [
|
|
1624
|
+
sidebarOn,
|
|
1625
|
+
sidebarW,
|
|
1626
|
+
activePanel,
|
|
1627
|
+
sessionId
|
|
1628
|
+
]);
|
|
1629
|
+
react.default.useEffect(() => {
|
|
1630
|
+
const onKey = (e) => {
|
|
1631
|
+
if ((e.ctrlKey || e.metaKey) && String(e.key).toLowerCase() === "b") {
|
|
1632
|
+
e.preventDefault();
|
|
1633
|
+
e.stopPropagation();
|
|
1634
|
+
setSidebarOn((v) => !v);
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
window.addEventListener("keydown", onKey, true);
|
|
1638
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
1639
|
+
}, []);
|
|
1640
|
+
const pendingByPath = react.default.useMemo(() => {
|
|
1641
|
+
const out = {};
|
|
1642
|
+
for (const f of sum.files) if (f.pending > 0) out[f.path] = f.pending;
|
|
1643
|
+
return out;
|
|
1644
|
+
}, [sum]);
|
|
1480
1645
|
react.default.useEffect(() => {
|
|
1481
1646
|
if (!active) return;
|
|
1482
1647
|
setContent(null);
|
|
1648
|
+
setContentPath(null);
|
|
1483
1649
|
setLoadError(null);
|
|
1484
1650
|
setLoadStage({
|
|
1485
1651
|
progress: 10,
|
|
@@ -1489,8 +1655,16 @@ window.__ModuleLoader__.load({
|
|
|
1489
1655
|
loadContent(active, sessionId);
|
|
1490
1656
|
}, [active, sessionId]);
|
|
1491
1657
|
react.default.useEffect(() => {
|
|
1492
|
-
if (
|
|
1493
|
-
|
|
1658
|
+
if (!tabMenu) return;
|
|
1659
|
+
const onKey = (e) => {
|
|
1660
|
+
if (e.key === "Escape") dismissMenus();
|
|
1661
|
+
};
|
|
1662
|
+
window.addEventListener("keydown", onKey, true);
|
|
1663
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
1664
|
+
}, [tabMenu]);
|
|
1665
|
+
react.default.useEffect(() => {
|
|
1666
|
+
if (monaco || monacoErr) return;
|
|
1667
|
+
let alive = true;
|
|
1494
1668
|
const onProgress = (stage) => {
|
|
1495
1669
|
if (!alive) return;
|
|
1496
1670
|
const progress = stage.phase === "ready" ? 70 : Math.round(10 + stage.progress * .6);
|
|
@@ -1544,6 +1718,7 @@ window.__ModuleLoader__.load({
|
|
|
1544
1718
|
if (res && res.ok) {
|
|
1545
1719
|
setStatus("已保存 " + (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8));
|
|
1546
1720
|
setContent(text);
|
|
1721
|
+
setContentPath(active);
|
|
1547
1722
|
setDirtyMap((d) => Object.assign({}, d, { [active]: false }));
|
|
1548
1723
|
refreshRecords();
|
|
1549
1724
|
emitRefresh();
|
|
@@ -1648,6 +1823,46 @@ window.__ModuleLoader__.load({
|
|
|
1648
1823
|
ed.onDidChangeCursorPosition((e) => {
|
|
1649
1824
|
setCursor("Ln " + e.position.lineNumber + ", Col " + e.position.column);
|
|
1650
1825
|
});
|
|
1826
|
+
const menuHandlers = () => menuHandlersRef.current;
|
|
1827
|
+
const pathOf = (edx) => {
|
|
1828
|
+
const uriPath = (edx?.getModel?.())?.uri?.path;
|
|
1829
|
+
return uriPath ? decodeURIComponent(String(uriPath).replace(/^\//, "")) : null;
|
|
1830
|
+
};
|
|
1831
|
+
const selectionOf = (edx) => {
|
|
1832
|
+
const s = edx?.getSelection?.();
|
|
1833
|
+
if (!s) return null;
|
|
1834
|
+
if (s.startLineNumber === s.endLineNumber && s.startColumn === s.endColumn) return null;
|
|
1835
|
+
return {
|
|
1836
|
+
startLine: s.startLineNumber,
|
|
1837
|
+
endLine: s.endLineNumber
|
|
1838
|
+
};
|
|
1839
|
+
};
|
|
1840
|
+
const selKey = ed.createContextKey("edrvSelection", false);
|
|
1841
|
+
ed.onDidChangeCursorSelection((e) => {
|
|
1842
|
+
const s = e?.selection;
|
|
1843
|
+
selKey.set(!!(s && (s.startLineNumber !== s.endLineNumber || s.startColumn !== s.endColumn)));
|
|
1844
|
+
});
|
|
1845
|
+
ed.addAction({
|
|
1846
|
+
id: "edrv.addFileRef",
|
|
1847
|
+
label: "添加文件到对话",
|
|
1848
|
+
contextMenuGroupId: "1_edrv",
|
|
1849
|
+
precondition: "editorTextFocus",
|
|
1850
|
+
run: (edx) => menuHandlers()?.addRefToChat(pathOf(edx))
|
|
1851
|
+
});
|
|
1852
|
+
ed.addAction({
|
|
1853
|
+
id: "edrv.addSelectionRef",
|
|
1854
|
+
label: "添加选中内容为引用",
|
|
1855
|
+
contextMenuGroupId: "1_edrv",
|
|
1856
|
+
precondition: "edrvSelection",
|
|
1857
|
+
run: (edx) => {
|
|
1858
|
+
const s = selectionOf(edx);
|
|
1859
|
+
const p = pathOf(edx);
|
|
1860
|
+
if (s && p) menuHandlers()?.addRefToChat(p, {
|
|
1861
|
+
startLine: s.startLine,
|
|
1862
|
+
endLine: s.endLine
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
});
|
|
1651
1866
|
const hideSoon = () => {
|
|
1652
1867
|
if (hoverEditorRef.current || hoverPanelRef.current || hideTimerRef.current) return;
|
|
1653
1868
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -1710,7 +1925,8 @@ window.__ModuleLoader__.load({
|
|
|
1710
1925
|
}, [
|
|
1711
1926
|
active,
|
|
1712
1927
|
content,
|
|
1713
|
-
pendingRegions
|
|
1928
|
+
pendingRegions,
|
|
1929
|
+
focusRequest
|
|
1714
1930
|
]);
|
|
1715
1931
|
const jumpTo = (region) => {
|
|
1716
1932
|
if (editorRef.current) {
|
|
@@ -1747,10 +1963,15 @@ window.__ModuleLoader__.load({
|
|
|
1747
1963
|
setFileIdx(next);
|
|
1748
1964
|
openFile(sum.pendingFiles[next].path, true);
|
|
1749
1965
|
};
|
|
1750
|
-
const
|
|
1966
|
+
const openNextFile = () => {
|
|
1967
|
+
if (!sum.pendingFiles.length) return;
|
|
1968
|
+
if (sum.pendingFiles.some((file) => file.path === active)) gotoFile(1);
|
|
1969
|
+
else openFile(sum.pendingFiles[0].path, true);
|
|
1970
|
+
};
|
|
1971
|
+
const reloadFile = (skipStale) => {
|
|
1751
1972
|
if (!active) return;
|
|
1752
1973
|
loadContent(active, sessionId);
|
|
1753
|
-
refreshRecords();
|
|
1974
|
+
refreshRecords(skipStale === true);
|
|
1754
1975
|
};
|
|
1755
1976
|
/**
|
|
1756
1977
|
* 关闭当前差异浮窗并清理 hover 锚点。
|
|
@@ -1798,11 +2019,77 @@ window.__ModuleLoader__.load({
|
|
|
1798
2019
|
return false;
|
|
1799
2020
|
});
|
|
1800
2021
|
};
|
|
2022
|
+
/**
|
|
2023
|
+
* 批量采纳/不采纳:一次 RPC 处理多个差异(Keep 整文件 / Keep All / Undo All)。
|
|
2024
|
+
* 单次 setRecords 合并全部结果,避免逐条往返读写整个 sidecar。
|
|
2025
|
+
* @author ddj 2026年08月25号
|
|
2026
|
+
* @param items 决策项数组(callId/scope/hunkIndex/decision)
|
|
2027
|
+
* @returns Promise<{ok:number; fail:number}> 成功/失败计数
|
|
2028
|
+
*/
|
|
2029
|
+
const actMany = (items) => {
|
|
2030
|
+
if (!items.length) return Promise.resolve({
|
|
2031
|
+
ok: 0,
|
|
2032
|
+
fail: 0
|
|
2033
|
+
});
|
|
2034
|
+
return rpc("edrv.decideBatch", {
|
|
2035
|
+
sessionId,
|
|
2036
|
+
items
|
|
2037
|
+
}).then((res) => {
|
|
2038
|
+
if (!res || !res.ok || !Array.isArray(res.results)) {
|
|
2039
|
+
setError(res?.error ? String(res.error) : "批量处理失败");
|
|
2040
|
+
return {
|
|
2041
|
+
ok: 0,
|
|
2042
|
+
fail: items.length
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
let ok = 0;
|
|
2046
|
+
let fail = 0;
|
|
2047
|
+
const next = {};
|
|
2048
|
+
for (const item of res.results) if (item && item.ok) {
|
|
2049
|
+
ok++;
|
|
2050
|
+
if (item.record) next[item.callId] = item.record;
|
|
2051
|
+
} else fail++;
|
|
2052
|
+
setRecords((prev) => Object.assign({}, prev, next));
|
|
2053
|
+
return {
|
|
2054
|
+
ok,
|
|
2055
|
+
fail
|
|
2056
|
+
};
|
|
2057
|
+
}).catch((e) => {
|
|
2058
|
+
setError("批量处理异常:" + String(e));
|
|
2059
|
+
return {
|
|
2060
|
+
ok: 0,
|
|
2061
|
+
fail: items.length
|
|
2062
|
+
};
|
|
2063
|
+
});
|
|
2064
|
+
};
|
|
2065
|
+
/**
|
|
2066
|
+
* 决策项构造(与单条 actHunk 的 scope 语义一致:create 记录走 call 作用域)。
|
|
2067
|
+
* @author ddj 2026年08月25号
|
|
2068
|
+
* @param r 差异区域/待处理项(callId/idx/create)
|
|
2069
|
+
* @param reject true=不采纳
|
|
2070
|
+
* @returns decideBatch items 元素
|
|
2071
|
+
*/
|
|
2072
|
+
const itemOf = (r, reject) => ({
|
|
2073
|
+
callId: r.callId,
|
|
2074
|
+
scope: r.create ? "call" : "hunk",
|
|
2075
|
+
hunkIndex: r.idx,
|
|
2076
|
+
decision: reject ? "rejected" : "accepted"
|
|
2077
|
+
});
|
|
1801
2078
|
const acceptFile = () => {
|
|
1802
|
-
|
|
2079
|
+
actMany(pendingRegions.map((r) => itemOf(r, false))).then(({ ok, fail }) => {
|
|
2080
|
+
reloadFile(true);
|
|
2081
|
+
emitRefresh();
|
|
2082
|
+
setStatus("已采纳 " + ok + " 处差异" + (fail ? "," + fail + " 处失败" : ""));
|
|
2083
|
+
if (fail) setError(fail + " 处差异处理失败(可能已被后续修改影响),可刷新后重试");
|
|
2084
|
+
});
|
|
1803
2085
|
};
|
|
1804
2086
|
const undoFile = () => {
|
|
1805
|
-
|
|
2087
|
+
actMany([...pendingRegions].reverse().map((r) => itemOf(r, true))).then(({ ok, fail }) => {
|
|
2088
|
+
reloadFile(true);
|
|
2089
|
+
emitRefresh();
|
|
2090
|
+
setStatus("已不采纳 " + ok + " 处差异" + (fail ? "," + fail + " 处失败" : ""));
|
|
2091
|
+
if (fail) setError(fail + " 处差异处理失败(可能已被后续修改影响),可刷新后重试");
|
|
2092
|
+
});
|
|
1806
2093
|
};
|
|
1807
2094
|
const allPending = react.default.useMemo(() => {
|
|
1808
2095
|
const out = [];
|
|
@@ -1820,7 +2107,7 @@ window.__ModuleLoader__.load({
|
|
|
1820
2107
|
return out;
|
|
1821
2108
|
}, [records]);
|
|
1822
2109
|
/**
|
|
1823
|
-
* 批量处理所有差异文件:采纳全部 / 不采纳全部。不采纳按 at
|
|
2110
|
+
* 批量处理所有差异文件:采纳全部 / 不采纳全部。不采纳按 at 降序(新改先回滚)排序后一次 RPC。
|
|
1824
2111
|
* @author ddj 2026年08月20号
|
|
1825
2112
|
* @param reject true=全部不采纳(回滚),false=全部采纳
|
|
1826
2113
|
*/
|
|
@@ -1828,17 +2115,15 @@ window.__ModuleLoader__.load({
|
|
|
1828
2115
|
if (batchBusyRef.current || !allPending.length) return;
|
|
1829
2116
|
batchBusyRef.current = true;
|
|
1830
2117
|
const list = reject ? [...allPending].sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : b.idx - a.idx) : allPending;
|
|
1831
|
-
|
|
1832
|
-
const run = async () => {
|
|
1833
|
-
for (const r of list) if (await actHunk(r, reject, true)) ok++;
|
|
1834
|
-
else fail++;
|
|
2118
|
+
actMany(list.map((r) => itemOf(r, reject))).then(({ ok, fail }) => {
|
|
1835
2119
|
batchBusyRef.current = false;
|
|
1836
|
-
reloadFile();
|
|
2120
|
+
reloadFile(true);
|
|
1837
2121
|
emitRefresh();
|
|
1838
2122
|
setStatus((reject ? "已不采纳 " : "已采纳 ") + ok + " 处差异" + (fail ? "," + fail + " 处失败" : ""));
|
|
1839
2123
|
if (fail) setError(fail + " 处差异处理失败(可能已被后续修改影响),可刷新后重试");
|
|
1840
|
-
}
|
|
1841
|
-
|
|
2124
|
+
}).catch(() => {
|
|
2125
|
+
batchBusyRef.current = false;
|
|
2126
|
+
});
|
|
1842
2127
|
};
|
|
1843
2128
|
const acceptAllFiles = () => actAllPending(false);
|
|
1844
2129
|
const undoAllFiles = () => actAllPending(true);
|
|
@@ -1856,6 +2141,33 @@ window.__ModuleLoader__.load({
|
|
|
1856
2141
|
} else setError(res?.error ? String(res.error) : "回滚失败");
|
|
1857
2142
|
}).catch((e) => setError("回滚异常:" + String(e)));
|
|
1858
2143
|
};
|
|
2144
|
+
/**
|
|
2145
|
+
* 关闭 Tab 右键菜单。
|
|
2146
|
+
* @author ddj 2026年08月25号
|
|
2147
|
+
*/
|
|
2148
|
+
const dismissMenus = () => {
|
|
2149
|
+
setTabMenu(null);
|
|
2150
|
+
};
|
|
2151
|
+
/** 把「添加到对话」返回状态映射为状态栏文案。
|
|
2152
|
+
* @author ddj 2026年08月25号 */
|
|
2153
|
+
const statusOfAdd = (outcome, okText) => {
|
|
2154
|
+
if (outcome === "ok") return okText;
|
|
2155
|
+
if (outcome === "busy") return okText + "(输入框忙,已降级纯文本)";
|
|
2156
|
+
return "无法添加到对话(无会话或输入框不可用)";
|
|
2157
|
+
};
|
|
2158
|
+
/** 添加文件/选中区引用到对话(异步,状态栏反馈)。 */
|
|
2159
|
+
const addRefToChat = (path, range) => {
|
|
2160
|
+
if (!path) {
|
|
2161
|
+
setStatus("无活动文件");
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
if (!addToConversation) {
|
|
2165
|
+
setStatus("添加到对话不可用");
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
addToConversation.appendReference(sessionId, path, range).then((o) => setStatus(statusOfAdd(o, "已添加文件引用")));
|
|
2169
|
+
};
|
|
2170
|
+
menuHandlersRef.current = { addRefToChat };
|
|
1859
2171
|
const openFile = (path, focusDiff) => {
|
|
1860
2172
|
if (!path) return;
|
|
1861
2173
|
addTab(path, true);
|
|
@@ -1902,6 +2214,15 @@ window.__ModuleLoader__.load({
|
|
|
1902
2214
|
flushSave();
|
|
1903
2215
|
setActive(t.path);
|
|
1904
2216
|
}
|
|
2217
|
+
},
|
|
2218
|
+
onContextMenu: (e) => {
|
|
2219
|
+
e.preventDefault();
|
|
2220
|
+
e.stopPropagation();
|
|
2221
|
+
setTabMenu({
|
|
2222
|
+
x: e.clientX,
|
|
2223
|
+
y: e.clientY,
|
|
2224
|
+
path: t.path
|
|
2225
|
+
});
|
|
1905
2226
|
}
|
|
1906
2227
|
}, react.default.createElement("span", { style: {
|
|
1907
2228
|
overflow: "hidden",
|
|
@@ -1931,25 +2252,28 @@ window.__ModuleLoader__.load({
|
|
|
1931
2252
|
const pathBar = react.default.createElement("div", {
|
|
1932
2253
|
className: "edrv-pathbar",
|
|
1933
2254
|
title: active || ""
|
|
1934
|
-
}, react.default.createElement("span", { className: "edrv-pb-name" }, active ? String(active).split(/[\\/]/).pop() : "未打开文件"), react.default.createElement("span", { className: "edrv-pb-full" }, active || "使用右上搜索框 (Ctrl+P) 打开文件"));
|
|
2255
|
+
}, 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);
|
|
1935
2256
|
const tabRow = react.default.createElement("div", { style: {
|
|
1936
2257
|
display: "flex",
|
|
1937
2258
|
alignItems: "center",
|
|
1938
2259
|
background: "var(--dsw-alias-bg-layer-1,transparent)",
|
|
1939
2260
|
flexShrink: 0
|
|
1940
|
-
} }, tabsEl, react.default.createElement(
|
|
1941
|
-
sessionId,
|
|
1942
|
-
onOpen: (p) => openFile(p, false)
|
|
1943
|
-
}));
|
|
1944
|
-
const statusBar = react.default.createElement("div", { className: "edrv-statusbar" }, react.default.createElement("span", null, "编辑"), react.default.createElement("span", null, active ? langOf(active) : ""), react.default.createElement("span", null, cursor), status ? react.default.createElement("span", null, status) : null, react.default.createElement("span", { style: { flex: 1 } }), sum.totalFiles > 0 ? react.default.createElement("button", {
|
|
2261
|
+
} }, tabsEl, sum.totalFiles > 0 ? react.default.createElement("button", {
|
|
1945
2262
|
className: "edrv-diffchip",
|
|
1946
2263
|
title: "打开差异总览/归档",
|
|
1947
2264
|
onClick: () => setLauncherOpen((o) => !o)
|
|
1948
2265
|
}, "⚠ 差异 " + sum.totalFiles + " 文件") : null, react.default.createElement("button", {
|
|
2266
|
+
className: "edrv-chip-btn" + (sidebarOn ? " edrv-chip-on" : ""),
|
|
2267
|
+
title: "切换侧边栏 (Ctrl+B)",
|
|
2268
|
+
onClick: () => setSidebarOn((v) => !v)
|
|
2269
|
+
}, "☰"), react.default.createElement("button", {
|
|
1949
2270
|
className: "edrv-chip-btn",
|
|
1950
2271
|
title: "刷新",
|
|
1951
2272
|
onClick: reloadFile
|
|
1952
|
-
}, "⟳")
|
|
2273
|
+
}, "⟳"), react.default.createElement(QuickOpen, {
|
|
2274
|
+
sessionId,
|
|
2275
|
+
onOpen: (p) => openFile(p, false)
|
|
2276
|
+
}));
|
|
1953
2277
|
const otherFiles = sum.pendingFiles.filter((f) => f.path !== active);
|
|
1954
2278
|
/**
|
|
1955
2279
|
* 渲染编辑器/文件加载进度面板。
|
|
@@ -1992,6 +2316,7 @@ window.__ModuleLoader__.load({
|
|
|
1992
2316
|
else if (content === null && loadError) body = loadingBody("文件加载失败:" + loadError, 0, () => {
|
|
1993
2317
|
setLoadError(null);
|
|
1994
2318
|
setContent(null);
|
|
2319
|
+
setContentPath(null);
|
|
1995
2320
|
setLoadStage({
|
|
1996
2321
|
progress: 10,
|
|
1997
2322
|
message: "重新读取文件…"
|
|
@@ -2044,6 +2369,40 @@ window.__ModuleLoader__.load({
|
|
|
2044
2369
|
actHunk(hoverAct.region, true);
|
|
2045
2370
|
}
|
|
2046
2371
|
}, "↩ Undo")) : null;
|
|
2372
|
+
react.default.useEffect(() => {
|
|
2373
|
+
if (!sessionId) return;
|
|
2374
|
+
const mode = editorDockMode(active);
|
|
2375
|
+
publishDiffDock(sessionId, {
|
|
2376
|
+
mode,
|
|
2377
|
+
pendingRegions,
|
|
2378
|
+
staleRegions,
|
|
2379
|
+
onAct: actHunk,
|
|
2380
|
+
onAcceptFile: acceptFile,
|
|
2381
|
+
onUndoFile: undoFile,
|
|
2382
|
+
onAcceptAllFiles: acceptAllFiles,
|
|
2383
|
+
onUndoAllFiles: undoAllFiles,
|
|
2384
|
+
allPendingCount: allPending.length,
|
|
2385
|
+
onRollback: rollbackFile,
|
|
2386
|
+
onJump: jumpTo,
|
|
2387
|
+
otherFiles,
|
|
2388
|
+
onOpenOther: (path) => openFile(path, true),
|
|
2389
|
+
onOpenLauncher: (tab) => {
|
|
2390
|
+
setLauncherTab(tab || "pending");
|
|
2391
|
+
setLauncherOpen(true);
|
|
2392
|
+
},
|
|
2393
|
+
onRefresh: reloadFile,
|
|
2394
|
+
activePath: active,
|
|
2395
|
+
diffIdx: contentReady ? diffIdx : 0,
|
|
2396
|
+
diffTotal: displayDiffTotal(contentReady, pendingRegions.length, sum.files.find((file) => file.path === active)?.pending ?? 0),
|
|
2397
|
+
fileIdx,
|
|
2398
|
+
fileTotal: sum.pendingFiles.length,
|
|
2399
|
+
onPrevDiff: () => gotoDiff(-1),
|
|
2400
|
+
onNextDiff: () => gotoDiff(1),
|
|
2401
|
+
onPrevFile: () => gotoFile(-1),
|
|
2402
|
+
onNextFile: () => gotoFile(1),
|
|
2403
|
+
onOpenNextFile: openNextFile
|
|
2404
|
+
}, dockSourceRef.current);
|
|
2405
|
+
});
|
|
2047
2406
|
const overlay = launcherOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
|
|
2048
2407
|
style: {
|
|
2049
2408
|
position: "fixed",
|
|
@@ -2060,63 +2419,89 @@ window.__ModuleLoader__.load({
|
|
|
2060
2419
|
openFile(p, true);
|
|
2061
2420
|
setLauncherOpen(false);
|
|
2062
2421
|
}
|
|
2063
|
-
})) :
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
setLauncherTab(tab || "pending");
|
|
2078
|
-
setLauncherOpen(true);
|
|
2422
|
+
})) : null;
|
|
2423
|
+
/**
|
|
2424
|
+
* 计算右键菜单固定定位(clamp 防越出视口)。
|
|
2425
|
+
* @author ddj 2026年08月25号
|
|
2426
|
+
*/
|
|
2427
|
+
const menuPos = (x, y) => ({
|
|
2428
|
+
left: Math.max(4, Math.min(x, (window.innerWidth || 800) - 224)),
|
|
2429
|
+
top: Math.max(4, Math.min(y, (window.innerHeight || 600) - 176))
|
|
2430
|
+
});
|
|
2431
|
+
const menuBackdrop = tabMenu ? react.default.createElement("div", {
|
|
2432
|
+
style: {
|
|
2433
|
+
position: "fixed",
|
|
2434
|
+
inset: 0,
|
|
2435
|
+
zIndex: 70
|
|
2079
2436
|
},
|
|
2080
|
-
|
|
2437
|
+
onClick: dismissMenus,
|
|
2438
|
+
onContextMenu: (e) => {
|
|
2439
|
+
e.preventDefault();
|
|
2440
|
+
dismissMenus();
|
|
2441
|
+
}
|
|
2442
|
+
}) : null;
|
|
2443
|
+
const tabMenuEl = tabMenu ? react.default.createElement("div", {
|
|
2444
|
+
className: "edrv-ctxmenu",
|
|
2445
|
+
style: menuPos(tabMenu.x, tabMenu.y)
|
|
2446
|
+
}, react.default.createElement("button", {
|
|
2447
|
+
className: "edrv-ctxmenu-item",
|
|
2448
|
+
onClick: () => {
|
|
2449
|
+
addRefToChat(tabMenu.path);
|
|
2450
|
+
dismissMenus();
|
|
2451
|
+
}
|
|
2452
|
+
}, "添加文件到对话"), react.default.createElement("div", { className: "edrv-ctxmenu-sep" }), react.default.createElement("button", {
|
|
2453
|
+
className: "edrv-ctxmenu-item edrv-ctxmenu-danger",
|
|
2454
|
+
onClick: () => {
|
|
2455
|
+
closeTab(tabMenu.path);
|
|
2456
|
+
dismissMenus();
|
|
2457
|
+
}
|
|
2458
|
+
}, "关闭标签页")) : null;
|
|
2459
|
+
const sidebarPanels = props.sidebarPanels;
|
|
2460
|
+
const sidebarCtx = {
|
|
2461
|
+
sessionId,
|
|
2462
|
+
openFile: (p) => openFile(p, false),
|
|
2081
2463
|
activePath: active,
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
onPrevDiff: () => gotoDiff(-1),
|
|
2087
|
-
onNextDiff: () => gotoDiff(1),
|
|
2088
|
-
onPrevFile: () => gotoFile(-1),
|
|
2089
|
-
onNextFile: () => gotoFile(1)
|
|
2090
|
-
}) : sum.pendingFiles.length > 0 ? react.default.createElement(DiffBarEmpty, {
|
|
2091
|
-
sum,
|
|
2092
|
-
active,
|
|
2093
|
-
staleCount: staleRegions.length,
|
|
2094
|
-
onNextFile: () => {
|
|
2095
|
-
if (sum.pendingFiles.length) openFile(sum.pendingFiles[0].path, true);
|
|
2096
|
-
},
|
|
2097
|
-
onOpenLauncher: (tab) => {
|
|
2098
|
-
setLauncherTab(tab || "pending");
|
|
2099
|
-
setLauncherOpen(true);
|
|
2464
|
+
pendingByPath,
|
|
2465
|
+
sum: {
|
|
2466
|
+
totalFiles: sum.totalFiles,
|
|
2467
|
+
files: sum.files
|
|
2100
2468
|
},
|
|
2101
|
-
|
|
2102
|
-
}
|
|
2469
|
+
refreshRecords: () => refreshRecords()
|
|
2470
|
+
};
|
|
2471
|
+
const editorArea = react.default.createElement("div", { className: "edrv-editor-area" }, body, hoverEl, overlay);
|
|
2472
|
+
const mainCol = react.default.createElement("div", {
|
|
2473
|
+
className: "edrv-main-col",
|
|
2474
|
+
style: {
|
|
2475
|
+
flex: 1,
|
|
2476
|
+
minWidth: 0,
|
|
2477
|
+
minHeight: 0,
|
|
2478
|
+
display: "flex",
|
|
2479
|
+
flexDirection: "column",
|
|
2480
|
+
overflow: "hidden"
|
|
2481
|
+
}
|
|
2482
|
+
}, pathBar, tabRow, editorArea);
|
|
2483
|
+
const editorRow = react.default.createElement("div", { className: "edrv-editor-row" }, sidebarOn && sidebarPanels ? react.default.createElement(SidebarView, {
|
|
2484
|
+
registry: sidebarPanels,
|
|
2485
|
+
ctx: sidebarCtx,
|
|
2486
|
+
activePanel,
|
|
2487
|
+
onActive: setActivePanel,
|
|
2488
|
+
width: sidebarW,
|
|
2489
|
+
onWidth: setSidebarW,
|
|
2490
|
+
onHide: () => setSidebarOn(false)
|
|
2491
|
+
}) : null, mainCol);
|
|
2103
2492
|
return react.default.createElement("div", {
|
|
2493
|
+
ref: viewRootRef,
|
|
2104
2494
|
"data-edrv-view": "1",
|
|
2105
2495
|
style: {
|
|
2106
|
-
height: "100%",
|
|
2496
|
+
height: "var(--edrv-editor-height, 100%)",
|
|
2497
|
+
maxHeight: "var(--edrv-editor-height, 100%)",
|
|
2107
2498
|
minHeight: 0,
|
|
2108
2499
|
display: "flex",
|
|
2109
2500
|
flexDirection: "column",
|
|
2110
2501
|
background: "var(--dsw-alias-bg-base,transparent)",
|
|
2111
2502
|
overflow: "hidden"
|
|
2112
2503
|
}
|
|
2113
|
-
},
|
|
2114
|
-
position: "relative",
|
|
2115
|
-
flex: 1,
|
|
2116
|
-
minHeight: 0,
|
|
2117
|
-
display: "flex",
|
|
2118
|
-
flexDirection: "column"
|
|
2119
|
-
} }, body, hoverEl, overlay), statusBar);
|
|
2504
|
+
}, editorRow, menuBackdrop, tabMenuEl);
|
|
2120
2505
|
}
|
|
2121
2506
|
//#endregion
|
|
2122
2507
|
//#region src/client/ui/DiffBadge.ts
|
|
@@ -2199,6 +2584,230 @@ window.__ModuleLoader__.load({
|
|
|
2199
2584
|
} }, String(n)));
|
|
2200
2585
|
}
|
|
2201
2586
|
//#endregion
|
|
2587
|
+
//#region src/client/ui/DiffBox.ts
|
|
2588
|
+
/**
|
|
2589
|
+
* dsh-vscode-mode client — DiffBox:每个文件底部紧凑差异条(per-file hunk + ⋮ 二级菜单)。
|
|
2590
|
+
* 迁移自原 src/client/index.ts 的 DiffBox,语义不改。
|
|
2591
|
+
* 作者 ddj 2026-08-20
|
|
2592
|
+
*/
|
|
2593
|
+
/**
|
|
2594
|
+
* 折叠控制使用任务栏同尺寸的 chevron 轮廓,避免依赖未声明的运行时图标包。
|
|
2595
|
+
* @author ddj 2026年08月25号
|
|
2596
|
+
* @param open 是否已展开
|
|
2597
|
+
* @returns 14×14 的装饰性 chevron SVG
|
|
2598
|
+
*/
|
|
2599
|
+
function chevronOf(open) {
|
|
2600
|
+
const path = open ? "M3.25 5.25 7 9l3.75-3.75" : "M3.25 8.75 7 5l3.75 3.75";
|
|
2601
|
+
return react.default.createElement("svg", {
|
|
2602
|
+
width: 14,
|
|
2603
|
+
height: 14,
|
|
2604
|
+
viewBox: "0 0 14 14",
|
|
2605
|
+
fill: "none",
|
|
2606
|
+
"aria-hidden": true,
|
|
2607
|
+
focusable: false
|
|
2608
|
+
}, react.default.createElement("path", {
|
|
2609
|
+
d: path,
|
|
2610
|
+
stroke: "currentColor",
|
|
2611
|
+
strokeWidth: 1.2,
|
|
2612
|
+
strokeLinecap: "round",
|
|
2613
|
+
strokeLinejoin: "round"
|
|
2614
|
+
}));
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
|
|
2618
|
+
* (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
|
|
2619
|
+
*/
|
|
2620
|
+
function DiffBox(props) {
|
|
2621
|
+
const { pendingRegions = [], staleRegions = [], onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles, allPendingCount = 0, onRollback, onJump, otherFiles = [], onOpenOther, onOpenLauncher, onRefresh, activePath, diffIdx = 0, diffTotal = 0, fileIdx = 0, fileTotal = 0, onPrevDiff, onNextDiff, onPrevFile, onNextFile, mode = "editor", onOpenNextFile, dock = false } = props;
|
|
2622
|
+
const [expanded, setExpanded] = react.default.useState(false);
|
|
2623
|
+
const [detailsOpen, setDetailsOpen] = react.default.useState(false);
|
|
2624
|
+
const detailsId = react.default.useId();
|
|
2625
|
+
const canAct = pendingRegions.length > 0;
|
|
2626
|
+
const base = String(activePath || "").split(/[\\/]/).pop() || "";
|
|
2627
|
+
if (mode === "chat" || mode === "editor-empty") return react.default.createElement("div", {
|
|
2628
|
+
className: "edrv-diffbar edrv-diffbar-dock",
|
|
2629
|
+
"data-edrv-diffbox": "1",
|
|
2630
|
+
"data-edrv-diff-mode": mode
|
|
2631
|
+
}, react.default.createElement("div", { className: "edrv-diffbar-row edrv-diffbar-dock-row" }, react.default.createElement("button", {
|
|
2632
|
+
type: "button",
|
|
2633
|
+
className: "edrv-diffbar-dock-button",
|
|
2634
|
+
title: "切换到下一个差异文件",
|
|
2635
|
+
"aria-label": diffDockText(fileTotal),
|
|
2636
|
+
onClick: onOpenNextFile
|
|
2637
|
+
}, diffDockText(fileTotal))));
|
|
2638
|
+
let bodyEl = null;
|
|
2639
|
+
if (expanded) {
|
|
2640
|
+
const rows = pendingRegions.map((r) => {
|
|
2641
|
+
const key = callIdAttr(r.callId, r.idx);
|
|
2642
|
+
return react.default.createElement("div", {
|
|
2643
|
+
key,
|
|
2644
|
+
className: "edrv-diffrow",
|
|
2645
|
+
"data-edrv-hunk": key,
|
|
2646
|
+
title: "跳转到 L" + (r.start ?? "?"),
|
|
2647
|
+
onClick: () => onJump(r)
|
|
2648
|
+
}, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + "-" + (r.end ?? "?")), r.oldLines.length ? react.default.createElement("span", { className: "edrv-diffrow-o" }, "-" + r.oldLines.length) : null, r.newLines.length ? react.default.createElement("span", { className: "edrv-diffrow-n" }, "+" + r.newLines.length) : null, r.create ? react.default.createElement("span", { className: "edrv-diffrow-tag" }, "新建") : null, react.default.createElement("span", { style: { flex: 1 } }));
|
|
2649
|
+
});
|
|
2650
|
+
const stale = staleRegions.map((r) => react.default.createElement("div", {
|
|
2651
|
+
key: "stale" + callIdAttr(r.callId, r.idx),
|
|
2652
|
+
className: "edrv-diffrow edrv-diffrow-stale",
|
|
2653
|
+
title: "差异无法定位(文件可能已被手动修改)"
|
|
2654
|
+
}, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + " 无法定位"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, "冲突/需刷新"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, badgeOf(r.status))));
|
|
2655
|
+
let othersEl = null;
|
|
2656
|
+
if (otherFiles.length) othersEl = react.default.createElement("div", { className: "edrv-diffbox-others" }, react.default.createElement("span", { className: "edrv-diffrow-tag" }, "其他差异文件 (" + otherFiles.length + "):"), otherFiles.map((f) => react.default.createElement("button", {
|
|
2657
|
+
key: f.path,
|
|
2658
|
+
className: "edrv-diffrow-file",
|
|
2659
|
+
title: f.path,
|
|
2660
|
+
onClick: () => onOpenOther(f.path)
|
|
2661
|
+
}, String(f.path).split(/[\\/]/).pop() + " (" + f.pending + ")")));
|
|
2662
|
+
bodyEl = react.default.createElement("div", { className: "edrv-diffbar-body" }, ...rows, ...stale, othersEl);
|
|
2663
|
+
}
|
|
2664
|
+
const details = detailsOpen ? react.default.createElement("div", {
|
|
2665
|
+
className: "edrv-diff-details",
|
|
2666
|
+
id: detailsId
|
|
2667
|
+
}, react.default.createElement("button", {
|
|
2668
|
+
className: "edrv-diffmenu-item",
|
|
2669
|
+
disabled: !allPendingCount,
|
|
2670
|
+
onClick: onAcceptAllFiles
|
|
2671
|
+
}, "✓ Keep All(全部文件采纳)"), react.default.createElement("button", {
|
|
2672
|
+
className: "edrv-diffmenu-item danger",
|
|
2673
|
+
disabled: !allPendingCount,
|
|
2674
|
+
onClick: onUndoAllFiles
|
|
2675
|
+
}, "↩ Undo All(全部文件不采纳)"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
|
|
2676
|
+
className: "edrv-diffmenu-item",
|
|
2677
|
+
onClick: () => onOpenLauncher("pending")
|
|
2678
|
+
}, "🗂 差异总览"), react.default.createElement("button", {
|
|
2679
|
+
className: "edrv-diffmenu-item",
|
|
2680
|
+
onClick: () => onOpenLauncher("archive")
|
|
2681
|
+
}, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
|
|
2682
|
+
className: "edrv-diffmenu-item",
|
|
2683
|
+
disabled: !canAct,
|
|
2684
|
+
onClick: onRollback
|
|
2685
|
+
}, "⟲ 回滚文件"), react.default.createElement("button", {
|
|
2686
|
+
className: "edrv-diffmenu-item",
|
|
2687
|
+
onClick: onRefresh
|
|
2688
|
+
}, "⟳ 刷新")) : null;
|
|
2689
|
+
return react.default.createElement("div", {
|
|
2690
|
+
className: "edrv-diffbar edrv-diffbar-dock",
|
|
2691
|
+
"data-edrv-diffbox": "1",
|
|
2692
|
+
"data-edrv-diff-mode": "editor"
|
|
2693
|
+
}, react.default.createElement("div", { className: "edrv-diffbar-row edrv-diffbar-editor-row" }, react.default.createElement("div", { className: "edrv-diffbar-editor-content" }, react.default.createElement("div", { className: "edrv-diffbar-nav" }, react.default.createElement("button", {
|
|
2694
|
+
className: "edrv-pill edrv-pill-ghost",
|
|
2695
|
+
title: "上一个差异",
|
|
2696
|
+
disabled: !canAct,
|
|
2697
|
+
onClick: onPrevDiff
|
|
2698
|
+
}, "↑"), react.default.createElement("span", {
|
|
2699
|
+
className: "edrv-diffbar-count edrv-diffbar-progress",
|
|
2700
|
+
title: "当前文件内差异位置(点击展开/收起差异列表)",
|
|
2701
|
+
onClick: () => setExpanded((v) => !v)
|
|
2702
|
+
}, diffTotal ? diffIdx + 1 + "/" + diffTotal : "0/0"), react.default.createElement("button", {
|
|
2703
|
+
className: "edrv-pill edrv-pill-ghost",
|
|
2704
|
+
title: "下一个差异",
|
|
2705
|
+
disabled: !canAct,
|
|
2706
|
+
onClick: onNextDiff
|
|
2707
|
+
}, "↓")), react.default.createElement("div", { className: "edrv-diffbar-files" }, react.default.createElement("button", {
|
|
2708
|
+
className: "edrv-pill edrv-pill-ghost",
|
|
2709
|
+
title: "上一个差异文件",
|
|
2710
|
+
disabled: !fileTotal,
|
|
2711
|
+
onClick: onPrevFile
|
|
2712
|
+
}, "←"), react.default.createElement("span", {
|
|
2713
|
+
className: "edrv-diffbar-count edrv-count-file edrv-diffbar-progress",
|
|
2714
|
+
title: "差异文件位置"
|
|
2715
|
+
}, fileTotal ? fileIdx + 1 + "/" + fileTotal + " 文件" : "0/0 文件"), react.default.createElement("button", {
|
|
2716
|
+
className: "edrv-pill edrv-pill-ghost",
|
|
2717
|
+
title: "下一个差异文件",
|
|
2718
|
+
disabled: !fileTotal,
|
|
2719
|
+
onClick: onNextFile
|
|
2720
|
+
}, "→"), react.default.createElement("span", {
|
|
2721
|
+
className: "edrv-diffbar-file edrv-diffbar-summary",
|
|
2722
|
+
title: activePath || ""
|
|
2723
|
+
}, base)), react.default.createElement("div", { className: "edrv-diffbar-actions" }, react.default.createElement("button", {
|
|
2724
|
+
className: "edrv-pill edrv-pill-keep",
|
|
2725
|
+
title: "采纳当前文件的全部差异",
|
|
2726
|
+
disabled: !canAct,
|
|
2727
|
+
onClick: onAcceptFile
|
|
2728
|
+
}, "✓ Keep"), react.default.createElement("button", {
|
|
2729
|
+
className: "edrv-pill edrv-pill-undo",
|
|
2730
|
+
title: "不采纳当前文件的全部差异(回滚)",
|
|
2731
|
+
disabled: !canAct,
|
|
2732
|
+
onClick: onUndoFile
|
|
2733
|
+
}, "↩ Undo"), react.default.createElement("button", {
|
|
2734
|
+
className: "edrv-pill edrv-pill-ghost edrv-diff-details-toggle",
|
|
2735
|
+
title: detailsOpen ? "收起差异操作" : "展开差异操作",
|
|
2736
|
+
"aria-label": detailsOpen ? "收起差异操作" : "展开差异操作",
|
|
2737
|
+
"aria-expanded": detailsOpen,
|
|
2738
|
+
"aria-controls": detailsId,
|
|
2739
|
+
onClick: () => setDetailsOpen((value) => !value)
|
|
2740
|
+
}, chevronOf(detailsOpen))))), bodyEl, details);
|
|
2741
|
+
}
|
|
2742
|
+
//#endregion
|
|
2743
|
+
//#region src/client/ui/ConversationDiffDock.ts
|
|
2744
|
+
/**
|
|
2745
|
+
* dsh-vscode-mode client — 会话级差异 dock。
|
|
2746
|
+
* conversation.input.dock 是唯一实例:普通对话显示摘要,文件编辑页显示完整操作条。
|
|
2747
|
+
* 作者 ddj 2026-08-26
|
|
2748
|
+
*/
|
|
2749
|
+
const nextIndexBySession = /* @__PURE__ */ new Map();
|
|
2750
|
+
/**
|
|
2751
|
+
* 对话输入框上方的唯一差异 dock。
|
|
2752
|
+
* @author ddj 2026年08月26号
|
|
2753
|
+
* @param props DSH 注入的会话属性
|
|
2754
|
+
* @returns 差异 dock React 元素或 null
|
|
2755
|
+
*/
|
|
2756
|
+
function ConversationDiffDock(props) {
|
|
2757
|
+
const sessionId = props?.sessionId;
|
|
2758
|
+
const [summary, setSummary] = react.default.useState(null);
|
|
2759
|
+
const [, setStoreRevision] = react.default.useState(0);
|
|
2760
|
+
const seq = react.default.useRef(0);
|
|
2761
|
+
const load = react.default.useCallback(() => {
|
|
2762
|
+
if (!sessionId) return;
|
|
2763
|
+
const current = ++seq.current;
|
|
2764
|
+
rpc("edrv.list", { sessionId }).then((res) => {
|
|
2765
|
+
if (current !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return;
|
|
2766
|
+
setSummary(summarize(res.records));
|
|
2767
|
+
}).catch(() => {});
|
|
2768
|
+
}, [sessionId]);
|
|
2769
|
+
react.default.useEffect(() => {
|
|
2770
|
+
load();
|
|
2771
|
+
const timer = setInterval(load, 5e3);
|
|
2772
|
+
const onRefresh = () => load();
|
|
2773
|
+
window.addEventListener("edrv:refresh", onRefresh);
|
|
2774
|
+
return () => {
|
|
2775
|
+
clearInterval(timer);
|
|
2776
|
+
window.removeEventListener("edrv:refresh", onRefresh);
|
|
2777
|
+
};
|
|
2778
|
+
}, [load]);
|
|
2779
|
+
react.default.useEffect(() => {
|
|
2780
|
+
if (!sessionId) return void 0;
|
|
2781
|
+
const sync = () => setStoreRevision((value) => value + 1);
|
|
2782
|
+
sync();
|
|
2783
|
+
return subscribeDiffDock(sessionId, sync);
|
|
2784
|
+
}, [sessionId]);
|
|
2785
|
+
const renderDock = (content, key) => react.default.createElement("div", {
|
|
2786
|
+
key,
|
|
2787
|
+
className: "edrv-diff-dock-shell",
|
|
2788
|
+
"data-edrv-diff-dock-shell": "1"
|
|
2789
|
+
}, content);
|
|
2790
|
+
const editorSnapshot = readDiffDock(sessionId);
|
|
2791
|
+
if (editorSnapshot) {
|
|
2792
|
+
if (editorSnapshot.mode === "editor-empty" && !editorSnapshot.fileTotal) return null;
|
|
2793
|
+
return renderDock(react.default.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId);
|
|
2794
|
+
}
|
|
2795
|
+
if (!sessionId || !summary?.pendingFiles?.length) return null;
|
|
2796
|
+
const paths = summary.pendingFiles.map((file) => file.path);
|
|
2797
|
+
const click = () => {
|
|
2798
|
+
const current = nextIndexBySession.get(sessionId) ?? 0;
|
|
2799
|
+
const next = nextDiffPath(paths, current);
|
|
2800
|
+
nextIndexBySession.set(sessionId, next.index);
|
|
2801
|
+
if (next.path) openDiffView(next.path);
|
|
2802
|
+
};
|
|
2803
|
+
return renderDock(react.default.createElement(DiffBox, {
|
|
2804
|
+
mode: "chat",
|
|
2805
|
+
dock: true,
|
|
2806
|
+
fileTotal: paths.length,
|
|
2807
|
+
onOpenNextFile: click
|
|
2808
|
+
}), sessionId || "chat");
|
|
2809
|
+
}
|
|
2810
|
+
//#endregion
|
|
2202
2811
|
//#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
|
|
2203
2812
|
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";
|
|
2204
2813
|
const tagId = "dsh-vscode-mode/mcp.css";
|
|
@@ -2919,10 +3528,349 @@ window.__ModuleLoader__.load({
|
|
|
2919
3528
|
return typeof value === "string" && value.trim() ? value.trim() : AUTO_OPEN_TOOL;
|
|
2920
3529
|
}
|
|
2921
3530
|
//#endregion
|
|
3531
|
+
//#region src/client/addToConversation.ts
|
|
3532
|
+
/** DSH reference source 名(dsh-client-ui-reference 注册的 @file/@session 统一源)。 */
|
|
3533
|
+
const REF_SOURCE = "reference";
|
|
3534
|
+
/**
|
|
3535
|
+
* 生成 DSH @file 语法引用串:cwd 相对化、\ → /、含空白时按 @"path" 语法加引号。
|
|
3536
|
+
* cwd 外/无法相对化的路径回退原路径。
|
|
3537
|
+
* @author ddj 2026年08月25号
|
|
3538
|
+
* @param path 打开的文件路径(相对 cwd 或绝对)
|
|
3539
|
+
* @param cwd 会话工作区目录(可选)
|
|
3540
|
+
* @returns 引用串(如 @src/index.ts 或 @"a b.ts")
|
|
3541
|
+
*/
|
|
3542
|
+
function mentionOf(path, cwd) {
|
|
3543
|
+
const raw = String(path);
|
|
3544
|
+
let rel = raw;
|
|
3545
|
+
if (cwd) {
|
|
3546
|
+
const base = String(cwd).replace(/[\\/]+$/, "");
|
|
3547
|
+
const up = raw.replace(/\\/g, "/");
|
|
3548
|
+
const baseUp = base.replace(/\\/g, "/");
|
|
3549
|
+
if (up.startsWith(baseUp + "/")) rel = up.slice(baseUp.length + 1);
|
|
3550
|
+
else if (up === baseUp) rel = "";
|
|
3551
|
+
}
|
|
3552
|
+
rel = rel.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3553
|
+
if (!rel) rel = raw.replace(/\\/g, "/");
|
|
3554
|
+
if (/[\u0000-\u001f\u007f-\u009f"]/u.test(rel)) return "@" + raw;
|
|
3555
|
+
return /\s/u.test(rel) ? `@"${rel}"` : `@${rel}`;
|
|
3556
|
+
}
|
|
3557
|
+
/** 引用标签:文件名 + 可选行区间(chip 内展示,不带 @ 前缀)。
|
|
3558
|
+
* @author ddj 2026年08月25号 */
|
|
3559
|
+
function labelOf(path, range) {
|
|
3560
|
+
const base = String(path).split(/[\\/]/).pop() || String(path);
|
|
3561
|
+
if (!range || range.endLine < range.startLine) return base;
|
|
3562
|
+
return range.startLine === range.endLine ? `${base} L${range.startLine}` : `${base} L${range.startLine}-${range.endLine}`;
|
|
3563
|
+
}
|
|
3564
|
+
/** 引用 ref:引用串 + 可选行区间(序列化直出,agent 据此读取对应片段)。
|
|
3565
|
+
* @author ddj 2026年08月25号 */
|
|
3566
|
+
function refOf(mention, range) {
|
|
3567
|
+
if (!range || range.endLine < range.startLine) return mention;
|
|
3568
|
+
return range.startLine === range.endLine ? `${mention} L${range.startLine}` : `${mention} L${range.startLine}-${range.endLine}`;
|
|
3569
|
+
}
|
|
3570
|
+
/** 构造 DSH 文件引用插入载荷(source=reference,发送序列化为 mention(+range) 文本)。 */
|
|
3571
|
+
function buildFileRef(path, cwd, range) {
|
|
3572
|
+
const mention = mentionOf(path, cwd);
|
|
3573
|
+
return {
|
|
3574
|
+
mention,
|
|
3575
|
+
reference: {
|
|
3576
|
+
source: REF_SOURCE,
|
|
3577
|
+
ref: refOf(mention, range),
|
|
3578
|
+
label: labelOf(path, range),
|
|
3579
|
+
appearance: "file",
|
|
3580
|
+
clipboardText: mention
|
|
3581
|
+
}
|
|
3582
|
+
};
|
|
3583
|
+
}
|
|
3584
|
+
/**
|
|
3585
|
+
* 解析会话输入门面;缺失时返回 undefined(动作侧守卫降级)。
|
|
3586
|
+
* @author ddj 2026年08月25号
|
|
3587
|
+
* @param ctx 客户端服务上下文
|
|
3588
|
+
* @param sessionId 会话 id
|
|
3589
|
+
* @returns SessionInput 或 undefined
|
|
3590
|
+
*/
|
|
3591
|
+
function inputFor(ctx, sessionId) {
|
|
3592
|
+
if (!sessionId) return void 0;
|
|
3593
|
+
try {
|
|
3594
|
+
const sessions = ctx.get("sessions");
|
|
3595
|
+
const conversation = ctx.get("conversation");
|
|
3596
|
+
const actx = sessions?.scope?.(sessionId);
|
|
3597
|
+
const shell = actx && conversation?.input?.for?.(actx);
|
|
3598
|
+
return shell && typeof shell.setDraft === "function" ? shell : void 0;
|
|
3599
|
+
} catch {
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
/** 取当前草稿长度与版本;无输入门面时返回 null。
|
|
3604
|
+
* @author ddj 2026年08月25号 */
|
|
3605
|
+
function draftCursor(input) {
|
|
3606
|
+
try {
|
|
3607
|
+
const s = input?.state?.getSnapshot?.();
|
|
3608
|
+
if (!s) return null;
|
|
3609
|
+
return {
|
|
3610
|
+
draft: String(s.draft ?? ""),
|
|
3611
|
+
draftRev: Number(s.draftRev ?? 0)
|
|
3612
|
+
};
|
|
3613
|
+
} catch {
|
|
3614
|
+
return null;
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
/** 从 sessions 列表快照读会话工作区目录(与 index.ts 既有读取方式一致)。
|
|
3618
|
+
* @author ddj 2026年08月25号 */
|
|
3619
|
+
function cwdOf(ctx, sessionId) {
|
|
3620
|
+
if (!sessionId) return void 0;
|
|
3621
|
+
try {
|
|
3622
|
+
return ctx.get("sessions")?.list?.getSnapshot?.()?.byId?.[sessionId]?.cwd;
|
|
3623
|
+
} catch {
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
/** 写入成功后的 composer 内联提示(门面可用时;失败静默)。
|
|
3628
|
+
* @author ddj 2026年08月25号 */
|
|
3629
|
+
function notify(input, text) {
|
|
3630
|
+
try {
|
|
3631
|
+
input.notify?.("info", text);
|
|
3632
|
+
} catch {}
|
|
3633
|
+
}
|
|
3634
|
+
/** 输入门面插入引用(拦截异常视作未应用,走降级)。
|
|
3635
|
+
* @author ddj 2026年08月25号 */
|
|
3636
|
+
function safeInsert(input, reference, span) {
|
|
3637
|
+
try {
|
|
3638
|
+
return input.insertReference(reference, span) === true;
|
|
3639
|
+
} catch {
|
|
3640
|
+
return false;
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* 创建「添加到对话」动作集(apply 阶段构建一次,随 props 传给 EditorView)。
|
|
3645
|
+
* @author ddj 2026年08月25号
|
|
3646
|
+
* @param ctx 客户端服务上下文(sessions + conversation)
|
|
3647
|
+
* @returns 动作集
|
|
3648
|
+
*/
|
|
3649
|
+
function createAddToConversation(ctx) {
|
|
3650
|
+
const appendReference = async (sessionId, path, range) => {
|
|
3651
|
+
const input = inputFor(ctx, sessionId);
|
|
3652
|
+
if (!input) return "unavailable";
|
|
3653
|
+
const cur = draftCursor(input);
|
|
3654
|
+
if (!cur) return "unavailable";
|
|
3655
|
+
const { reference, mention } = buildFileRef(path, cwdOf(ctx, sessionId), range);
|
|
3656
|
+
if (!safeInsert(input, reference, {
|
|
3657
|
+
start: cur.draft.length,
|
|
3658
|
+
end: cur.draft.length,
|
|
3659
|
+
draftRev: cur.draftRev
|
|
3660
|
+
})) {
|
|
3661
|
+
const gap = cur.draft.length > 0 && !/\s$/.test(cur.draft) ? " " : "";
|
|
3662
|
+
input.setDraft(cur.draft + gap + mention + " ");
|
|
3663
|
+
notify(input, "已添加文件引用 " + mention);
|
|
3664
|
+
return "busy";
|
|
3665
|
+
}
|
|
3666
|
+
notify(input, "已添加文件引用 " + mention);
|
|
3667
|
+
return "ok";
|
|
3668
|
+
};
|
|
3669
|
+
return { appendReference };
|
|
3670
|
+
}
|
|
3671
|
+
//#endregion
|
|
3672
|
+
//#region src/client/sidebar/registry.ts
|
|
3673
|
+
/** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
|
|
3674
|
+
function panelRegister(entries, notify, panel) {
|
|
3675
|
+
if (!panel.id || typeof panel.render !== "function") throw new TypeError("侧边栏面板必须提供 id 和 render");
|
|
3676
|
+
entries.set(panel.id, panel);
|
|
3677
|
+
notify();
|
|
3678
|
+
}
|
|
3679
|
+
/**
|
|
3680
|
+
* 创建生命周期独立的面板注册表。
|
|
3681
|
+
* @author ddj 2026年08月26号
|
|
3682
|
+
* @returns 面板注册表
|
|
3683
|
+
*/
|
|
3684
|
+
function createSidebarPanelRegistry() {
|
|
3685
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3686
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3687
|
+
const notify = () => {
|
|
3688
|
+
for (const listener of listeners) listener();
|
|
3689
|
+
};
|
|
3690
|
+
const list = () => [...entries.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
3691
|
+
return {
|
|
3692
|
+
register(panel) {
|
|
3693
|
+
panelRegister(entries, notify, panel);
|
|
3694
|
+
return () => {
|
|
3695
|
+
if (entries.get(panel.id) !== panel) return;
|
|
3696
|
+
entries.delete(panel.id);
|
|
3697
|
+
notify();
|
|
3698
|
+
};
|
|
3699
|
+
},
|
|
3700
|
+
list,
|
|
3701
|
+
subscribe(listener) {
|
|
3702
|
+
listeners.add(listener);
|
|
3703
|
+
return () => listeners.delete(listener);
|
|
3704
|
+
},
|
|
3705
|
+
get: (id) => entries.get(id)
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
//#endregion
|
|
3709
|
+
//#region src/client/sidebar/panels/FileExplorer.ts
|
|
3710
|
+
/**
|
|
3711
|
+
* dsh-vscode-mode client — 侧边栏「文件管理」面板(懒加载目录树)。
|
|
3712
|
+
* 点目录展开/收起(首次经 edrv.listDir 拉取并缓存),点文件经 ctx.openFile 打开;
|
|
3713
|
+
* 差异角标取 pendingByPath,活动文件高亮;edrv:refresh 事件与手动刷新重载树。
|
|
3714
|
+
* 作者 ddj 2026-08-26
|
|
3715
|
+
*/
|
|
3716
|
+
const DIR_CAP = 4e3;
|
|
3717
|
+
/**
|
|
3718
|
+
* 目录树面板主体。
|
|
3719
|
+
* @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath)
|
|
3720
|
+
*/
|
|
3721
|
+
function FileExplorer(props) {
|
|
3722
|
+
const ctx = props?.ctx;
|
|
3723
|
+
const sessionId = ctx?.sessionId;
|
|
3724
|
+
const openFile = ctx?.openFile;
|
|
3725
|
+
const activePath = ctx?.activePath ?? null;
|
|
3726
|
+
const pendingByPath = ctx?.pendingByPath ?? {};
|
|
3727
|
+
const [root, setRoot] = react.default.useState(null);
|
|
3728
|
+
const [dirs, setDirs] = react.default.useState({});
|
|
3729
|
+
const [expanded, setExpanded] = react.default.useState({});
|
|
3730
|
+
const [loading, setLoading] = react.default.useState({});
|
|
3731
|
+
const [error, setError] = react.default.useState(null);
|
|
3732
|
+
const tokensRef = react.default.useRef({});
|
|
3733
|
+
const expandedRef = react.default.useRef({});
|
|
3734
|
+
expandedRef.current = expanded;
|
|
3735
|
+
const loadDir = (rel) => {
|
|
3736
|
+
const token = (tokensRef.current[rel] || 0) + 1;
|
|
3737
|
+
tokensRef.current[rel] = token;
|
|
3738
|
+
setLoading((prev) => Object.assign({}, prev, { [rel]: true }));
|
|
3739
|
+
setError(null);
|
|
3740
|
+
rpc("edrv.listDir", {
|
|
3741
|
+
sessionId,
|
|
3742
|
+
path: rel
|
|
3743
|
+
}).then((res) => {
|
|
3744
|
+
if (tokensRef.current[rel] !== token) return;
|
|
3745
|
+
if (res && res.ok && Array.isArray(res.entries)) {
|
|
3746
|
+
setDirs((prev) => Object.assign({}, prev, { [rel]: res.entries }));
|
|
3747
|
+
if (res.root && !root) setRoot(res.root);
|
|
3748
|
+
} else setError(res?.error ? String(res.error) : "读取目录失败");
|
|
3749
|
+
}).catch((e) => {
|
|
3750
|
+
if (tokensRef.current[rel] !== token) return;
|
|
3751
|
+
setError("读取目录异常:" + String(e));
|
|
3752
|
+
}).finally(() => {
|
|
3753
|
+
if (tokensRef.current[rel] !== token) return;
|
|
3754
|
+
setLoading((prev) => {
|
|
3755
|
+
const next = Object.assign({}, prev);
|
|
3756
|
+
delete next[rel];
|
|
3757
|
+
return next;
|
|
3758
|
+
});
|
|
3759
|
+
});
|
|
3760
|
+
};
|
|
3761
|
+
const toggle = (rel) => {
|
|
3762
|
+
if (expanded[rel] === true) {
|
|
3763
|
+
setExpanded((prev) => Object.assign({}, prev, { [rel]: false }));
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
if (!dirs[rel]) loadDir(rel);
|
|
3767
|
+
setExpanded((prev) => Object.assign({}, prev, { [rel]: true }));
|
|
3768
|
+
};
|
|
3769
|
+
const refresh = () => {
|
|
3770
|
+
const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
|
|
3771
|
+
tokensRef.current = {};
|
|
3772
|
+
setDirs({});
|
|
3773
|
+
setLoading({});
|
|
3774
|
+
setError(null);
|
|
3775
|
+
if (keep.length) {
|
|
3776
|
+
const next = {};
|
|
3777
|
+
for (const rel of keep) next[rel] = true;
|
|
3778
|
+
setExpanded(next);
|
|
3779
|
+
for (const rel of keep) loadDir(rel);
|
|
3780
|
+
} else setExpanded({});
|
|
3781
|
+
loadDir("");
|
|
3782
|
+
};
|
|
3783
|
+
react.default.useEffect(() => {
|
|
3784
|
+
tokensRef.current = {};
|
|
3785
|
+
setDirs({});
|
|
3786
|
+
setExpanded({});
|
|
3787
|
+
setLoading({});
|
|
3788
|
+
setError(null);
|
|
3789
|
+
setRoot(null);
|
|
3790
|
+
loadDir("");
|
|
3791
|
+
}, [sessionId]);
|
|
3792
|
+
react.default.useEffect(() => {
|
|
3793
|
+
const onRefresh = () => refresh();
|
|
3794
|
+
window.addEventListener("edrv:refresh", onRefresh);
|
|
3795
|
+
return () => window.removeEventListener("edrv:refresh", onRefresh);
|
|
3796
|
+
}, []);
|
|
3797
|
+
const rootName = root ? String(root).split(/[\\/]/).pop() || root : "";
|
|
3798
|
+
const rowEl = (e, depth, isDir, isOpen) => {
|
|
3799
|
+
const pending = isDir ? 0 : pendingByPath[e.path] ?? 0;
|
|
3800
|
+
const active = !isDir && e.path === activePath;
|
|
3801
|
+
const dim = e.type === "other";
|
|
3802
|
+
return react.default.createElement("div", {
|
|
3803
|
+
key: e.path,
|
|
3804
|
+
className: "edrv-tree-row" + (active ? " edrv-tree-active" : ""),
|
|
3805
|
+
title: e.path,
|
|
3806
|
+
style: { paddingLeft: 6 + depth * 14 },
|
|
3807
|
+
onClick: () => {
|
|
3808
|
+
if (isDir) toggle(e.path);
|
|
3809
|
+
else openFile(e.path);
|
|
3810
|
+
}
|
|
3811
|
+
}, react.default.createElement("span", { className: "edrv-tree-chev" }, isDir ? isOpen ? "▾" : "▸" : ""), react.default.createElement("span", { className: "edrv-tree-name" + (dim ? " edrv-tree-dim" : "") }, isDir ? isOpen ? "📂" : "📁" : "📄", " ", e.name), pending > 0 ? react.default.createElement("span", { className: "edrv-tree-badge" }, String(pending)) : null);
|
|
3812
|
+
};
|
|
3813
|
+
const rowsOf = (rel, depth) => {
|
|
3814
|
+
const entries = dirs[rel];
|
|
3815
|
+
if (!entries) return [react.default.createElement("div", {
|
|
3816
|
+
key: rel + ":loading",
|
|
3817
|
+
className: "edrv-tree-loading",
|
|
3818
|
+
style: { paddingLeft: 6 + depth * 14 }
|
|
3819
|
+
}, loading[rel] ? "加载中…" : "无法加载")];
|
|
3820
|
+
const rows = [];
|
|
3821
|
+
const cap = entries.length;
|
|
3822
|
+
for (let i = 0; i < cap; i++) {
|
|
3823
|
+
const e = entries[i];
|
|
3824
|
+
const isDir = e.type === "directory";
|
|
3825
|
+
const isOpen = expanded[e.path] === true;
|
|
3826
|
+
rows.push(rowEl(e, depth, isDir, isOpen));
|
|
3827
|
+
if (isDir && isOpen) rows.push(...rowsOf(e.path, depth + 1));
|
|
3828
|
+
}
|
|
3829
|
+
if (cap >= DIR_CAP) rows.push(react.default.createElement("div", {
|
|
3830
|
+
key: rel + ":cap",
|
|
3831
|
+
className: "edrv-tree-loading",
|
|
3832
|
+
style: { paddingLeft: 6 + depth * 14 }
|
|
3833
|
+
}, "目录条目过多,仅显示前 4000 项"));
|
|
3834
|
+
return rows;
|
|
3835
|
+
};
|
|
3836
|
+
return react.default.createElement("div", { className: "edrv-side-panel" }, react.default.createElement("div", { className: "edrv-side-head" }, react.default.createElement("span", { className: "edrv-side-title" }, "资源管理器"), react.default.createElement("span", {
|
|
3837
|
+
className: "edrv-side-root",
|
|
3838
|
+
title: root || ""
|
|
3839
|
+
}, rootName), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("button", {
|
|
3840
|
+
className: "edrv-side-btn",
|
|
3841
|
+
title: "刷新目录树",
|
|
3842
|
+
onClick: refresh
|
|
3843
|
+
}, "⟳")), react.default.createElement("div", { className: "edrv-tree" }, error ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(error)), react.default.createElement("button", {
|
|
3844
|
+
className: "edrv-side-btn",
|
|
3845
|
+
onClick: () => refresh()
|
|
3846
|
+
}, "重试")) : null, rowsOf("", 0)));
|
|
3847
|
+
}
|
|
3848
|
+
//#endregion
|
|
3849
|
+
//#region src/client/sidebar/panels/index.ts
|
|
3850
|
+
/**
|
|
3851
|
+
* dsh-vscode-mode client — 「文件管理」面板定义(注册表一项)。
|
|
3852
|
+
* 作者 ddj 2026-08-26
|
|
3853
|
+
*/
|
|
3854
|
+
/**
|
|
3855
|
+
* 构造文件管理面板定义。
|
|
3856
|
+
* @author ddj 2026年08月26号
|
|
3857
|
+
* @returns 面板定义(含活动栏徽标 = 差异文件总数)
|
|
3858
|
+
*/
|
|
3859
|
+
function createFilePanel() {
|
|
3860
|
+
return {
|
|
3861
|
+
id: "explorer",
|
|
3862
|
+
title: "文件管理",
|
|
3863
|
+
icon: "📁",
|
|
3864
|
+
order: 10,
|
|
3865
|
+
badge: (ctx) => ctx.sum && ctx.sum.totalFiles > 0 ? ctx.sum.totalFiles : null,
|
|
3866
|
+
render: (ctx) => react.default.createElement(FileExplorer, { ctx })
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
//#endregion
|
|
2922
3870
|
//#region src/client/index.ts
|
|
2923
3871
|
/**
|
|
2924
3872
|
* dsh-vscode-mode client — 浏览器半入口:slot 注册 + 装配。
|
|
2925
|
-
* 挂点:conversation.view「文件编辑」页签(中央 Monaco 编辑器)+ header 差异角标。
|
|
3873
|
+
* 挂点:conversation.view「文件编辑」页签(中央 Monaco 编辑器)+ conversation.input.dock 差异条 + header 差异角标。
|
|
2926
3874
|
* 与 Host 通信:同源 fetch('/edrv/rpc')(shared/rpc 契约)。
|
|
2927
3875
|
*
|
|
2928
3876
|
* ⚠️ 跨版本 slot 装配(2026-08-21):新版 DSH 的 slots 系统要求 slot 必须由父 entry
|
|
@@ -2930,7 +3878,8 @@ window.__ModuleLoader__.load({
|
|
|
2930
3878
|
* 声明未就绪时抛 `slot ... is not declared (a parent entry's children table must declare it)`。
|
|
2931
3879
|
* 正确写法 = `ctx.slots.inject(name, () => ctx.slots.register(...))`:声明存在时同步
|
|
2932
3880
|
* 执行,否则等待声明(官方 ui-conversation 自身即此模式);旧版(rc.8 及更早)同样支持,
|
|
2933
|
-
* 故跨版本兼容。slot
|
|
3881
|
+
* 故跨版本兼容。slot 名:conversation.view / conversation.session.header.utilities /
|
|
3882
|
+
* conversation.input.dock。
|
|
2934
3883
|
* 作者 ddj 2026-08-20
|
|
2935
3884
|
*/
|
|
2936
3885
|
const inject = [
|
|
@@ -2941,6 +3890,7 @@ window.__ModuleLoader__.load({
|
|
|
2941
3890
|
"remote",
|
|
2942
3891
|
"workspaces",
|
|
2943
3892
|
"sessions",
|
|
3893
|
+
"conversation",
|
|
2944
3894
|
"settingsScope",
|
|
2945
3895
|
"webUiSettings"
|
|
2946
3896
|
];
|
|
@@ -2954,10 +3904,27 @@ window.__ModuleLoader__.load({
|
|
|
2954
3904
|
const registry = createFileOpenerRegistry();
|
|
2955
3905
|
const workspaces = ctx.get("workspaces");
|
|
2956
3906
|
const sessions = ctx.get("sessions");
|
|
3907
|
+
const monacoList = sessions?.list;
|
|
3908
|
+
if (typeof window !== "undefined" && monacoList && typeof monacoList.subscribe === "function") {
|
|
3909
|
+
const list = monacoList;
|
|
3910
|
+
const schedulePreload = () => {
|
|
3911
|
+
const preload = () => loadMonaco(() => {}).catch(() => {});
|
|
3912
|
+
if (typeof window.requestIdleCallback === "function") window.requestIdleCallback(preload, { timeout: 2e3 });
|
|
3913
|
+
else schedule(preload, 300);
|
|
3914
|
+
};
|
|
3915
|
+
const onSessionEnter = () => {
|
|
3916
|
+
if (!list.getSnapshot()?.current) return;
|
|
3917
|
+
schedulePreload();
|
|
3918
|
+
};
|
|
3919
|
+
ctx.effect(() => list.subscribe(onSessionEnter), "vscode-mode: monaco session trigger");
|
|
3920
|
+
onSessionEnter();
|
|
3921
|
+
}
|
|
2957
3922
|
const originalOpenPath = workspaces?.openPath;
|
|
2958
3923
|
const binder = pickSettingsBinder(ctx);
|
|
2959
3924
|
const settings = binder.scope;
|
|
2960
3925
|
let selected = autoValue("auto");
|
|
3926
|
+
/** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
|
|
3927
|
+
const addToConversation = createAddToConversation(ctx);
|
|
2961
3928
|
/** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
|
|
2962
3929
|
const compatSummary = () => [
|
|
2963
3930
|
{
|
|
@@ -2978,6 +3945,9 @@ window.__ModuleLoader__.load({
|
|
|
2978
3945
|
];
|
|
2979
3946
|
ctx.provide("fileOpeners", registry);
|
|
2980
3947
|
ctx.effect(() => registry.register(vscodeOpener()), "vscode-mode: file opener");
|
|
3948
|
+
const sidebarPanels = createSidebarPanelRegistry();
|
|
3949
|
+
ctx.provide("edrvSidebarPanels", sidebarPanels);
|
|
3950
|
+
ctx.effect(() => sidebarPanels.register(createFilePanel()), "vscode-mode: sidebar panel");
|
|
2981
3951
|
ctx.effect(() => registry.register({
|
|
2982
3952
|
id: "system",
|
|
2983
3953
|
label: "系统默认应用",
|
|
@@ -3019,7 +3989,18 @@ window.__ModuleLoader__.load({
|
|
|
3019
3989
|
order: 5,
|
|
3020
3990
|
label: "文件编辑",
|
|
3021
3991
|
inject: (sessionId) => ({ sessionId })
|
|
3022
|
-
}, (props) => react.default.createElement(EditorView, Object.assign({}, props, {
|
|
3992
|
+
}, (props) => react.default.createElement(EditorView, Object.assign({}, props, {
|
|
3993
|
+
schedule,
|
|
3994
|
+
addToConversation,
|
|
3995
|
+
sidebarPanels
|
|
3996
|
+
})));
|
|
3997
|
+
registerSlotSafely(ctx, {
|
|
3998
|
+
name: "conversation.input.dock",
|
|
3999
|
+
id: "edrv-diff-dock",
|
|
4000
|
+
order: 30,
|
|
4001
|
+
label: "差异",
|
|
4002
|
+
inject: (sessionId) => ({ sessionId })
|
|
4003
|
+
}, (props) => react.default.createElement(ConversationDiffDock, Object.assign({}, props)));
|
|
3023
4004
|
registerSlotSafely(ctx, {
|
|
3024
4005
|
name: "conversation.session.header.utilities",
|
|
3025
4006
|
id: "edrv-diff-badge",
|