dsh-vscode-mode 0.1.47 → 0.1.49
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 +10 -1
- package/lib/client.js +729 -205
- package/lib/client.js.map +1 -1
- package/lib/index.js +562 -3
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/addToConversation.ts +32 -19
- package/src/client/compat.ts +1 -1
- package/src/client/index.ts +9 -2
- package/src/client/paths.ts +2 -0
- package/src/client/sidebar/SidebarView.ts +82 -16
- package/src/client/sidebar/menuItems.ts +23 -2
- package/src/client/sidebar/panels/FileExplorer.ts +2 -4
- package/src/client/sidebar/panels/RulesPanel.ts +284 -0
- package/src/client/sidebar/panels/index.ts +18 -2
- package/src/client/sidebar/types.ts +5 -0
- package/src/client/sidebarMin.ts +48 -0
- package/src/client/styles/editor.css +43 -0
- package/src/client/ui/EditorView.ts +25 -13
- package/src/client/ui/McpSettings.ts +37 -2
- package/src/client/ui/SideEditorTab.ts +1 -0
- package/src/client/ui/menuPosition.ts +2 -28
- package/src/fileOpenSettings.ts +2 -0
- package/src/index.ts +5 -1
- package/src/rpc.ts +37 -0
- package/src/rules.ts +528 -0
- package/src/shared/rpc.ts +11 -0
- package/src/shared/rules.ts +60 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
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
|
|
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
|
-
/**
|
|
115
|
-
appendReference(
|
|
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
|
|
package/src/client/compat.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const SIDEBAR_PLUGIN = 'dsh-better-sidebar'
|
|
|
11
11
|
|
|
12
12
|
/** 设置 scope 的跨组件上下文形状(与官方 settingsScope 绑定的最小面)。 */
|
|
13
13
|
export interface SettingsScopeLike {
|
|
14
|
-
getSnapshot: () => { status?: string; value?: { fileOpenTool?: unknown; keybindings?: Record<string, string
|
|
14
|
+
getSnapshot: () => { status?: string; value?: { fileOpenTool?: unknown; keybindings?: Record<string, string>; sidebarMinWidth?: unknown }; writable?: boolean }
|
|
15
15
|
subscribe: (listener: () => void) => () => void
|
|
16
16
|
set: (field: string, value: unknown) => Promise<void>
|
|
17
17
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -33,12 +33,14 @@ import { createAddToConversation } from './addToConversation.js'
|
|
|
33
33
|
import { createSidebarPanelRegistry } from './sidebar/registry.js'
|
|
34
34
|
import { createFilePanel } from './sidebar/panels/index.js'
|
|
35
35
|
import { createSearchPanel } from './sidebar/panels/index.js'
|
|
36
|
+
import { createRulesPanel } from './sidebar/panels/index.js'
|
|
36
37
|
import { createTreeMenuRegistry } from './sidebar/contextMenu.js'
|
|
37
38
|
import { createDefaultFileMenuItems } from './sidebar/menuItems.js'
|
|
38
39
|
import { createOutlinePanel } from './outline/index.js'
|
|
39
40
|
import { createOutlineSourceRegistry, registerBuiltinOutlineSources } from './outline/sources.js'
|
|
40
41
|
import { createLspOutlineSource } from './outline/lspSource.js'
|
|
41
42
|
import { keybindingsApply } from './keybindings.js'
|
|
43
|
+
import { sidebarMinApply } from './sidebarMin.js'
|
|
42
44
|
import { setupLsp, setSession } from './monaco/lsp/index.js'
|
|
43
45
|
import type { CompatAdapter } from '../shared/compat.js'
|
|
44
46
|
|
|
@@ -114,6 +116,8 @@ export function apply(ctx: any): void {
|
|
|
114
116
|
ctx.effect(() => sidebarPanels.register(createFilePanel()), 'vscode-mode: sidebar panel')
|
|
115
117
|
// 搜索面板:活动栏「搜索」页签(Ctrl+Shift+F 唤起,VSCode 搜索视图形态)
|
|
116
118
|
ctx.effect(() => sidebarPanels.register(createSearchPanel()), 'vscode-mode: sidebar panel search')
|
|
119
|
+
// 规则面板:活动栏「规则」页签(Codebuddy 规则管理形态:用户/项目规则 + 启用开关,host 注入生效)
|
|
120
|
+
ctx.effect(() => sidebarPanels.register(createRulesPanel()), 'vscode-mode: sidebar panel rules')
|
|
117
121
|
// 文件右键菜单项注册表(对外 provide,供本插件/第三方注册;内置「在文件浏览器中打开」)
|
|
118
122
|
const fileMenuItems = createTreeMenuRegistry()
|
|
119
123
|
ctx.provide('edrvFileContextMenuItems', fileMenuItems)
|
|
@@ -147,12 +151,15 @@ export function apply(ctx: any): void {
|
|
|
147
151
|
return settings.subscribe(sync)
|
|
148
152
|
}, 'vscode-mode: file opener setting sync')
|
|
149
153
|
// 快捷键配置同步:设置提交后立即刷新键位匹配(编辑器/QuickOpen 按事件时读取)
|
|
154
|
+
// 同一订阅里顺带同步侧边栏最小宽度(sidebarMinWidth)→ 派发 edrv:sidebar-min-width 通知编辑器重夹
|
|
150
155
|
ctx.effect(() => {
|
|
151
156
|
if (!settings) return undefined
|
|
152
157
|
const sync = (): void => {
|
|
153
158
|
const snapshot = settings.getSnapshot()
|
|
154
159
|
if (snapshot.status === 'loading') return
|
|
155
160
|
keybindingsApply(snapshot.value?.keybindings)
|
|
161
|
+
const minW = sidebarMinApply(snapshot.value?.sidebarMinWidth)
|
|
162
|
+
window.dispatchEvent(new CustomEvent('edrv:sidebar-min-width', { detail: { value: minW } }))
|
|
156
163
|
}
|
|
157
164
|
sync()
|
|
158
165
|
return settings.subscribe(sync)
|
|
@@ -181,7 +188,7 @@ export function apply(ctx: any): void {
|
|
|
181
188
|
order: 5,
|
|
182
189
|
label: '文件编辑',
|
|
183
190
|
inject: (sessionId: string) => ({ sessionId }),
|
|
184
|
-
}, (props: unknown) => React.createElement(EditorView, Object.assign({}, props, { layout: 'tab', sideHint: SIDEBAR_INSTALL_CMD, schedule, addToConversation, sidebarPanels, outlineSources, fileMenuItems })))
|
|
191
|
+
}, (props: unknown) => React.createElement(EditorView, Object.assign({}, props, { layout: 'tab', sideHint: SIDEBAR_INSTALL_CMD, schedule, addToConversation, sidebarPanels, outlineSources, fileMenuItems, sessions })))
|
|
185
192
|
}
|
|
186
193
|
|
|
187
194
|
// 侧边栏/中央页签两形态互斥切换:sideDisposer(侧边栏 Tab)与 legacyDisposer(中央页签)只保留其一。
|
|
@@ -197,7 +204,7 @@ export function apply(ctx: any): void {
|
|
|
197
204
|
if (legacyDisposer !== null) { legacyDisposer(); legacyDisposer = null }
|
|
198
205
|
sideDisposer = ctx.effect(() => installSideEditor({
|
|
199
206
|
service: sideService as NonNullable<typeof sideService>,
|
|
200
|
-
renderTab: (props: Record<string, unknown>) => React.createElement(SideEditorTab, Object.assign({}, props, { schedule, addToConversation, sidebarPanels, outlineSources, fileMenuItems })),
|
|
207
|
+
renderTab: (props: Record<string, unknown>) => React.createElement(SideEditorTab, Object.assign({}, props, { schedule, addToConversation, sidebarPanels, outlineSources, fileMenuItems, sessions })),
|
|
201
208
|
activeSession: () => {
|
|
202
209
|
const snapshot = sessions?.list?.getSnapshot?.() as { current?: string; byId?: Record<string, { cwd?: string }> } | undefined
|
|
203
210
|
const sessionId = snapshot?.current
|
package/src/client/paths.ts
CHANGED
|
@@ -24,6 +24,8 @@ export const CACHE_KEY = {
|
|
|
24
24
|
sideHint: 'edrv.side-hint-dismissed',
|
|
25
25
|
/** 「性能优化」页工作区栏目展开状态(workspaceFoldCache v1,全局不按会话)。 */
|
|
26
26
|
workspaceFold: 'edrv.ws-fold.v1.',
|
|
27
|
+
/** 规则面板 UI 状态(RulesPanel v1:Tab 与项目工作区选择,全局不按会话)。 */
|
|
28
|
+
rules: 'edrv.rules.v1.',
|
|
27
29
|
} as const
|
|
28
30
|
|
|
29
31
|
/** 按前缀拼会话键(侧边栏/编辑器等需要布局段时自行拼接后传入)。 */
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
|
-
* dsh-vscode-mode client —
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* dsh-vscode-mode client — 侧边栏容器(活动栏常显 + 可隐藏面板区 + 拖拽调宽)。
|
|
4
|
+
* 面板列表来自注册表(活动栏图标/徽标/激活态);最左侧图标列(活动栏)常显,
|
|
5
|
+
* 右侧详情面板区可拖拽调宽/隐藏:visible=false 时仅保留图标列(宽 = 图标列宽),
|
|
6
|
+
* 点击任一图标重新展开面板(onShow),底部按钮在 ◀ 收起 / ▶ 展开间切换。
|
|
7
|
+
* 拖拽调宽下限 = minWidth prop(通用设置 sidebarMinWidth,默认 300),低于下限直接收起面板区。
|
|
8
|
+
* 作者 ddj 2026-08-26 / 2026-09-04
|
|
7
9
|
*/
|
|
8
10
|
import React from 'react'
|
|
9
11
|
import type { SidebarCtx } from './types.js'
|
|
10
12
|
|
|
11
|
-
const W_MIN = 180
|
|
12
13
|
const W_MAX = 560
|
|
14
|
+
/** 面板区隐藏时容器的宽度(= 图标列宽,与 editor.css .edrv-rail 对齐)。 */
|
|
15
|
+
const RAIL_ONLY_W = 44
|
|
16
|
+
/** 隐藏态接缝拖拽条宽度(与 editor.css .edrv-side-resize 对齐)。 */
|
|
17
|
+
const SEAM_W = 5
|
|
18
|
+
/** 隐藏态拖出面板区的触发阈值(px,拖过即展开并继续实时调宽)。 */
|
|
19
|
+
const PULL_OUT_THRESHOLD = 12
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* 侧边栏容器。
|
|
@@ -17,18 +24,24 @@ const W_MAX = 560
|
|
|
17
24
|
* @param props.ctx 面板共享上下文
|
|
18
25
|
* @param props.activePanel 当前激活面板 id
|
|
19
26
|
* @param props.onActive 激活面板变化回调
|
|
27
|
+
* @param props.visible 面板区是否可见(false = 只显示活动栏)
|
|
28
|
+
* @param props.onShow 重新展开面板区回调(活动栏图标点击/底部按钮)
|
|
20
29
|
* @param props.width 面板区宽度
|
|
21
30
|
* @param props.onWidth 宽度变化回调
|
|
22
|
-
* @param props.onHide
|
|
31
|
+
* @param props.onHide 收起面板区回调
|
|
32
|
+
* @param props.minWidth 最小宽度(通用设置 sidebarMinWidth;拖拽低于它触发隐藏)
|
|
23
33
|
*/
|
|
24
34
|
export function SidebarView(props) {
|
|
25
35
|
const panels = props.registry?.list?.() ?? []
|
|
26
36
|
const ctx = props.ctx
|
|
27
37
|
const activePanel = props.activePanel
|
|
28
38
|
const onActive = props.onActive
|
|
39
|
+
const visible = props.visible !== false
|
|
40
|
+
const onShow = props.onShow
|
|
29
41
|
const width = props.width
|
|
30
42
|
const onWidth = props.onWidth
|
|
31
43
|
const onHide = props.onHide
|
|
44
|
+
const wMin = Math.max(120, Math.min(W_MAX, Math.round(Number(props.minWidth) || 300)))
|
|
32
45
|
|
|
33
46
|
const active = panels.find((p) => p.id === activePanel) || panels[0]
|
|
34
47
|
|
|
@@ -36,14 +49,51 @@ export function SidebarView(props) {
|
|
|
36
49
|
e.preventDefault()
|
|
37
50
|
const startX = e.clientX
|
|
38
51
|
const startW = width
|
|
52
|
+
const cleanup = () => {
|
|
53
|
+
window.removeEventListener('pointermove', onMove)
|
|
54
|
+
window.removeEventListener('pointerup', onUp)
|
|
55
|
+
}
|
|
56
|
+
const onUp = () => cleanup()
|
|
39
57
|
const onMove = (ev) => {
|
|
40
|
-
const
|
|
41
|
-
|
|
58
|
+
const raw = startW + (ev.clientX - startX)
|
|
59
|
+
// 低于最小宽度:清理监听并直接收起面板区(不夹住)
|
|
60
|
+
if (raw < wMin) {
|
|
61
|
+
cleanup()
|
|
62
|
+
onHide()
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
onWidth(Math.min(W_MAX, raw))
|
|
42
66
|
}
|
|
43
|
-
|
|
67
|
+
window.addEventListener('pointermove', onMove)
|
|
68
|
+
window.addEventListener('pointerup', onUp)
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 隐藏态接缝拖拽:向右拖过阈值把面板区拉出来(以记忆宽度展开),继续拖动实时调宽。
|
|
74
|
+
* @param e pointerdown 事件
|
|
75
|
+
*/
|
|
76
|
+
const onSeamPointerDown = (e) => {
|
|
77
|
+
e.preventDefault()
|
|
78
|
+
const startX = e.clientX
|
|
79
|
+
const baseW = Math.max(wMin, Math.min(W_MAX, Math.round(Number(width) || wMin)))
|
|
80
|
+
let expanded = false
|
|
81
|
+
let expandStartX = 0
|
|
82
|
+
const cleanup = () => {
|
|
44
83
|
window.removeEventListener('pointermove', onMove)
|
|
45
84
|
window.removeEventListener('pointerup', onUp)
|
|
46
85
|
}
|
|
86
|
+
const onUp = () => cleanup()
|
|
87
|
+
const onMove = (ev) => {
|
|
88
|
+
if (!expanded) {
|
|
89
|
+
if (ev.clientX - startX < PULL_OUT_THRESHOLD) return
|
|
90
|
+
expanded = true
|
|
91
|
+
expandStartX = ev.clientX
|
|
92
|
+
onShow()
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
onWidth(Math.max(wMin, Math.min(W_MAX, baseW + (ev.clientX - expandStartX))))
|
|
96
|
+
}
|
|
47
97
|
window.addEventListener('pointermove', onMove)
|
|
48
98
|
window.addEventListener('pointerup', onUp)
|
|
49
99
|
}
|
|
@@ -54,20 +104,36 @@ export function SidebarView(props) {
|
|
|
54
104
|
const count = typeof p.badge === 'function' ? p.badge(ctx) : null
|
|
55
105
|
return React.createElement('button', {
|
|
56
106
|
key: p.id,
|
|
57
|
-
className: 'edrv-rail-btn' + (active && p.id === active.id ? ' edrv-rail-on' : ''),
|
|
107
|
+
className: 'edrv-rail-btn' + (visible && active && p.id === active.id ? ' edrv-rail-on' : ''),
|
|
58
108
|
title: p.title,
|
|
59
|
-
onClick: () =>
|
|
109
|
+
onClick: () => {
|
|
110
|
+
onActive(p.id)
|
|
111
|
+
// 面板区隐藏时点击图标 = 展开到该面板(VSCode 活动栏行为)
|
|
112
|
+
if (!visible && typeof onShow === 'function') onShow()
|
|
113
|
+
},
|
|
60
114
|
},
|
|
61
115
|
React.createElement('span', { className: 'edrv-rail-icon' }, p.icon),
|
|
62
116
|
count > 0
|
|
63
117
|
? React.createElement('span', { className: 'edrv-rail-badge' }, String(count))
|
|
64
118
|
: null)
|
|
65
119
|
})),
|
|
66
|
-
React.createElement('button', {
|
|
120
|
+
React.createElement('button', {
|
|
121
|
+
className: 'edrv-rail-btn',
|
|
122
|
+
title: (visible ? '隐藏面板区' : '展开面板区') + ' (Ctrl+B)',
|
|
123
|
+
onClick: () => (visible ? onHide() : onShow?.()),
|
|
124
|
+
}, visible ? '◀' : '▶'))
|
|
67
125
|
|
|
68
|
-
return React.createElement('div', {
|
|
126
|
+
return React.createElement('div', {
|
|
127
|
+
className: 'edrv-sidebar' + (visible ? '' : ' edrv-sidebar-rail-only'),
|
|
128
|
+
style: { width: visible ? width : RAIL_ONLY_W + SEAM_W },
|
|
129
|
+
},
|
|
69
130
|
rail,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
131
|
+
visible
|
|
132
|
+
? React.createElement('div', { className: 'edrv-side-body' },
|
|
133
|
+
active ? active.render(ctx) : null)
|
|
134
|
+
: null,
|
|
135
|
+
visible
|
|
136
|
+
? React.createElement('div', { className: 'edrv-side-resize', onPointerDown: onPointerDown })
|
|
137
|
+
// 隐藏态接缝:悬浮显示拖拽光标与高亮,向右拖把面板区拉出来
|
|
138
|
+
: React.createElement('div', { className: 'edrv-side-resize', title: '拖拽展开面板区', onPointerDown: onSeamPointerDown }))
|
|
73
139
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-vscode-mode client — 文件管理右键菜单内置项。
|
|
3
|
-
*
|
|
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
|
-
|
|
223
|
-
|
|
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' },
|