dsh-vscode-mode 0.1.47 → 0.1.48

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.47",
3
+ "version": "0.1.48",
4
4
  "description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 dsh-better-sidebar 侧边栏与对话同屏)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车",
5
5
  "keywords": [
6
6
  "dsh",
@@ -15,6 +15,21 @@ export interface RefRange {
15
15
  /** 引用动作的返回状态。 */
16
16
  export type AddOutcome = 'ok' | 'busy' | 'unavailable'
17
17
 
18
+ /** 引用外观类型:文件/文件夹(DSH reference source 均支持)。 */
19
+ export type RefAppearance = 'file' | 'folder'
20
+
21
+ /** 把追加引用结果映射为可读文案(ok 用 okText;busy 提示已降级纯文本;unavailable 提示不可用)。
22
+ * @author ddj 2026年09月03号
23
+ * @param outcome 追加结果状态
24
+ * @param okText 成功文案(如「已添加文件引用」/「已添加文件夹引用」)
25
+ * @returns 状态栏/通知用文案
26
+ */
27
+ export function statusOfAdd(outcome: AddOutcome, okText: string): string {
28
+ if (outcome === 'ok') return okText
29
+ if (outcome === 'busy') return okText + '(输入框忙,已降级纯文本)'
30
+ return '无法添加到对话(无会话或输入框不可用)'
31
+ }
32
+
18
33
  /** DSH reference source 名(dsh-client-ui-reference 注册的 @file/@session 统一源)。 */
19
34
  const REF_SOURCE = 'reference'
20
35
 
@@ -61,11 +76,17 @@ export function refOf(mention: string, range: RefRange | undefined): string {
61
76
  : `${mention} L${range.startLine}-${range.endLine}`
62
77
  }
63
78
 
64
- /** 构造 DSH 文件引用插入载荷(source=reference,发送序列化为 mention(+range) 文本)。 */
79
+ /** 构造 DSH 文件引用插入载荷(source=reference,发送序列化为 mention(+range) 文本)。
80
+ * @param path 目标路径(相对 cwd 或绝对)
81
+ * @param cwd 会话工作区目录(可选)
82
+ * @param range 行区间(可选)
83
+ * @param appearance 引用外观:'file'(默认)| 'folder'
84
+ */
65
85
  export function buildFileRef(
66
86
  path: string,
67
87
  cwd: string | undefined,
68
88
  range?: RefRange,
89
+ appearance: RefAppearance = 'file',
69
90
  ): { reference: ReferenceInsertLike; mention: string } {
70
91
  const mention = mentionOf(path, cwd)
71
92
  return {
@@ -74,7 +95,7 @@ export function buildFileRef(
74
95
  source: REF_SOURCE,
75
96
  ref: refOf(mention, range),
76
97
  label: labelOf(path, range),
77
- appearance: 'file',
98
+ appearance,
78
99
  clipboardText: mention,
79
100
  },
80
101
  }
@@ -85,7 +106,6 @@ export interface InputLike {
85
106
  state: { getSnapshot: () => { draft: string; draftRev: number } }
86
107
  insertReference: (ref: ReferenceInsertLike, span: TokenSpanLike) => boolean
87
108
  setDraft: (text: string) => void
88
- notify: (level: 'info' | 'error', text: string) => void
89
109
  }
90
110
 
91
111
  /** ReferenceInsert 结构形状(与 dsh-client-ui-input-trigger 类型一致)。 */
@@ -111,8 +131,13 @@ export interface CtxLike {
111
131
 
112
132
  /** 注入器产物:给 EditorView 用的动作集合。 */
113
133
  export interface AddToConversation {
114
- /** 追加文件引用 chip(@path [Lstart-end]);忙态自动降级纯文本。 */
115
- appendReference(sessionId: string | undefined, path: string, range?: RefRange): Promise<AddOutcome>
134
+ /** 追加文件/文件夹引用 chip(@path [Lstart-end]);忙态自动降级纯文本。 */
135
+ appendReference(
136
+ sessionId: string | undefined,
137
+ path: string,
138
+ range?: RefRange,
139
+ appearance?: RefAppearance,
140
+ ): Promise<AddOutcome>
116
141
  }
117
142
 
118
143
  /**
@@ -161,16 +186,6 @@ function cwdOf(ctx: CtxLike, sessionId: string | undefined): string | undefined
161
186
  }
162
187
  }
163
188
 
164
- /** 写入成功后的 composer 内联提示(门面可用时;失败静默)。
165
- * @author ddj 2026年08月25号 */
166
- function notify(input: InputLike, text: string): void {
167
- try {
168
- input.notify?.('info', text)
169
- } catch {
170
- /* 提示失败忽略 */
171
- }
172
- }
173
-
174
189
  /** 输入门面插入引用(拦截异常视作未应用,走降级)。
175
190
  * @author ddj 2026年08月25号 */
176
191
  function safeInsert(input: InputLike, reference: ReferenceInsertLike, span: TokenSpanLike): boolean {
@@ -188,22 +203,20 @@ function safeInsert(input: InputLike, reference: ReferenceInsertLike, span: Toke
188
203
  * @returns 动作集
189
204
  */
190
205
  export function createAddToConversation(ctx: CtxLike): AddToConversation {
191
- const appendReference: AddToConversation['appendReference'] = async (sessionId, path, range) => {
206
+ const appendReference: AddToConversation['appendReference'] = async (sessionId, path, range, appearance) => {
192
207
  const input = inputFor(ctx, sessionId)
193
208
  if (!input) return 'unavailable'
194
209
  const cur = draftCursor(input)
195
210
  if (!cur) return 'unavailable'
196
- const { reference, mention } = buildFileRef(path, cwdOf(ctx, sessionId), range)
211
+ const { reference, mention } = buildFileRef(path, cwdOf(ctx, sessionId), range, appearance)
197
212
  const span: TokenSpanLike = { start: cur.draft.length, end: cur.draft.length, draftRev: cur.draftRev }
198
213
  const ok = safeInsert(input, reference, span)
199
214
  if (!ok) {
200
215
  // 忙态(adjudicating/submitting)或 CAS 失败:降级纯文本追加,保证动作有落点。
201
216
  const gap = cur.draft.length > 0 && !/\s$/.test(cur.draft) ? ' ' : ''
202
217
  input.setDraft(cur.draft + gap + mention + ' ')
203
- notify(input, '已添加文件引用 ' + mention)
204
218
  return 'busy'
205
219
  }
206
- notify(input, '已添加文件引用 ' + mention)
207
220
  return 'ok'
208
221
  }
209
222
 
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * dsh-vscode-mode client — 文件管理右键菜单内置项。
3
- * 首个内置项:「在文件浏览器中打开」(文件→OS Explorer 定位选中、目录→打开目录)。
3
+ * 内置项:「在文件浏览器中打开」(文件→OS Explorer 定位选中、目录→打开目录);
4
+ * 「添加引用到对话」(文件/文件夹引用注入当前会话对话输入框)。
4
5
  * 反馈统一走 ctx.notify(由 EditorView 提供,落到编辑区路径栏状态)。
5
- * 作者 ddj 2026-08-27
6
+ * 作者 ddj 2026-08-27 / 2026-09-03
6
7
  */
7
8
  import { revealInExplorer } from '../fileReveal.js'
9
+ import { statusOfAdd } from '../addToConversation.js'
8
10
  import type { TreeMenuItem } from './contextMenu.js'
9
11
 
10
12
  /**
@@ -24,5 +26,24 @@ export function createDefaultFileMenuItems(): TreeMenuItem[] {
24
26
  })
25
27
  },
26
28
  },
29
+ {
30
+ id: 'add-to-conversation',
31
+ label: '添加引用到对话',
32
+ order: 1,
33
+ // 排除根目录空白区(path==='' 无意义)、无会话、动作集缺失时隐藏
34
+ visible: (target, ctx) => Boolean(target.path && ctx.sessionId && ctx.addToConversation),
35
+ run: (target, ctx) => {
36
+ const isDir = target.type === 'directory'
37
+ const add = ctx.addToConversation
38
+ if (!add) {
39
+ ctx.notify?.('添加到对话不可用')
40
+ return
41
+ }
42
+ const okText = isDir ? '已添加文件夹引用' : '已添加文件引用'
43
+ void add.appendReference(ctx.sessionId, target.path, undefined, isDir ? 'folder' : 'file').then((outcome) => {
44
+ ctx.notify?.(statusOfAdd(outcome, okText))
45
+ })
46
+ },
47
+ },
27
48
  ]
28
49
  }
@@ -11,7 +11,6 @@
11
11
  import React from 'react'
12
12
  import { rpc } from '../../rpc.js'
13
13
  import { ContextMenu } from '../../ui/ContextMenu.js'
14
- import { rowMenuPosition } from '../../ui/menuPosition.js'
15
14
  import { buildTreeMenu } from '../contextMenu.js'
16
15
  import { explorerLoad, explorerSave } from '../../state/explorerCache.js'
17
16
  import { entriesCacheGet, entriesCacheIsFresh, entriesCachePut } from '../../state/explorerEntriesCache.js'
@@ -219,9 +218,8 @@ export function FileExplorer(props) {
219
218
  onContextMenu: (ev) => {
220
219
  ev.preventDefault()
221
220
  ev.stopPropagation()
222
- const rect = ev.currentTarget?.getBoundingClientRect?.()
223
- const position = rowMenuPosition(rect, ev.clientX, ev.clientY)
224
- setMenu({ x: position.left, y: position.top, target: { path: e.path, type: e.type } })
221
+ // 菜单左上角对齐鼠标点击位置(视口坐标,ContextMenu 内再做 viewport clamp)
222
+ setMenu({ x: ev.clientX, y: ev.clientY, target: { path: e.path, type: e.type } })
225
223
  },
226
224
  },
227
225
  React.createElement('span', { className: 'edrv-tree-chev' },
@@ -4,6 +4,7 @@
4
4
  * 作者 ddj 2026-08-26
5
5
  */
6
6
  import type { OutlineSourceRegistry } from '../outline/types.js'
7
+ import type { AddToConversation } from '../addToConversation.js'
7
8
  import type { TreeMenuRegistry } from './contextMenu.js'
8
9
 
9
10
  /** 面板可用的共享上下文(由 SidebarView 从 EditorView 注入,面板不直碰其内部)。 */
@@ -25,6 +26,8 @@ export interface SidebarCtx {
25
26
  outlineSources?: OutlineSourceRegistry
26
27
  /** 文件右键菜单项注册表(公开 provide 为 edrvFileContextMenuItems;文件管理面板构建菜单用)。 */
27
28
  fileMenuItems?: TreeMenuRegistry
29
+ /** 「添加到对话」动作集(文件/文件夹引用注入对话输入框;缺省时菜单项降级提示)。 */
30
+ addToConversation?: AddToConversation
28
31
  /** 面板动作反馈(如右键菜单操作结果 → 编辑区路径栏状态)。 */
29
32
  notify?: (message: string) => void
30
33
  }
@@ -24,6 +24,7 @@ import { setSidePending, SIDEBAR_INSTALL_CMD } from '../sidebarBridge.js'
24
24
  import { upsertViewState, viewStatesLoad, viewStatesSave } from '../state/viewStateCache.js'
25
25
  import { bindingsOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybindings.js'
26
26
  import { createNavHistory } from '../navHistory.js'
27
+ import { statusOfAdd } from '../addToConversation.js'
27
28
  import { CACHE_KEY } from '../paths.js'
28
29
  import { bindLspEditor, runGoToDefinition, runFindReferences, hideReferencesOverlay } from '../monaco/lsp/providers.js'
29
30
  import { bindLspUnderline } from '../monaco/lsp/underline.js'
@@ -1119,15 +1120,7 @@ export function EditorView(props) {
1119
1120
  setTabMenu(null)
1120
1121
  }
1121
1122
 
1122
- /** 把「添加到对话」返回状态映射为状态栏文案。
1123
- * @author ddj 2026年08月25号 */
1124
- const statusOfAdd = (outcome, okText) => {
1125
- if (outcome === 'ok') return okText
1126
- if (outcome === 'busy') return okText + '(输入框忙,已降级纯文本)'
1127
- return '无法添加到对话(无会话或输入框不可用)'
1128
- }
1129
-
1130
- /** 添加文件/选中区引用到对话(异步,状态栏反馈)。 */
1123
+ /** 添加文件/选中区引用到对话(异步,状态栏反馈;状态文案走共享 statusOfAdd)。 */
1131
1124
  const addRefToChat = (path, range) => {
1132
1125
  if (!path) { setStatus('无活动文件'); return }
1133
1126
  if (!addToConversation) { setStatus('添加到对话不可用'); return }
@@ -1406,6 +1399,7 @@ export function EditorView(props) {
1406
1399
  editor: () => editorRef.current,
1407
1400
  outlineSources: props.outlineSources,
1408
1401
  fileMenuItems: props.fileMenuItems,
1402
+ addToConversation,
1409
1403
  notify: (message) => setStatus(message),
1410
1404
  }
1411
1405
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * dsh-vscode-mode client — 右键菜单 viewport 定位纯函数。
3
- * 将目标行锚点与视口边界计算从 React 渲染层拆出,便于复用和单测。
4
- * 作者 ddj 2026-08-28
3
+ * 将视口边界 clamp 从 React 渲染层拆出,便于复用和单测。
4
+ * 作者 ddj 2026-08-28 / 2026-09-03
5
5
  */
6
6
 
7
7
  export interface MenuPosition {
@@ -9,13 +9,6 @@ export interface MenuPosition {
9
9
  top: number
10
10
  }
11
11
 
12
- export interface MenuAnchorRect {
13
- top: number
14
- right: number
15
- width: number
16
- height: number
17
- }
18
-
19
12
  /**
20
13
  * 将菜单坐标限制在 viewport 内。
21
14
  * @author ddj 2026年08月28号
@@ -42,22 +35,3 @@ export function clampMenuPosition(
42
35
  top: Math.max(4, Math.min(y, maxTop)),
43
36
  }
44
37
  }
45
-
46
- /**
47
- * 将文件树行右键菜单锚定到目标行右侧;无法测量时回退到鼠标坐标。
48
- * @author ddj 2026年08月28号
49
- * @param rect 目标行的布局矩形
50
- * @param fallbackX 鼠标 viewport x 坐标
51
- * @param fallbackY 鼠标 viewport y 坐标
52
- * @returns 菜单初始位置
53
- */
54
- export function rowMenuPosition(
55
- rect: MenuAnchorRect | null | undefined,
56
- fallbackX: number,
57
- fallbackY: number,
58
- ): MenuPosition {
59
- if (!rect || rect.width <= 0 || rect.height <= 0) {
60
- return { left: fallbackX, top: fallbackY }
61
- }
62
- return { left: rect.right + 4, top: rect.top }
63
- }