dsh-vscode-mode 0.5.3 → 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 +55 -2
- package/lib/client.js +5173 -355
- package/lib/client.js.map +1 -1
- package/lib/index.js +2356 -126
- package/lib/index.js.map +1 -1
- package/package.json +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/index.ts +9 -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/sidebar/panels/DebugPanel.ts +613 -0
- 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 +176 -0
- package/src/client/svnActions.ts +18 -1
- package/src/client/svnLog.ts +3 -3
- package/src/client/svnStatus.ts +140 -2
- package/src/client/ui/EditorView.ts +544 -16
- 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 +49 -0
- package/src/client/ui/svnExport.ts +71 -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/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/keybindings.ts +7 -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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — Monaco 调试装饰:断点圆点(gutter)与停帧行高亮。
|
|
3
|
+
* 装饰 id 挂在 editor 实例属性上(与 diffRender/underline/navFlash 同款隔离模式),
|
|
4
|
+
* 独立 collection 互不干扰;range 用 IRange 形状普通对象(Monaco 内部归一化,
|
|
5
|
+
* 与 navFlashRangeOf 产物同口径);编辑器销毁时装饰随实例消失,无需显式清理。
|
|
6
|
+
* 作者 ddj 2026年09月29号
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** editor 上的装饰 id 槽位 key。 */
|
|
10
|
+
const BP_KEY = '__edrvDapBpDecorations'
|
|
11
|
+
const PAUSED_KEY = '__edrvDapPausedDecorations'
|
|
12
|
+
const EDITING_KEY = '__edrvDapEditingDecorations'
|
|
13
|
+
const HINT_KEY = '__edrvDapHintDecorations'
|
|
14
|
+
/** 当前 hover 预览行(避免同一行重复设置装饰)。 */
|
|
15
|
+
const HINT_LINE = '__edrvDapHintLine'
|
|
16
|
+
|
|
17
|
+
/** Monaco editor 最小子集(deltaDecorations 旧 API,工程内既有口径)。 */
|
|
18
|
+
interface DecoEditor {
|
|
19
|
+
deltaDecorations(oldIds: string[], next: unknown[]): string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** IRange 形状普通对象。 */
|
|
23
|
+
function lineRange(line: number): unknown {
|
|
24
|
+
return { startLineNumber: line, startColumn: 1, endLineNumber: line, endColumn: 1 }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 应用断点装饰(全量替换:启用行红点 + 未验证行空心红圈 + 禁用行灰点)。
|
|
29
|
+
* 未验证 = 适配器真实回执 verified:false(如该行无可执行代码),对齐 VS Code 空心圆语义。
|
|
30
|
+
* @author ddj 2026年09月29号 / 2026年09月21号
|
|
31
|
+
* @param editor Monaco 编辑器
|
|
32
|
+
* @param enabledLines 启用断点行
|
|
33
|
+
* @param disabledLines 禁用断点行
|
|
34
|
+
* @param unverifiedLines 启用但未验证的行(缺省视为全部已验证)
|
|
35
|
+
*/
|
|
36
|
+
export function applyBpDecorations(editor: unknown, enabledLines: number[], disabledLines: number[], unverifiedLines: readonly number[] = []): void {
|
|
37
|
+
const ed = editor as DecoEditor | null
|
|
38
|
+
if (!ed?.deltaDecorations) return
|
|
39
|
+
const make = (line: number, cls: string) => ({
|
|
40
|
+
range: lineRange(line),
|
|
41
|
+
options: { isWholeLine: true, glyphMarginClassName: cls, glyphMarginHoverMessage: { value: '断点(点击行首圆点处切换)' } },
|
|
42
|
+
})
|
|
43
|
+
const next = [
|
|
44
|
+
...enabledLines.map((line) => make(line, unverifiedLines.includes(line) ? 'edrv-dap-bp-unverified' : 'edrv-dap-bp')),
|
|
45
|
+
...disabledLines.map((line) => make(line, 'edrv-dap-bp-off')),
|
|
46
|
+
]
|
|
47
|
+
const slot = ed as unknown as Record<string, unknown>
|
|
48
|
+
const prev = slot[BP_KEY] as string[] | undefined
|
|
49
|
+
slot[BP_KEY] = ed.deltaDecorations(prev ?? [], next)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 设置停帧行装饰(null/0 清除;整行浅底 + gutter 箭头)。
|
|
54
|
+
* @author ddj 2026年09月29号
|
|
55
|
+
* @param editor Monaco 编辑器
|
|
56
|
+
* @param line 停帧行
|
|
57
|
+
*/
|
|
58
|
+
export function setPausedLine(editor: unknown, line: number | null): void {
|
|
59
|
+
const ed = editor as DecoEditor | null
|
|
60
|
+
if (!ed?.deltaDecorations) return
|
|
61
|
+
const slot = ed as unknown as Record<string, unknown>
|
|
62
|
+
const prev = slot[PAUSED_KEY] as string[] | undefined
|
|
63
|
+
if (!line) {
|
|
64
|
+
if (prev?.length) ed.deltaDecorations(prev, [])
|
|
65
|
+
slot[PAUSED_KEY] = []
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
slot[PAUSED_KEY] = ed.deltaDecorations(prev ?? [], [{
|
|
69
|
+
range: lineRange(line),
|
|
70
|
+
options: {
|
|
71
|
+
isWholeLine: true,
|
|
72
|
+
className: 'edrv-dap-paused-line',
|
|
73
|
+
glyphMarginClassName: 'edrv-dap-paused-glyph',
|
|
74
|
+
},
|
|
75
|
+
}])
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 设置「断点编辑中」目标行高亮(打开行内浮层期间;null 清除)。
|
|
80
|
+
* 对齐 VS Code:编辑断点时目标行整行高亮,明确正在编辑哪一行。
|
|
81
|
+
* @author ddj 2026年09月29号
|
|
82
|
+
* @param editor Monaco 编辑器
|
|
83
|
+
* @param line 目标行(null 清除)
|
|
84
|
+
*/
|
|
85
|
+
export function setEditingLine(editor: unknown, line: number | null): void {
|
|
86
|
+
const ed = editor as DecoEditor | null
|
|
87
|
+
if (!ed?.deltaDecorations) return
|
|
88
|
+
const slot = ed as unknown as Record<string, unknown>
|
|
89
|
+
const prev = slot[EDITING_KEY] as string[] | undefined
|
|
90
|
+
if (!line) {
|
|
91
|
+
if (prev?.length) ed.deltaDecorations(prev, [])
|
|
92
|
+
slot[EDITING_KEY] = []
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
slot[EDITING_KEY] = ed.deltaDecorations(prev ?? [], [{
|
|
96
|
+
range: lineRange(line),
|
|
97
|
+
options: { isWholeLine: true, className: 'edrv-dap-editing-line' },
|
|
98
|
+
}])
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 设置 glyph 区 hover 预览断点(半透明小红点;null 清除)。
|
|
103
|
+
* 对齐 VS Code:鼠标悬停在可下断点的行首区域时,显示低透明度断点预览 + pointer 光标
|
|
104
|
+
* (其 `.codicon-debug-hint { cursor: pointer; opacity: .4 }` 同源语义)。
|
|
105
|
+
* 同一行重复调用直接返回(onMouseMove 高频,避免无谓装饰抖动)。
|
|
106
|
+
* @author ddj 2026年09月29号
|
|
107
|
+
* @param editor Monaco 编辑器
|
|
108
|
+
* @param line 预览行(null/0 清除)
|
|
109
|
+
*/
|
|
110
|
+
export function setHintLine(editor: unknown, line: number | null): void {
|
|
111
|
+
const ed = editor as DecoEditor | null
|
|
112
|
+
if (!ed?.deltaDecorations) return
|
|
113
|
+
const slot = ed as unknown as Record<string, unknown>
|
|
114
|
+
const current = (slot[HINT_LINE] as number | undefined) ?? 0
|
|
115
|
+
const next = line ?? 0
|
|
116
|
+
if (current === next) return
|
|
117
|
+
slot[HINT_LINE] = next
|
|
118
|
+
const prev = slot[HINT_KEY] as string[] | undefined
|
|
119
|
+
if (!next) {
|
|
120
|
+
if (prev?.length) ed.deltaDecorations(prev, [])
|
|
121
|
+
slot[HINT_KEY] = []
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
slot[HINT_KEY] = ed.deltaDecorations(prev ?? [], [{
|
|
125
|
+
range: lineRange(next),
|
|
126
|
+
options: { isWholeLine: true, glyphMarginClassName: 'edrv-dap-bp-hint' },
|
|
127
|
+
}])
|
|
128
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 断点 hover 预览判定(纯函数,可单测)。
|
|
3
|
+
* 对齐 VS Code `breakpointEditorContribution` 的 onMouseMove 分支:
|
|
4
|
+
* `(target.type === GUTTER_GLYPH_MARGIN || target.type === GUTTER_LINE_NUMBERS) &&
|
|
5
|
+
* canSetBreakpointsIn(model) && marginFreeFromNonDebugDecorations(line)` → 显示 hint。
|
|
6
|
+
* 本插件口径:glyph 区(type=2)与行号区(type=3)且该行**尚无断点**时给出预览。
|
|
7
|
+
* 作者 ddj 2026年09月29号
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Monaco MouseTargetType 相关值(实测 0.42-dev:GUTTER_GLYPH_MARGIN=2 / GUTTER_LINE_NUMBERS=3)。 */
|
|
11
|
+
export const TARGET_GUTTER_GLYPH = 2
|
|
12
|
+
export const TARGET_GUTTER_LINE = 3
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 判定 hover 是否应显示断点预览,返回预览行号(0 = 不显示)。
|
|
16
|
+
* @author ddj 2026年09月29号
|
|
17
|
+
* @param targetType Monaco 鼠标目标类型
|
|
18
|
+
* @param line 目标行号(0/undefined = 无)
|
|
19
|
+
* @param existingLines 当前文件已有断点的行号集合
|
|
20
|
+
* @returns 预览行号;0 表示不显示
|
|
21
|
+
*/
|
|
22
|
+
export function hintLineFor(targetType: number | undefined, line: number | undefined, existingLines: readonly number[]): number {
|
|
23
|
+
if (targetType !== TARGET_GUTTER_GLYPH && targetType !== TARGET_GUTTER_LINE) return 0
|
|
24
|
+
const target = Math.floor(Number(line) || 0)
|
|
25
|
+
if (target < 1) return 0
|
|
26
|
+
// 已有断点的行不显示预览(避免与实心红点重叠)
|
|
27
|
+
if (existingLines.includes(target)) return 0
|
|
28
|
+
return target
|
|
29
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 暂停态 Lua DAP hover。
|
|
3
|
+
* provider 只在 DAP paused 时工作,按当前 selectedFrameId 求值,并把「表达式值 + 类型 + 可折叠
|
|
4
|
+
* 变量树」渲染成**单个 HTML 块**(`[data-edrv-dap]`);Alt 切换 LSP 信息由 hover 层做行级显隐
|
|
5
|
+
* (见 hoverTree.ts),因此这里不按 Alt 改变内容,避免依赖 Monaco 的 hover 重算路径。
|
|
6
|
+
* 作者 ddj 2026年09月29号 / 2026年09月21号
|
|
7
|
+
*/
|
|
8
|
+
import { dapStore } from './store.js'
|
|
9
|
+
import { pathOfModel } from '../monaco/lsp/lspClient.js'
|
|
10
|
+
import { BLOCK_ATTR, EQ_ATTR, EXPR_ATTR, HEAD_ATTR, TYPE_ATTR, VAL_ATTR, createTreeState, escapeHtml, initialHtml } from './hoverTree.js'
|
|
11
|
+
|
|
12
|
+
const GLOBAL_KEY = '__edrvDapHoverProvider__'
|
|
13
|
+
|
|
14
|
+
/** Monaco hover 依赖的最小模型接口。 */
|
|
15
|
+
interface HoverModel {
|
|
16
|
+
getWordAtPosition: (position: HoverPosition) => { word?: string; startColumn: number; endColumn: number } | null
|
|
17
|
+
getLineContent?: (line: number) => string
|
|
18
|
+
getLanguageId?: () => string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 光标位置(1-based)。 */
|
|
22
|
+
interface HoverPosition {
|
|
23
|
+
lineNumber: number
|
|
24
|
+
column: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 树状态键:表达式 + 模型路径 + 光标范围(同一次 hover 内复用展开状态与缓存)。
|
|
29
|
+
* @author ddj 2026年09月21号
|
|
30
|
+
* @param target 光标处表达式与范围
|
|
31
|
+
* @param path 模型工作区路径
|
|
32
|
+
* @returns 状态键
|
|
33
|
+
*/
|
|
34
|
+
function treeKeyOf(target: { expression: string; lineNumber: number; startColumn: number; endColumn: number }, path: string): string {
|
|
35
|
+
return target.expression + '@' + path + ':' + target.lineNumber + ':' + target.startColumn + '-' + target.endColumn
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Lua 保留字集合(保留字不是可求值表达式,避免 adapter 返回 syntax err 噪声)。 */
|
|
39
|
+
const LUA_KEYWORDS = new Set([
|
|
40
|
+
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'goto',
|
|
41
|
+
'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
/** 可求值路径表达式:标识符链,成员用 . 或 : 连接(注释里的中文文本等一律不匹配)。 */
|
|
45
|
+
const EVAL_EXPR = /^[A-Za-z_][A-Za-z0-9_]*(?:[.:][A-Za-z_][A-Za-z0-9_]*)*$/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 是否可交给调试器求值:标识符链且非保留字。
|
|
49
|
+
* @author ddj 2026年09月21号
|
|
50
|
+
* @param text 候选表达式
|
|
51
|
+
* @returns 是否可求值
|
|
52
|
+
*/
|
|
53
|
+
function isEvalExpr(text: string): boolean {
|
|
54
|
+
return EVAL_EXPR.test(text) && !LUA_KEYWORDS.has(text)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 从光标处提取 Lua 标识符/成员表达式(如 this.model、self:GetName)。
|
|
59
|
+
* 光标在链首段时只取该段(悬停 this 求 this,而不是 this.canUpdate);
|
|
60
|
+
* 光标在成员段时取整条链(悬停 canUpdate 求 this.canUpdate)。
|
|
61
|
+
* @author ddj 2026年09月29号 / 2026年09月21号
|
|
62
|
+
* @param model Monaco 文本模型
|
|
63
|
+
* @param position 光标位置
|
|
64
|
+
*/
|
|
65
|
+
export function expressionAt(model: HoverModel, position: HoverPosition): { expression: string; lineNumber: number; startColumn: number; endColumn: number } | null {
|
|
66
|
+
const word = model.getWordAtPosition(position)
|
|
67
|
+
if (!word) return null
|
|
68
|
+
const line = model.getLineContent?.(position.lineNumber) ?? word.word ?? ''
|
|
69
|
+
const start = Math.max(0, word.startColumn - 1)
|
|
70
|
+
const end = Math.max(start, word.endColumn - 1)
|
|
71
|
+
const isPart = (char: string): boolean => /[A-Za-z0-9_.:]/.test(char)
|
|
72
|
+
let left = start
|
|
73
|
+
while (left > 0 && isPart(line[left - 1])) left -= 1
|
|
74
|
+
// 光标词即链首段:只返回该段(向右扩展会把 this 吃成 this.canUpdate)
|
|
75
|
+
if (left === start) {
|
|
76
|
+
const head = line.slice(start, end).trim()
|
|
77
|
+
return head ? { expression: head, lineNumber: position.lineNumber, startColumn: start + 1, endColumn: end + 1 } : null
|
|
78
|
+
}
|
|
79
|
+
let right = end
|
|
80
|
+
while (right < line.length && isPart(line[right])) right += 1
|
|
81
|
+
const expression = line.slice(left, right).trim()
|
|
82
|
+
return expression ? { expression, lineNumber: position.lineNumber, startColumn: left + 1, endColumn: right + 1 } : null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 组装调试块 HTML(头行 + 类型 + 变量树;全部动态文本转义后拼进 supportHtml 片段)。
|
|
87
|
+
* ⚠️ 钩子一律用 data-*:markdown 渲染会剥掉 class(见 hoverTree.ts 常量区注释)。
|
|
88
|
+
* @author ddj 2026年09月21号
|
|
89
|
+
* @param expression 光标处表达式
|
|
90
|
+
* @param value 求值结果
|
|
91
|
+
* @param type 值类型(可空)
|
|
92
|
+
* @param treeHtml 变量树片段(可空)
|
|
93
|
+
* @returns 单块 HTML
|
|
94
|
+
*/
|
|
95
|
+
export function blockHtml(expression: string, value: string, type: string, treeHtml: string): string {
|
|
96
|
+
const head = '<div ' + HEAD_ATTR + '="1">'
|
|
97
|
+
+ '<span ' + EXPR_ATTR + '="1">' + escapeHtml(expression) + '</span>'
|
|
98
|
+
+ '<span ' + EQ_ATTR + '="1"> = </span>'
|
|
99
|
+
+ '<code ' + VAL_ATTR + '="1">' + escapeHtml(value) + '</code>'
|
|
100
|
+
+ '</div>'
|
|
101
|
+
const typeLine = type ? '<div ' + TYPE_ATTR + '="1">type: <code>' + escapeHtml(type) + '</code></div>' : ''
|
|
102
|
+
return '<div ' + BLOCK_ATTR + '="1">' + head + typeLine + treeHtml + '</div>'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 暂停态求值并组装 hover 内容(单个 HTML 块)。
|
|
107
|
+
* @author ddj 2026年09月21号
|
|
108
|
+
* @param monaco Monaco 命名空间
|
|
109
|
+
* @param model 文本模型
|
|
110
|
+
* @param position 光标位置
|
|
111
|
+
* @param token Monaco 取消令牌
|
|
112
|
+
* @returns hover 结果;非暂停、取不到表达式/路径、被取消时为 null
|
|
113
|
+
*/
|
|
114
|
+
async function dapHoverAt(monaco: any, model: HoverModel, position: HoverPosition, token: any): Promise<{ contents: Array<{ value: string; supportHtml?: boolean }>; range: unknown } | null> {
|
|
115
|
+
const snapshot = dapStore.getSnapshot()
|
|
116
|
+
if (snapshot.phase !== 'paused' || token?.isCancellationRequested) return null
|
|
117
|
+
const target = expressionAt(model, position)
|
|
118
|
+
if (!target || !isEvalExpr(target.expression)) return null
|
|
119
|
+
const path = pathOfModel(model)
|
|
120
|
+
if (!path) return null
|
|
121
|
+
const result = await dapStore.evaluate(target.expression, snapshot.selectedFrameId)
|
|
122
|
+
if (/^syntax err/i.test(result.result)) return null
|
|
123
|
+
if (token?.isCancellationRequested) return null
|
|
124
|
+
let treeHtml = ''
|
|
125
|
+
if (result.ref > 0) {
|
|
126
|
+
const children = await dapStore.variables(result.ref)
|
|
127
|
+
if (token?.isCancellationRequested) return null
|
|
128
|
+
// 可展开树:首层随 hover 取回,点「▸」才懒加载下一层(绑定见 hoverTree.ts)
|
|
129
|
+
treeHtml = initialHtml(createTreeState(treeKeyOf(target, path), children))
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
contents: [{ value: blockHtml(target.expression, result.result, result.type ?? '', treeHtml), supportHtml: true }],
|
|
133
|
+
// 必须 new:Monaco 的 Range 是 ES class(createMonacoBaseAPI 导出类本体),
|
|
134
|
+
// 无 new 调用抛 TypeError,被 getHover 的逐 provider try/catch 吞掉 →
|
|
135
|
+
// 表现为「暂停态只有 LSP 浮窗、没有调试值行」。
|
|
136
|
+
range: new monaco.Range(target.lineNumber, target.startColumn, target.lineNumber, target.endColumn),
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 注册暂停态 DAP hover provider(幂等,跨插件重载会先清理旧 provider)。
|
|
142
|
+
* @author ddj 2026年09月29号
|
|
143
|
+
* @param monaco Monaco 命名空间
|
|
144
|
+
*/
|
|
145
|
+
export function registerDapHover(monaco: any): void {
|
|
146
|
+
if (!monaco?.languages?.registerHoverProvider) return
|
|
147
|
+
const host = typeof window === 'undefined' ? null : window as any
|
|
148
|
+
host?.[GLOBAL_KEY]?.dispose?.()
|
|
149
|
+
const disposable = monaco.languages.registerHoverProvider(['lua'], {
|
|
150
|
+
provideHover: async (model: HoverModel, position: HoverPosition, token: any) => {
|
|
151
|
+
try {
|
|
152
|
+
return await dapHoverAt(monaco, model, position, token)
|
|
153
|
+
} catch {
|
|
154
|
+
return null // 异常不外抛:Monaco 会把整个 provider 的结果丢弃
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
if (host) host[GLOBAL_KEY] = { dispose: () => disposable?.dispose?.() }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 注销 DAP hover provider。 */
|
|
162
|
+
export function disposeDapHover(): void {
|
|
163
|
+
const host = typeof window === 'undefined' ? null : window as any
|
|
164
|
+
host?.[GLOBAL_KEY]?.dispose?.()
|
|
165
|
+
if (host) delete host[GLOBAL_KEY]
|
|
166
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — hover 修饰键(Alt)状态。
|
|
3
|
+
* 对齐 CodeBuddy/VS Code 调试浮窗语义:暂停态默认显示调试值,按住 Alt 切换为 LSP 信息。
|
|
4
|
+
* 切换走**显示层行级显隐**(hoverTree.applyAltToHovers):不重算 hover、不重跑 provider,
|
|
5
|
+
* 因此不受 Monaco「同 anchor 直接早退」影响;状态同步不依赖单一键事件——任意带 altKey 的输入
|
|
6
|
+
* 事件都会校正;且**调试浮层可见时消费裸 Alt**——否则 Chromium/宿主把 Alt 当菜单键会把焦点
|
|
7
|
+
* 抢出页面(实测:每次 Alt 后约 4ms 页面失焦、要 4~5s 才回来),此后键事件到不了页面,
|
|
8
|
+
* 切换只能等下一次鼠标移动自愈,观感就是「按下/松开都要等一会儿」。
|
|
9
|
+
* 只读监听(capture 阶段);安装幂等 + 可卸载,跨插件重载经 window 标记认领。
|
|
10
|
+
* 作者 ddj 2026年09月21号
|
|
11
|
+
*/
|
|
12
|
+
import { applyAltToHovers, hasDebugHover } from './hoverTree.js'
|
|
13
|
+
|
|
14
|
+
// --region 常量与状态
|
|
15
|
+
/** 跨插件重载认领标记(存卸载函数)。 */
|
|
16
|
+
const MODE_GLOBAL = '__edrvHoverModeWatch__'
|
|
17
|
+
/** Alt 引发的抢焦点窗口(ms):该窗口内的 blur 不回退状态(见 onBlur)。 */
|
|
18
|
+
const BLUR_GRACE_MS = 500
|
|
19
|
+
/** Alt 是否按住(供 hover provider 同步判定,无需异步等待)。 */
|
|
20
|
+
let altHeld = false
|
|
21
|
+
/** 最近一次「Alt 按住」键事件时间戳(blur 条件复位用)。 */
|
|
22
|
+
let lastAltAt = 0
|
|
23
|
+
/** Alt 状态变化订阅者。 */
|
|
24
|
+
const altListeners = new Set<(held: boolean) => void>()
|
|
25
|
+
// --endregion
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 更新 Alt 状态:仅在真正变化时同步显示层并通知订阅者。
|
|
29
|
+
* @author ddj 2026年09月21号
|
|
30
|
+
* @param next 新状态
|
|
31
|
+
*/
|
|
32
|
+
function setAlt(next: boolean): void {
|
|
33
|
+
if (altHeld === next) return
|
|
34
|
+
altHeld = next
|
|
35
|
+
applyAltToHovers(next)
|
|
36
|
+
for (const fn of Array.from(altListeners)) {
|
|
37
|
+
try { fn(next) } catch { /* 单个订阅异常不影响其它订阅者 */ }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 订阅 Alt 状态变化。
|
|
43
|
+
* @author ddj 2026年09月21号
|
|
44
|
+
* @param fn 变化回调(参数为当前是否按住)
|
|
45
|
+
* @returns 退订函数
|
|
46
|
+
*/
|
|
47
|
+
export function onAltChange(fn: (held: boolean) => void): () => void {
|
|
48
|
+
altListeners.add(fn)
|
|
49
|
+
return () => { altListeners.delete(fn) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Alt 是否按住。
|
|
54
|
+
* @author ddj 2026年09月21号
|
|
55
|
+
* @returns 是否按住
|
|
56
|
+
*/
|
|
57
|
+
export function isAltHeld(): boolean {
|
|
58
|
+
return altHeld
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 设置 Alt 状态(测试注入用;运行期由监听器维护)。
|
|
63
|
+
* @author ddj 2026年09月21号
|
|
64
|
+
* @param on 是否按住
|
|
65
|
+
*/
|
|
66
|
+
export function setAltHeld(on: boolean): void {
|
|
67
|
+
setAlt(Boolean(on))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 是否应消费裸 Alt:仅 Alt 单键(无 Ctrl/Shift/Meta)且有调试浮层可见。
|
|
72
|
+
* 消费 = preventDefault + stopPropagation,阻止浏览器/宿主把 Alt 当菜单键抢走焦点
|
|
73
|
+
* (焦点一旦移出页面,后续键事件到不了页面,切换只能等下一次鼠标移动自愈)。
|
|
74
|
+
* @author ddj 2026年09月21号
|
|
75
|
+
* @param ev 键盘事件(仅取修饰键字段,便于单测)
|
|
76
|
+
* @param debugHoverVisible 是否有可见的调试浮层
|
|
77
|
+
* @returns 是否消费该事件
|
|
78
|
+
*/
|
|
79
|
+
export function consumesBareAlt(ev: { key: string; ctrlKey?: boolean; shiftKey?: boolean; metaKey?: boolean }, debugHoverVisible: boolean): boolean {
|
|
80
|
+
return ev.key === 'Alt' && !ev.ctrlKey && !ev.shiftKey && !ev.metaKey && debugHoverVisible
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Alt 键事件:消费(必要时)+ 记录时间(供 blur 条件复位判断)+ 同步状态。
|
|
85
|
+
* @author ddj 2026年09月21号
|
|
86
|
+
* @param ev 键盘事件
|
|
87
|
+
*/
|
|
88
|
+
function onAltKey(ev: KeyboardEvent): void {
|
|
89
|
+
if (ev.key !== 'Alt') return
|
|
90
|
+
if (consumesBareAlt(ev, hasDebugHover())) {
|
|
91
|
+
ev.preventDefault()
|
|
92
|
+
ev.stopPropagation()
|
|
93
|
+
}
|
|
94
|
+
lastAltAt = Date.now()
|
|
95
|
+
setAlt(ev.type === 'keydown')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 任意输入事件的 altKey 校正(键事件被吞时的兜底)。
|
|
100
|
+
* @author ddj 2026年09月21号
|
|
101
|
+
* @param ev 鼠标/键盘事件
|
|
102
|
+
*/
|
|
103
|
+
function onAnyInput(ev: Event): void {
|
|
104
|
+
const held = Boolean((ev as MouseEvent).altKey)
|
|
105
|
+
if (held) lastAltAt = Date.now()
|
|
106
|
+
setAlt(held)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 窗口失焦复位(条件):刚看到 Alt 键事件则不复位
|
|
111
|
+
* (既可能是宿主菜单抢焦点的 blur,也可能是紧邻 Alt 的页内元素级 blur)。
|
|
112
|
+
* @author ddj 2026年09月21号
|
|
113
|
+
*/
|
|
114
|
+
function onBlur(): void {
|
|
115
|
+
const since = lastAltAt ? Date.now() - lastAltAt : -1
|
|
116
|
+
if (since >= 0 && since < BLUR_GRACE_MS) return
|
|
117
|
+
setAlt(false)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 安装 Alt 跟踪(幂等)。
|
|
122
|
+
* keydown/keyup 记 Alt;任意带 altKey 的输入事件(含 mousemove/mousedown/wheel)校正——
|
|
123
|
+
* 宿主吞掉 Alt 键事件时也能随下一次输入自愈;blur 仅在「不是刚按 Alt」时复位。
|
|
124
|
+
* @author ddj 2026年09月21号
|
|
125
|
+
*/
|
|
126
|
+
export function installHoverMode(): void {
|
|
127
|
+
if (typeof window === 'undefined') return
|
|
128
|
+
const host = window as unknown as Record<string, unknown>
|
|
129
|
+
if (typeof host[MODE_GLOBAL] === 'function') return // 上一代已安装(window 跨重载存活)
|
|
130
|
+
const bound: Array<[string, EventListener]> = [
|
|
131
|
+
['keydown', onAltKey as EventListener],
|
|
132
|
+
['keyup', onAltKey as EventListener],
|
|
133
|
+
['keydown', onAnyInput as EventListener],
|
|
134
|
+
['mousemove', onAnyInput as EventListener],
|
|
135
|
+
['mousedown', onAnyInput as EventListener],
|
|
136
|
+
['wheel', onAnyInput as EventListener],
|
|
137
|
+
['blur', onBlur as EventListener],
|
|
138
|
+
]
|
|
139
|
+
for (const [type, fn] of bound) window.addEventListener(type, fn, true)
|
|
140
|
+
applyAltToHovers(altHeld) // 已存在的 hover 按当前状态对齐
|
|
141
|
+
host[MODE_GLOBAL] = (): void => {
|
|
142
|
+
for (const [type, fn] of bound) window.removeEventListener(type, fn, true)
|
|
143
|
+
altHeld = false // 卸载路径直接复位,不再触发重算
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 卸载 Alt 跟踪(插件重载/卸载时调用)。
|
|
149
|
+
* @author ddj 2026年09月21号
|
|
150
|
+
*/
|
|
151
|
+
export function disposeHoverMode(): void {
|
|
152
|
+
if (typeof window === 'undefined') return
|
|
153
|
+
const host = window as unknown as Record<string, unknown>
|
|
154
|
+
const off = host[MODE_GLOBAL]
|
|
155
|
+
if (typeof off === 'function') off()
|
|
156
|
+
delete host[MODE_GLOBAL]
|
|
157
|
+
altHeld = false
|
|
158
|
+
lastAltAt = 0
|
|
159
|
+
}
|