dsh-vscode-mode 0.1.43 → 0.1.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vscode-mode",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 dsh-better-sidebar 侧边栏与对话同屏)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车",
5
5
  "keywords": [
6
6
  "dsh",
@@ -2,16 +2,18 @@
2
2
  /**
3
3
  * dsh-vscode-mode client — Monaco LSP provider 注册 + 跳转/引用命令。
4
4
  * 数据层:Definition/Reference/DocumentSymbol/Hover provider(同一文件内跳转 Monaco 原生可用)。
5
- * 交互层:edrv.goToDefinition(F12)/ edrv.findReferences(Shift+F12 / 右键),跨文件跳转自研
6
- * (target 经 openFileAt 打开并定位),引用结果用轻量浮动面板展示(点击跳转)。
5
+ * 交互层:edrv.goToDefinition(F12)/ edrv.findReferences(Shift+F12 / 右键)/ Ctrl+点击引用导航,
6
+ * 跨文件跳转自研(target 经 openFileAt 打开并定位),多条引用走 Monaco 原生 References Peek
7
+ * (左侧代码预览 + 右侧引用列表;registerEditorOpener 接管 edrv:// 点击跳转)。
7
8
  * 作者 ddj 2026-08-27
8
9
  */
9
10
  import {
10
11
  pathOfModel, syncDoc, closeDoc, findDefinition, findReferences,
11
12
  fetchDocumentSymbols, fetchHover, fetchSemanticTokens,
12
13
  targetOpenPath, targetMonoPosition, lspStatusFor, refreshStatus,
14
+ lspUriToAbs,
13
15
  } from './lspClient.js'
14
- import { LSP_SEMANTIC_TOKEN_MODIFIERS, LSP_SEMANTIC_TOKEN_TYPES } from '../../../shared/lsp.js'
16
+ import { LSP_SEMANTIC_TOKEN_MODIFIERS, LSP_SEMANTIC_TOKEN_TYPES, monoToLsp } from '../../../shared/lsp.js'
15
17
 
16
18
  const LSP_LANGS = ['lua', 'csharp']
17
19
  const SEMANTIC_LEGEND = {
@@ -20,7 +22,6 @@ const SEMANTIC_LEGEND = {
20
22
  }
21
23
  let registered = false
22
24
  const disposables = []
23
- let overlayEl = null
24
25
 
25
26
  /** 目标文件打开并定位(复用现有 openFileAt 的 edrv:open-editor 事件通道)。 */
26
27
  function openAt(path, line, column) {
@@ -72,6 +73,20 @@ export function registerLspProviders(monaco) {
72
73
 
73
74
  // —— 数据 provider ——
74
75
  disposables.push(
76
+ // 原生跳转(peek 参考文献列表点击 / 原生 go to definition 等)的 edrv:// 打开兜底:
77
+ // 目标 uri 由本插件自己定义(edrv:// 工作区相对路径),Monaco 无法自行加载,
78
+ // 必须经事件通道交给 EditorView openFileAt 打开并定位。
79
+ monaco.editor.registerEditorOpener({
80
+ openCodeEditor: (source, resource, selectionOrPosition) => {
81
+ const path = lspUriToAbs(resource)
82
+ if (!path) return false
83
+ const start = selectionOrPosition?.getStartPosition?.() ?? selectionOrPosition
84
+ const line = Math.max(1, start?.lineNumber ?? 1)
85
+ const column = Math.max(1, start?.column ?? 1)
86
+ openAt(path, line, column)
87
+ return true
88
+ },
89
+ }),
75
90
  monaco.languages.registerDefinitionProvider(LSP_LANGS, {
76
91
  provideDefinition: (model, position, token) => {
77
92
  const path = pathOfModel(model)
@@ -168,12 +183,108 @@ export async function runGoToDefinition(ed) {
168
183
  await goToDefinition(ed, ed.getPosition(), true)
169
184
  }
170
185
 
171
- /** Ctrl+鼠标点击定义:使用点击位置查询,避免依赖当前光标位置。 */
172
- export async function gotoDefinitionAt(ed, position) {
173
- await goToDefinition(ed, position)
186
+ /**
187
+ * Ctrl+鼠标点击:查询其它内容对该项的引用并导航。
188
+ * 0 条 → 无特殊效果;1 条 → 直接跳转;多条 → 打开 Monaco 原生 References Peek
189
+ * (左侧代码预览 + 右侧引用列表,点击经 registerEditorOpener 跳转)。
190
+ * @author ddj 2026年09月02号
191
+ * @param ed Monaco 编辑器
192
+ * @param position 点击位置(Monaco 1-based)
193
+ */
194
+ export async function gotoRefsAt(ed, position) {
195
+ const model = ed.getModel()
196
+ const path = pathOfModel(model)
197
+ if (!path || !position) return
198
+ const all = await findReferences(path, model.getValue(), position, false)
199
+ const others = filterOtherRefs(all, path, position)
200
+ if (!others.length) return
201
+ if (others.length === 1) {
202
+ jumpToLocation(others[0])
203
+ return
204
+ }
205
+ // 计数器就绪:光标移到点击处(原生 Peek 按光标词查询),走原生两栏引用视图
206
+ ed.setPosition(position)
207
+ await triggerReferencePeek(ed)
208
+ }
209
+
210
+ /**
211
+ * 触发 Monaco 原生 References Peek(左侧代码 + 右侧引用列表,原生点击跳转)。
212
+ * @author ddj 2026年09月02号
213
+ * @param ed Monaco 编辑器
214
+ * @returns 是否成功触发(动作缺失时返回 false)
215
+ */
216
+ export async function triggerReferencePeek(ed) {
217
+ const action = ed.getAction('editor.action.referenceSearch.trigger')
218
+ if (!action || typeof action.run !== 'function') return false
219
+ await action.run()
220
+ return true
221
+ }
222
+
223
+ /**
224
+ * 过滤「其它内容」引用:剔除当前点击处自身(同文件且点击位置落入其 range),并按路径+起点去重。
225
+ * @author ddj 2026年09月02号
226
+ * @param locations 引用结果
227
+ * @param path 当前文档路径
228
+ * @param position 点击位置(Monaco 1-based)
229
+ * @returns 过滤后的其它引用
230
+ */
231
+ function filterOtherRefs(locations, path, position) {
232
+ const lspPos = monoToLsp(position.lineNumber, position.column)
233
+ const seen = new Set()
234
+ const out = []
235
+ for (const loc of locations) {
236
+ const rel = targetOpenPath(loc)
237
+ if (sameFile(rel, path) && posInRange(lspPos, loc.lspRange)) continue
238
+ const start = loc.lspRange?.start ?? {}
239
+ const key = String(rel).toLowerCase() + ':' + (start.line ?? 0) + ':' + (start.character ?? 0)
240
+ if (seen.has(key)) continue
241
+ seen.add(key)
242
+ out.push(loc)
243
+ }
244
+ return out
245
+ }
246
+
247
+ /**
248
+ * 两个路径是否指向同一文件(容忍相对/绝对混合与大小写差异)。
249
+ * @author ddj 2026年09月02号
250
+ * @param a 路径 A
251
+ * @param b 路径 B
252
+ * @returns 是否同一文件
253
+ */
254
+ function sameFile(a, b) {
255
+ if (!a || !b) return false
256
+ const na = String(a).replace(/\\/g, '/').toLowerCase()
257
+ const nb = String(b).replace(/\\/g, '/').toLowerCase()
258
+ if (na === nb) return true
259
+ const absA = /^[A-Za-z]:\//.test(na) || na.startsWith('/')
260
+ const absB = /^[A-Za-z]:\//.test(nb) || nb.startsWith('/')
261
+ // 同为相对/同为绝对但不等 → 不同文件(避免根目录文件误匹配同名子目录文件)
262
+ if (absA === absB) return false
263
+ // 一方绝对一方相对:绝对路径以相对路径结尾视为同文件
264
+ return absA ? na.endsWith('/' + nb) : nb.endsWith('/' + na)
265
+ }
266
+
267
+ /**
268
+ * 0-based LSP 位置是否落在 range 内(end 排除;零宽 range 按点匹配)。
269
+ * @author ddj 2026年09月02号
270
+ * @param pos LSP 位置(0-based)
271
+ * @param range LSP range
272
+ * @returns 是否在范围内
273
+ */
274
+ function posInRange(pos, range) {
275
+ const start = range?.start
276
+ if (!start) return false
277
+ const end = range?.end ?? start
278
+ if (start.line === end.line && start.character === end.character) {
279
+ return pos.line === start.line && pos.character === start.character
280
+ }
281
+ if (pos.line < start.line || pos.line > end.line) return false
282
+ if (pos.line === start.line && pos.character < start.character) return false
283
+ if (pos.line === end.line && pos.character >= end.character) return false
284
+ return true
174
285
  }
175
286
 
176
- /** 给一个 Monaco 编辑器绑定 Ctrl/Cmd+鼠标定义跳转(幂等)。 */
287
+ /** 给一个 Monaco 编辑器绑定 Ctrl/Cmd+鼠标引用导航(幂等)。 */
177
288
  export function bindLspEditor(ed) {
178
289
  if (!ed || ed.__edrvLspClick) return
179
290
  ed.__edrvLspClick = true
@@ -187,68 +298,24 @@ export function bindLspEditor(ed) {
187
298
  if (!native || !position || !isLeftButton || (!ctrl && !meta)) return
188
299
  native.preventDefault?.()
189
300
  native.stopPropagation?.()
190
- void gotoDefinitionAt(ed, position)
301
+ void gotoRefsAt(ed, position)
191
302
  })
192
303
  disposables.push(disposable)
193
304
  }
194
305
 
195
- /** Shift+F12 / 右键「查找所有引用」:查询引用并展示浮动面板。 */
306
+ /** Shift+F12 / 右键「查找所有引用」:走 Monaco 原生 References Peek(两栏引用视图)。 */
196
307
  export async function runFindReferences(ed) {
197
- const model = ed.getModel()
198
- const path = pathOfModel(model)
199
308
  const position = ed.getPosition()
200
- if (!path || !position) return
201
- const locations = await findReferences(path, model.getValue(), position, false)
202
- showReferencesOverlay(ed, path, position, locations)
309
+ if (!position) return
310
+ ed.setPosition(position)
311
+ await triggerReferencePeek(ed)
203
312
  }
204
313
 
205
- /** 引用结果浮动面板(轻量 DOM,点击跳转;Esc/失焦关闭)。 */
206
- function showReferencesOverlay(ed, path, position, locations) {
207
- hideReferencesOverlay()
208
- if (!locations.length) {
209
- setStatus('未找到引用')
210
- return
211
- }
212
- const root = ed.getDomNode()
213
- if (!root) return
214
- const el = document.createElement('div')
215
- el.className = 'edrv-refs'
216
- const header = document.createElement('div')
217
- header.className = 'edrv-refs-head'
218
- header.textContent = '引用 ' + locations.length + ' 处'
219
- el.appendChild(header)
220
- const list = document.createElement('div')
221
- list.className = 'edrv-refs-list'
222
- for (const loc of locations) {
223
- const row = document.createElement('div')
224
- row.className = 'edrv-refs-row'
225
- const pos = targetMonoPosition(loc)
226
- const rel = targetOpenPath(loc)
227
- const name = String(rel).split('/').pop() || rel
228
- row.textContent = name + ':' + pos.line + ':' + pos.column
229
- row.title = rel + ':' + pos.line
230
- row.addEventListener('click', () => {
231
- hideReferencesOverlay()
232
- jumpToLocation(loc)
233
- })
234
- list.appendChild(row)
235
- }
236
- el.appendChild(list)
237
- root.appendChild(el)
238
- overlayEl = el
239
- const onKey = (e) => { if (e.key === 'Escape') hideReferencesOverlay() }
240
- window.addEventListener('keydown', onKey)
241
- el.addEventListener('click', (e) => { if (e.target === el) hideReferencesOverlay() })
242
- el._onKey = onKey
243
- }
244
-
245
- export function hideReferencesOverlay() {
246
- if (overlayEl) {
247
- window.removeEventListener('keydown', overlayEl._onKey)
248
- overlayEl.remove()
249
- overlayEl = null
250
- }
251
- }
314
+ /**
315
+ * 兼容占位(历史导出;自绘引用浮窗已移除,改走 Monaco 原生 Peek)。
316
+ * @author ddj 2026年09月02号
317
+ */
318
+ export function hideReferencesOverlay() {}
252
319
 
253
320
  /** 编辑器状态栏提示(复用 EditorView 的 status 通道)。 */
254
321
  function setStatus(text) {
@@ -291,11 +291,5 @@
291
291
  [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); }
292
292
  [data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }
293
293
 
294
- /* LSP 引用结果浮动面板(Shift+F12 / 右键「查找所有引用」) */
295
- [data-edrv-view] .edrv-refs { position: absolute; right: 12px; top: 40px; z-index: 60; width: 360px; max-height: 60%; display: flex; flex-direction: column; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 8px; background: var(--dsw-alias-bg-panel, #161b22); box-shadow: 0 6px 24px rgba(0,0,0,.4); overflow: hidden; font-size: 12px; }
296
- [data-edrv-view] .edrv-refs-head { padding: 6px 10px; font-weight: 600; color: var(--dsw-alias-label-primary, #eee); border-bottom: 1px solid var(--dsw-alias-border-l2, #333); background: var(--dsw-alias-bg-layer-1, transparent); }
297
- [data-edrv-view] .edrv-refs-list { overflow-y: auto; }
298
- [data-edrv-view] .edrv-refs-row { padding: 4px 10px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #c0c8d0); font-family: var(--ds-font-family-code, ui-monospace, monospace); }
299
- [data-edrv-view] .edrv-refs-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); color: var(--dsw-alias-label-primary, #fff); }
300
294
 
301
295