dsh-vscode-mode 0.5.2 → 0.6.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 +78 -5
- package/lib/client.js +5579 -236
- package/lib/client.js.map +1 -1
- package/lib/index.js +2358 -126
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/compat.ts +1 -1
- package/src/client/dap/BpWidget.ts +262 -0
- package/src/client/dap/bpMenu.ts +91 -0
- package/src/client/dap/bpRowMenu.ts +53 -0
- package/src/client/dap/breakpoints.ts +222 -0
- package/src/client/dap/decorate.ts +128 -0
- package/src/client/dap/hintLine.ts +29 -0
- package/src/client/dap/hover.ts +166 -0
- package/src/client/dap/hoverMode.ts +159 -0
- package/src/client/dap/hoverTree.ts +722 -0
- package/src/client/dap/launchInsert.ts +229 -0
- package/src/client/dap/launchSnippetProvider.ts +206 -0
- package/src/client/dap/modelPath.ts +23 -0
- package/src/client/dap/panelSplit.ts +105 -0
- package/src/client/dap/pidPick.ts +24 -0
- package/src/client/dap/store.ts +571 -0
- package/src/client/dap/toolbarDrag.ts +51 -0
- package/src/client/dap/trace.ts +22 -0
- package/src/client/dap/variableTree.ts +62 -0
- package/src/client/editorLimit.ts +31 -0
- package/src/client/index.ts +13 -0
- package/src/client/markdownPreview.ts +24 -0
- package/src/client/md/componentType.ts +28 -0
- package/src/client/md/mdPanel.ts +98 -0
- package/src/client/monaco/lsp/index.ts +15 -0
- package/src/client/monaco/lsp/providers.ts +2 -0
- package/src/client/monaco/theme.ts +15 -0
- package/src/client/searchSeed.ts +64 -0
- package/src/client/sidebar/panels/DebugPanel.ts +613 -0
- package/src/client/sidebar/panels/SearchPanel.ts +70 -15
- package/src/client/sidebar/panels/SvnPanel.ts +167 -23
- package/src/client/sidebar/panels/index.ts +19 -0
- package/src/client/sidebar/types.ts +2 -0
- package/src/client/styles/editor.css +186 -1
- package/src/client/svnActions.ts +18 -1
- package/src/client/svnLog.ts +3 -3
- package/src/client/svnStatus.ts +140 -2
- package/src/client/tabActions.ts +54 -0
- package/src/client/ui/EditorView.ts +711 -29
- package/src/client/ui/McpSettings.ts +29 -0
- package/src/client/ui/SideBySideDiff.tsx +214 -29
- package/src/client/ui/SvnDiffPanel.ts +21 -8
- package/src/client/ui/SvnLogDialog.ts +44 -2
- package/src/client/ui/SvnPatchDialog.ts +63 -0
- package/src/client/ui/SvnSumDialog.ts +77 -0
- package/src/client/ui/commandCatalog.ts +54 -0
- package/src/client/ui/horizontalWheel.ts +85 -0
- package/src/client/ui/svnExport.ts +71 -0
- package/src/client-primitives.d.ts +34 -0
- package/src/dap/configSnippets.ts +269 -0
- package/src/dap/discovery.ts +198 -0
- package/src/dap/launchConfig.ts +127 -0
- package/src/dap/manager.ts +588 -0
- package/src/dap/processList.ts +76 -0
- package/src/dap/protocol.ts +80 -0
- package/src/dap/provider.ts +49 -0
- package/src/dap/rpc.ts +192 -0
- package/src/dap/sourcePath.ts +82 -0
- package/src/fileOpenSettings.ts +3 -0
- package/src/index.ts +7 -3
- package/src/lsp/providers.ts +3 -2
- package/src/reveal.ts +31 -20
- package/src/rpc.ts +11 -3
- package/src/search/ripgrep.ts +127 -11
- package/src/shared/dap.ts +262 -0
- package/src/shared/editorLimit.ts +46 -0
- package/src/shared/keybindings.ts +10 -0
- package/src/shared/rpc.ts +40 -2
- package/src/shared/svn.ts +147 -0
- package/src/shared/svnActions.ts +20 -0
- package/src/svn.ts +320 -11
- package/src/workspace.ts +0 -75
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — DAP 变量树纯函数。
|
|
3
|
+
* 把 DAP variablesReference 缓存展平成可渲染 sibling rows,避免把子行嵌套进父级
|
|
4
|
+
* 横向 flex 行导致变量重叠;同时限制递归深度与总行数,保护侧栏性能。
|
|
5
|
+
* 作者 ddj 2026年09月29号
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** DAP 变量最小视图。 */
|
|
9
|
+
export interface VariableLike {
|
|
10
|
+
name: string
|
|
11
|
+
value: string
|
|
12
|
+
type?: string
|
|
13
|
+
ref: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 展平后的变量行。 */
|
|
17
|
+
export interface VariableRow {
|
|
18
|
+
key: string
|
|
19
|
+
variable: VariableLike
|
|
20
|
+
depth: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 展平选项。 */
|
|
24
|
+
export interface VariableTreeOptions {
|
|
25
|
+
maxRows?: number
|
|
26
|
+
maxDepth?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 将变量树展平为 sibling rows;循环 ref 只展开一次,避免异常适配器数据造成递归。
|
|
31
|
+
* @author ddj 2026年09月29号
|
|
32
|
+
* @param roots 根变量
|
|
33
|
+
* @param expanded 已展开的 variablesReference
|
|
34
|
+
* @param cache ref → 子变量缓存
|
|
35
|
+
* @param options 行数/深度上限
|
|
36
|
+
*/
|
|
37
|
+
export function flattenVariableRows(
|
|
38
|
+
roots: readonly VariableLike[],
|
|
39
|
+
expanded: ReadonlySet<number>,
|
|
40
|
+
cache: ReadonlyMap<number, readonly VariableLike[]>,
|
|
41
|
+
options: VariableTreeOptions = {},
|
|
42
|
+
): VariableRow[] {
|
|
43
|
+
const rows: VariableRow[] = []
|
|
44
|
+
const maxRows = options.maxRows ?? 1000
|
|
45
|
+
const maxDepth = options.maxDepth ?? 32
|
|
46
|
+
const walk = (items: readonly VariableLike[], depth: number, ancestry: ReadonlySet<number>, prefix: string): void => {
|
|
47
|
+
if (depth > maxDepth || rows.length >= maxRows) return
|
|
48
|
+
for (let index = 0; index < items.length && rows.length < maxRows; index += 1) {
|
|
49
|
+
const variable = items[index]
|
|
50
|
+
const key = prefix + '/' + index + ':' + variable.name
|
|
51
|
+
rows.push({ key, variable, depth })
|
|
52
|
+
if (variable.ref <= 0 || !expanded.has(variable.ref) || ancestry.has(variable.ref)) continue
|
|
53
|
+
const children = cache.get(variable.ref)
|
|
54
|
+
if (!children?.length) continue
|
|
55
|
+
const nextAncestry = new Set(ancestry)
|
|
56
|
+
nextAncestry.add(variable.ref)
|
|
57
|
+
walk(children, depth + 1, nextAncestry, key)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
walk(roots, 0, new Set(), 'root')
|
|
61
|
+
return rows
|
|
62
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 页签数量上限共享状态。
|
|
3
|
+
* 设置来源:dsh-vscode-mode 命名空间的 maxOpenEditors 字段(通用设置页可调);
|
|
4
|
+
* 客户端 settings 订阅经 editorLimitApply 写入,编辑器经 getMaxOpenEditors 读取,
|
|
5
|
+
* 变更时由 client/index.ts 派发 edrv:max-open-editors 窗口事件通知编辑器复算淘汰。
|
|
6
|
+
* 形态与 sidebarMin.ts 一致(同一设置同步链路,便于统一维护)。
|
|
7
|
+
* 作者 ddj 2026年09月18号
|
|
8
|
+
*/
|
|
9
|
+
import { EDITOR_LIMIT_DEFAULT, normalizeMaxOpenEditors } from '../shared/editorLimit.js'
|
|
10
|
+
|
|
11
|
+
let currentLimit = EDITOR_LIMIT_DEFAULT
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 应用设置值(client/index.ts 设置订阅同步调用)。
|
|
15
|
+
* @author ddj 2026年09月18号
|
|
16
|
+
* @param value 设置文档中的 maxOpenEditors
|
|
17
|
+
* @returns 归一化后的生效值(0 = 不限制)
|
|
18
|
+
*/
|
|
19
|
+
export function editorLimitApply(value: unknown): number {
|
|
20
|
+
currentLimit = normalizeMaxOpenEditors(value)
|
|
21
|
+
return currentLimit
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 读取当前生效的页签上限(编辑器初始/事件回调共用;0 = 不限制)。
|
|
26
|
+
* @author ddj 2026年09月18号
|
|
27
|
+
* @returns 当前上限
|
|
28
|
+
*/
|
|
29
|
+
export function getMaxOpenEditors(): number {
|
|
30
|
+
return currentLimit
|
|
31
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { createFilePanel } from './sidebar/panels/index.js'
|
|
|
41
41
|
import { createSearchPanel } from './sidebar/panels/index.js'
|
|
42
42
|
import { createRulesPanel } from './sidebar/panels/index.js'
|
|
43
43
|
import { createSvnPanel } from './sidebar/panels/index.js'
|
|
44
|
+
import { createDebugPanel } from './sidebar/panels/index.js'
|
|
44
45
|
import { createTreeMenuRegistry } from './sidebar/contextMenu.js'
|
|
45
46
|
import { createDefaultFileMenuItems } from './sidebar/menuItems.js'
|
|
46
47
|
import { createOutlinePanel } from './outline/index.js'
|
|
@@ -50,11 +51,14 @@ import { keybindingsApply } from './keybindings.js'
|
|
|
50
51
|
import { createCommandBridge } from './commandBridge.js'
|
|
51
52
|
import { REGISTRY_GLOBAL } from './commandGlobals.js'
|
|
52
53
|
import { sidebarMinApply } from './sidebarMin.js'
|
|
54
|
+
import { editorLimitApply } from './editorLimit.js'
|
|
53
55
|
import { log } from './log.js'
|
|
54
56
|
import { setupLsp, setSession, disposeLsp } from './monaco/lsp/index.js'
|
|
55
57
|
import { disposeSnippets } from './snippets/provider.js'
|
|
58
|
+
import { disposeLaunchJson } from './dap/launchSnippetProvider.js'
|
|
56
59
|
import { disposeAiInline } from './ai/inlineProvider.js'
|
|
57
60
|
import { readSessionScope, subscribeScope } from './sessionScope.js'
|
|
61
|
+
import { dapStore } from './dap/store.js'
|
|
58
62
|
import type { CompatAdapter } from '../shared/compat.js'
|
|
59
63
|
|
|
60
64
|
// ⚠️ inject 只列必需服务:webUiSettings 是 @linxin666/dsh-client-ui-web-ui-settings 提供的
|
|
@@ -193,6 +197,11 @@ export function apply(ctx: any): void {
|
|
|
193
197
|
ctx.effect(() => sidebarPanels.register(createRulesPanel()), 'vscode-mode: sidebar panel rules')
|
|
194
198
|
// SVN 变更面板:活动栏「SVN 变更」页签(工作副本状态列表;visible 守卫使非 SVN 工作区不出现)
|
|
195
199
|
ctx.effect(() => sidebarPanels.register(createSvnPanel()), 'vscode-mode: sidebar panel svn')
|
|
200
|
+
// 调试面板:活动栏「调试」页签(VS Code 调试视图四段布局 + REPL;DAP 桥接)
|
|
201
|
+
ctx.effect(() => sidebarPanels.register(createDebugPanel()), 'vscode-mode: sidebar panel debug')
|
|
202
|
+
// 客户端(重)装配对账:host 的 DapSession 常驻,而本模块状态随刷新/HMR 归零,
|
|
203
|
+
// 不对账则调试面板与暂停态 hover 静默失效(见 dapStore.resync)。
|
|
204
|
+
void dapStore.resync()
|
|
196
205
|
// 文件右键菜单项注册表(对外 provide,供本插件/第三方注册;内置「在文件浏览器中打开」)
|
|
197
206
|
const fileMenuItems = createTreeMenuRegistry()
|
|
198
207
|
ctx.provide('edrvFileContextMenuItems', fileMenuItems)
|
|
@@ -246,6 +255,7 @@ export function apply(ctx: any): void {
|
|
|
246
255
|
}, 'vscode-mode: file opener setting sync')
|
|
247
256
|
// 快捷键配置同步:设置提交后立即刷新键位匹配(编辑器/QuickOpen 按事件时读取)
|
|
248
257
|
// 同一订阅里顺带同步侧边栏最小宽度(sidebarMinWidth)→ 派发 edrv:sidebar-min-width 通知编辑器重夹
|
|
258
|
+
// 以及页签上限(maxOpenEditors)→ 派发 edrv:max-open-editors 通知编辑器复算淘汰
|
|
249
259
|
ctx.effect(() => {
|
|
250
260
|
if (!settings) return undefined
|
|
251
261
|
const sync = (): void => {
|
|
@@ -254,6 +264,8 @@ export function apply(ctx: any): void {
|
|
|
254
264
|
keybindingsApply(snapshot.value?.keybindings)
|
|
255
265
|
const minW = sidebarMinApply(snapshot.value?.sidebarMinWidth)
|
|
256
266
|
window.dispatchEvent(new CustomEvent('edrv:sidebar-min-width', { detail: { value: minW } }))
|
|
267
|
+
const limit = editorLimitApply(snapshot.value?.maxOpenEditors)
|
|
268
|
+
window.dispatchEvent(new CustomEvent('edrv:max-open-editors', { detail: { value: limit } }))
|
|
257
269
|
}
|
|
258
270
|
sync()
|
|
259
271
|
return settings.subscribe(sync)
|
|
@@ -444,6 +456,7 @@ export function apply(ctx: any): void {
|
|
|
444
456
|
// 不做会导致重载后重复注册(补全/跳转/hover 各翻倍)与陈旧开关残留。
|
|
445
457
|
ctx.effect(() => () => {
|
|
446
458
|
try { disposeSnippets() } catch { /* 卸载异常不得阻断其余清理 */ }
|
|
459
|
+
try { disposeLaunchJson() } catch { /* 同上 */ }
|
|
447
460
|
try { disposeAiInline() } catch { /* 同上 */ }
|
|
448
461
|
try { disposeLsp() } catch { /* 同上 */ }
|
|
449
462
|
}, 'vscode-mode: monaco providers teardown')
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — Markdown 文件判定(编辑区预览用)。
|
|
3
|
+
* 纯函数、无 DOM/React 依赖,便于单测。
|
|
4
|
+
* @author ddj 2026年09月18号
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** 可进入预览的 Markdown 扩展名(小写、无点)。 */
|
|
8
|
+
const MARKDOWN_EXT_SET: Set<string> = new Set(['md', 'markdown'])
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 判断路径是否为可预览的 Markdown 文件(取 basename 扩展名,大小写不敏感)。
|
|
12
|
+
*
|
|
13
|
+
* 为什么不含 `mdx`:官方 MarkdownText 只走 GFM+KaTeX 语法,不解析 JSX;
|
|
14
|
+
* 对 .mdx 做「预览」会把 JSX 当字面文本渲染出来,比文本编辑更容易误导,故排除。
|
|
15
|
+
* @author ddj 2026年09月18号
|
|
16
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
17
|
+
* @returns 是否 Markdown 文件
|
|
18
|
+
*/
|
|
19
|
+
export function isMarkdownPath(path: string): boolean {
|
|
20
|
+
const base = String(path || '').split(/[\\/]/).pop() || ''
|
|
21
|
+
const dot = base.lastIndexOf('.')
|
|
22
|
+
if (dot <= 0) return false
|
|
23
|
+
return MARKDOWN_EXT_SET.has(base.slice(dot + 1).toLowerCase())
|
|
24
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — React 元素类型判定(纯函数,无 React/primitives 依赖)。
|
|
3
|
+
*
|
|
4
|
+
* 用途:编辑区 Markdown 预览要判断官方 `MarkdownText` 原语是否可用(旧版 DSH 可能缺该导出)。
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ 为什么不能只判 `typeof value === 'function'`:
|
|
7
|
+
* 官方 `MarkdownText` 是 `React.memo(...)` 的产物(`MemoExoticComponent`),
|
|
8
|
+
* 其 `typeof` 是 **'object'** 而非 'function'。函数判据恒为 false,会让「兼容降级」分支在
|
|
9
|
+
* **所有新版 DSH 上永久生效** —— 表现是预览只显示纯文本降级提示。
|
|
10
|
+
* 该缺陷由 GUI 端到端验证捕获:纯函数单测不覆盖该守卫,手写垫片又把它声明得很宽松,
|
|
11
|
+
* 故 tsc 与既有测试都无法发现。抽出本模块正是为了让它有单测守约。
|
|
12
|
+
*
|
|
13
|
+
* 判定口径与 React 自身一致:函数(函数组件 / 类组件),或带 `$$typeof` 标记的非 null
|
|
14
|
+
* 对象(memo / forwardRef / lazy 等「外部对象类型」)。
|
|
15
|
+
* 作者 ddj 2026年09月18号
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 判断值是否可作为 React 元素类型传给 `React.createElement`。
|
|
20
|
+
* @author ddj 2026年09月18号
|
|
21
|
+
* @param value 候选组件(undefined/null 表示该导出不存在)
|
|
22
|
+
* @returns 是否可作为元素类型使用
|
|
23
|
+
*/
|
|
24
|
+
export function isComponentType(value: unknown): boolean {
|
|
25
|
+
if (typeof value === 'function') return true
|
|
26
|
+
if (typeof value !== 'object' || value === null) return false
|
|
27
|
+
return (value as { $$typeof?: unknown }).$$typeof !== undefined
|
|
28
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 编辑区 Markdown 预览面板。
|
|
3
|
+
*
|
|
4
|
+
* 需求 4:.md 文件支持预览。渲染复用官方 UI 原语 MarkdownText(GFM + KaTeX,走 --dsw-*
|
|
5
|
+
* 令牌自动跟随 DSH 主题与皮肤),不引入任何第三方 Markdown 依赖。
|
|
6
|
+
*
|
|
7
|
+
* 与既有图片 / PDF 面板的形态保持一致:工具条(meta + 切换按钮 + 刷新)+ 内容区。
|
|
8
|
+
* 本面板为**只读**视图(编辑仍在 Monaco 文本态完成),故不接收编辑回调、不参与脏标记。
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ 兼容降级:旧版 DSH 的 primitives 可能没有 MarkdownText。此时渲染只读 <pre> 并给出
|
|
11
|
+
* 提示,绝不抛错白屏(与 pdf.js / Monaco 加载失败的降级口径一致)。
|
|
12
|
+
* 作者 ddj 2026年09月18号
|
|
13
|
+
*/
|
|
14
|
+
import React from 'react'
|
|
15
|
+
import type { ComponentType, ReactNode } from 'react'
|
|
16
|
+
import { MarkdownDelegateProvider, MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
17
|
+
import type { MarkdownLabels } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
18
|
+
import { isComponentType } from './componentType.js'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 渲染 chrome 常量。必须是模块级稳定引用:MarkdownText 的 memo 以 labels 身份
|
|
22
|
+
* 参与缓存判定,每次渲染新建对象会让渲染缓存在流式场景反复失效。
|
|
23
|
+
*/
|
|
24
|
+
const MD_LABELS: MarkdownLabels = {
|
|
25
|
+
code: { copyLabel: '复制', copiedLabel: '已复制' },
|
|
26
|
+
footnotes: '脚注',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 官方原语声明在 client-primitives.d.ts 的手写垫片里,组件返回值只能声明为 unknown
|
|
31
|
+
* (该虚拟模块不入本地 node_modules,无法在环境声明中引用 react 类型)。
|
|
32
|
+
* 故在此把垫片面收敛成 react 可接受的组件类型,仅本文件承担这层转换;
|
|
33
|
+
* 原语在运行时缺失时由下方 hasMarkdownPrimitive 守卫降级。
|
|
34
|
+
*/
|
|
35
|
+
type MdTextProps = { text: string; streaming?: boolean; labels: MarkdownLabels; variant?: 'body' | 'compact' }
|
|
36
|
+
type MdProviderProps = { children?: ReactNode; openFile?: (path: string, options?: { line?: number }) => void }
|
|
37
|
+
|
|
38
|
+
const MdText = MarkdownText as unknown as ComponentType<MdTextProps>
|
|
39
|
+
const MdProvider = MarkdownDelegateProvider as unknown as ComponentType<MdProviderProps>
|
|
40
|
+
|
|
41
|
+
/** 官方 Markdown 原语是否可用(旧版 DSH 缺该导出时为 false)。 */
|
|
42
|
+
const hasMarkdownPrimitive = isComponentType(MarkdownText)
|
|
43
|
+
|
|
44
|
+
/** 文本行数(meta 展示用;空文本计 0 行)。 */
|
|
45
|
+
function lineCountOf(text: string): number {
|
|
46
|
+
const value = String(text ?? '')
|
|
47
|
+
return value ? value.split(/\r?\n/).length : 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 预览面板 props(面板由 EditorView 装配,故用宽松形状避免跨文件类型耦合)。 */
|
|
51
|
+
interface MarkdownPanelProps {
|
|
52
|
+
/** Markdown 原文(取自编辑器已加载内容,无需额外 RPC)。 */
|
|
53
|
+
text?: string
|
|
54
|
+
/** 切回源码编辑(工具条按钮)。 */
|
|
55
|
+
onToggleSource?: () => void
|
|
56
|
+
/** 重新读取文件(工具条刷新按钮)。 */
|
|
57
|
+
onReload?: () => void
|
|
58
|
+
/** 打开 Markdown 内的本地文件链接(可空:不给则链接保持纯文本)。 */
|
|
59
|
+
onOpenFile?: (path: string, options?: { line?: number }) => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Markdown 预览面板。
|
|
64
|
+
* @author ddj 2026年09月18号
|
|
65
|
+
* @param props 面板输入(原文与三个动作回调)
|
|
66
|
+
* @returns 预览面板元素
|
|
67
|
+
*/
|
|
68
|
+
export function MarkdownPanel(props: MarkdownPanelProps) {
|
|
69
|
+
const text = String(props?.text ?? '')
|
|
70
|
+
const onToggleSource = props?.onToggleSource
|
|
71
|
+
const onReload = props?.onReload
|
|
72
|
+
const onOpenFile = props?.onOpenFile
|
|
73
|
+
const chars = text.length
|
|
74
|
+
const lines = lineCountOf(text)
|
|
75
|
+
|
|
76
|
+
const body = hasMarkdownPrimitive
|
|
77
|
+
? React.createElement(MdProvider, {
|
|
78
|
+
openFile: typeof onOpenFile === 'function' ? onOpenFile : undefined,
|
|
79
|
+
}, React.createElement(MdText, { text, streaming: false, labels: MD_LABELS, variant: 'body' }))
|
|
80
|
+
: React.createElement('div', { className: 'edrv-mdview-fallback' },
|
|
81
|
+
React.createElement('div', null, '当前 DSH 版本不含 Markdown 渲染原语,已降级为纯文本预览。'),
|
|
82
|
+
React.createElement('pre', { className: 'edrv-mdview-pre' }, text))
|
|
83
|
+
|
|
84
|
+
return React.createElement('div', { className: 'edrv-mdview' },
|
|
85
|
+
React.createElement('div', { className: 'edrv-mdview-bar' },
|
|
86
|
+
React.createElement('span', { className: 'edrv-mdview-meta' }, lines + ' 行 · ' + chars + ' 字符'),
|
|
87
|
+
React.createElement('span', { style: { flex: 1 } }),
|
|
88
|
+
React.createElement('button', {
|
|
89
|
+
className: 'edrv-pill edrv-pill-ghost', title: '切回源码编辑',
|
|
90
|
+
onClick: () => onToggleSource?.(),
|
|
91
|
+
}, '以源码打开'),
|
|
92
|
+
React.createElement('button', {
|
|
93
|
+
className: 'edrv-pill edrv-pill-ghost', title: '重新读取文件',
|
|
94
|
+
onClick: () => onReload?.(),
|
|
95
|
+
}, '⟳ 刷新')),
|
|
96
|
+
React.createElement('div', { className: 'edrv-mdview-stage' },
|
|
97
|
+
React.createElement('div', { className: 'edrv-mdview-doc' }, body)))
|
|
98
|
+
}
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* 作者 ddj 2026-08-27
|
|
7
7
|
*/
|
|
8
8
|
import { registerLspProviders, disposeLspProviders, hideReferencesOverlay } from './providers.js'
|
|
9
|
+
import { registerDapHover, disposeDapHover } from '../../dap/hover.js'
|
|
10
|
+
import { installHoverTree, disposeHoverTree } from '../../dap/hoverTree.js'
|
|
11
|
+
import { installHoverMode, disposeHoverMode } from '../../dap/hoverMode.js'
|
|
9
12
|
import { setLspSession, refreshStatus, lspStatusFor, onLspProgress, bindLspSession } from './lspClient.js'
|
|
10
13
|
|
|
11
14
|
let monacoRef = null
|
|
@@ -19,7 +22,16 @@ const LSP_SESSION_GLOBAL = '__edrvLspSessionUnbind__'
|
|
|
19
22
|
/** Monaco 加载后装配(幂等;重复调用仅刷新会话)。 */
|
|
20
23
|
export function setupLsp(monaco) {
|
|
21
24
|
monacoRef = monaco
|
|
25
|
+
// 注册顺序决定同分 provider 的 ordinal:LanguageFeatureRegistry._compareByScoreAndTime 对
|
|
26
|
+
// 同分选择器按 _time 倒序(后注册者排前),hover 部件再按 ordinal 升序渲染 —— 因此 DAP
|
|
27
|
+
// 必须**最后**注册,暂停态的调试值行才会稳定渲染在 LSP 文档行之前;反序会让调试值被
|
|
28
|
+
// LSP 长文档挤到浮窗下方(超出 maxHeight 需滚动,等同看不到)。
|
|
22
29
|
registerLspProviders(monaco)
|
|
30
|
+
registerDapHover(monaco)
|
|
31
|
+
// hover 变量树的 DOM 绑定(观察 hover 面板插入/重渲染;幂等)
|
|
32
|
+
installHoverTree()
|
|
33
|
+
// Alt 跟踪:暂停态默认调试值浮窗、按住 Alt 切 LSP 信息(对齐 CodeBuddy)
|
|
34
|
+
installHoverMode()
|
|
23
35
|
// 会话广播订阅只装一次:跨重载时上一代已订阅(取消函数落 window,模块级状态会复位),
|
|
24
36
|
// 只判 sessionBound 会重复订阅 → 同一 LSP 会话事件被处理多次。
|
|
25
37
|
const host = /* @__PURE__ */ (typeof window === 'undefined' ? undefined : window)
|
|
@@ -52,6 +64,9 @@ export function disposeLspOverlay() {
|
|
|
52
64
|
*/
|
|
53
65
|
export function disposeLsp() {
|
|
54
66
|
disposeLspProviders()
|
|
67
|
+
disposeDapHover()
|
|
68
|
+
disposeHoverTree()
|
|
69
|
+
disposeHoverMode()
|
|
55
70
|
const unbind = sessionUnbind
|
|
56
71
|
if (typeof unbind === 'function') {
|
|
57
72
|
try { unbind() } catch { /* 已解绑 */ }
|
|
@@ -136,6 +136,8 @@ export function registerLspProviders(monaco) {
|
|
|
136
136
|
}),
|
|
137
137
|
monaco.languages.registerHoverProvider(LSP_LANGS, {
|
|
138
138
|
provideHover: async (model, position, token) => {
|
|
139
|
+
// 暂停态的 LSP 行由调试 hover 层默认隐藏、按住 Alt 时显示(见 dap/hoverTree.ts),
|
|
140
|
+
// 因此这里照常返回内容,不再按位置让位。
|
|
139
141
|
const path = pathOfModel(model)
|
|
140
142
|
if (!path) return null
|
|
141
143
|
const hover = await fetchHover(path, model.getValue(), position)
|
|
@@ -137,6 +137,14 @@ const DARK_COLORS = {
|
|
|
137
137
|
'scrollbarSlider.background': '#79797966',
|
|
138
138
|
'scrollbarSlider.hoverBackground': '#646464b3',
|
|
139
139
|
'scrollbarSlider.activeBackground': '#bfbfbf66',
|
|
140
|
+
// 差异视图色块(本仓库 vendored Monaco 无 .line-insert 静态背景规则,靠主题键保证;
|
|
141
|
+
// 色相取自研差异 UI 同源:绿 #0f9d58 / 红 #d9534f,alpha 提到肉眼可辨)
|
|
142
|
+
'diffEditor.insertedLineBackground': '#0f9d5830',
|
|
143
|
+
'diffEditor.removedLineBackground': '#d9534f2e',
|
|
144
|
+
'diffEditor.insertedTextBackground': '#0f9d5833',
|
|
145
|
+
'diffEditor.removedTextBackground': '#d9534f40',
|
|
146
|
+
'diffEditorGutter.insertedLineBackground': '#0f9d5859',
|
|
147
|
+
'diffEditorGutter.removedLineBackground': '#d9534f59',
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
const LIGHT_COLORS = {
|
|
@@ -157,6 +165,13 @@ const LIGHT_COLORS = {
|
|
|
157
165
|
'scrollbarSlider.background': '#64646466',
|
|
158
166
|
'scrollbarSlider.hoverBackground': '#646464b3',
|
|
159
167
|
'scrollbarSlider.activeBackground': '#00000099',
|
|
168
|
+
// 差异视图色块(同上:主题键保证整行背景可见;色相/透明度与暗色一致)
|
|
169
|
+
'diffEditor.insertedLineBackground': '#0f9d5830',
|
|
170
|
+
'diffEditor.removedLineBackground': '#d9534f2e',
|
|
171
|
+
'diffEditor.insertedTextBackground': '#0f9d5833',
|
|
172
|
+
'diffEditor.removedTextBackground': '#d9534f40',
|
|
173
|
+
'diffEditorGutter.insertedLineBackground': '#0f9d5859',
|
|
174
|
+
'diffEditorGutter.removedLineBackground': '#d9534f59',
|
|
160
175
|
}
|
|
161
176
|
|
|
162
177
|
/**
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 工作区搜索的「选区种子」。
|
|
3
|
+
*
|
|
4
|
+
* 需求:Ctrl+Shift+F 时若编辑器有选中,把选中文本自动填入搜索框。
|
|
5
|
+
* 链路:EditorView 从 Monaco 取选区文本 → seedQueryOf 归一 → setSearchSeed 存入一次性槽
|
|
6
|
+
* → 派发 edrv:search-focus → SearchPanel 消费(挂载时或已挂载时),填入并立即搜索。
|
|
7
|
+
*
|
|
8
|
+
* 为什么用一次性槽而不是事件 detail:
|
|
9
|
+
* 侧边栏原本收起时搜索面板**尚未挂载**,派发瞬间没有监听者;事件 detail 会丢。
|
|
10
|
+
* 槽在 EditorView 与 SearchPanel 之间充当交接点,挂载后由消费方自行取走。
|
|
11
|
+
*
|
|
12
|
+
* 为什么取首行:搜索框是单行 input,ripgrep 按字面连续匹配。多行选区(整段/整函数)
|
|
13
|
+
* 作为单条 query 永远无匹配,故按项目既有口径取首行(与 VS Code 单行选区种子行为一致)。
|
|
14
|
+
*
|
|
15
|
+
* 纯逻辑 + 模块级槽,不依赖 React/DOM,可 node 单测。
|
|
16
|
+
* 作者 ddj 2026年09月18号
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** 种子最大长度(超长选区只取前 N 字符,防一次性把巨型 query 送进搜索)。 */
|
|
20
|
+
export const SEED_MAX = 200
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 由选区文本推导搜索种子。
|
|
24
|
+
* @author ddj 2026年09月18号
|
|
25
|
+
* @param text 选区原文(可能含多行、前后空白;null/undefined 视为空)
|
|
26
|
+
* @returns 归一后的搜索词;无效输入返回空串(调用方据此保持原搜索词)
|
|
27
|
+
*/
|
|
28
|
+
export function seedQueryOf(text: unknown): string {
|
|
29
|
+
const raw = String(text ?? '')
|
|
30
|
+
if (!raw) return ''
|
|
31
|
+
const firstLine = raw.split(/\r?\n/)[0] ?? ''
|
|
32
|
+
const trimmed = firstLine.trim()
|
|
33
|
+
if (!trimmed) return ''
|
|
34
|
+
return trimmed.length > SEED_MAX ? trimmed.slice(0, SEED_MAX) : trimmed
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 一次性种子槽(字符串或 null;null 表示无待消费种子)。 */
|
|
38
|
+
let pendingSeed: string | null = null
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 写入待消费种子(空词等价于清除)。
|
|
42
|
+
* @author ddj 2026年09月18号
|
|
43
|
+
* @param text 选区文本(经 seedQueryOf 归一)
|
|
44
|
+
*/
|
|
45
|
+
export function setSearchSeed(text: unknown): void {
|
|
46
|
+
const seed = seedQueryOf(text)
|
|
47
|
+
pendingSeed = seed ? seed : null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 取走待消费种子(取后即清空,保证挂载路径与焦点路径不会重复应用同一个种子)。
|
|
52
|
+
* @author ddj 2026年09月18号
|
|
53
|
+
* @returns 种子搜索词;无待消费种子返回空串
|
|
54
|
+
*/
|
|
55
|
+
export function takeSearchSeed(): string {
|
|
56
|
+
const seed = pendingSeed
|
|
57
|
+
pendingSeed = null
|
|
58
|
+
return seed ?? ''
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 清空待消费种子(测试隔离用)。 */
|
|
62
|
+
export function clearSearchSeed(): void {
|
|
63
|
+
pendingSeed = null
|
|
64
|
+
}
|