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