dsh-vscode-mode 0.5.3 → 0.7.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 +72 -2
- package/lib/client.js +12082 -6420
- package/lib/client.js.map +1 -1
- package/lib/index.js +2750 -147
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/copyText.ts +28 -0
- 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/fileClipboard.ts +46 -0
- package/src/client/index.ts +12 -3
- 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/openWithDialog.ts +66 -0
- package/src/client/searchSeed.ts +36 -0
- package/src/client/sidebar/contextMenu.ts +51 -10
- package/src/client/sidebar/menuItems.ts +473 -50
- package/src/client/sidebar/panels/DebugPanel.ts +613 -0
- package/src/client/sidebar/panels/SearchPanel.ts +24 -3
- package/src/client/sidebar/panels/SvnPanel.ts +167 -23
- package/src/client/sidebar/panels/index.ts +19 -0
- package/src/client/sidebar/types.ts +8 -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/tabActions.ts +22 -10
- package/src/client/ui/EditorView.ts +625 -23
- package/src/client/ui/OfficialSideTab.ts +1 -0
- package/src/client/ui/PromptDialog.ts +63 -0
- package/src/client/ui/SideBySideDiff.tsx +214 -29
- package/src/client/ui/SideEditorTab.ts +1 -0
- 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 +63 -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 +209 -5
- package/src/search/ripgrep.ts +127 -11
- package/src/shared/dap.ts +262 -0
- package/src/shared/fsNames.ts +115 -0
- package/src/shared/keybindings.ts +9 -0
- package/src/shared/rpc.ts +52 -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
|
@@ -50,7 +50,7 @@ import { bindingsOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybi
|
|
|
50
50
|
import { getSidebarMinWidth } from '../sidebarMin.js'
|
|
51
51
|
import { navHistoryFor } from '../navHistory.js'
|
|
52
52
|
import { statusOfAdd } from '../addToConversation.js'
|
|
53
|
-
import { setSearchSeed } from '../searchSeed.js'
|
|
53
|
+
import { setSearchScope, setSearchSeed } from '../searchSeed.js'
|
|
54
54
|
import { CACHE_KEY } from '../paths.js'
|
|
55
55
|
import { runGoToDefinition, runFindReferences, hideReferencesOverlay } from '../monaco/lsp/providers.js'
|
|
56
56
|
import { bindLspUnderline } from '../monaco/lsp/underline.js'
|
|
@@ -58,13 +58,15 @@ import { onLspProgress, refreshStatus, setSession as setLspSession } from '../mo
|
|
|
58
58
|
import { setupAiInline, trackAiEditor, aiInlineEnabled } from '../ai/inlineProvider.js'
|
|
59
59
|
import { SnippetsPicker } from './SnippetsPicker.js'
|
|
60
60
|
import { invalidateSnippets, setSnippetsSession, setupSnippets } from '../snippets/provider.js'
|
|
61
|
+
import { setupLaunchJson, prefetchSnippets } from '../dap/launchSnippetProvider.js'
|
|
62
|
+
import { findArrayPos, LAUNCH_JSON_RE } from '../dap/launchInsert.js'
|
|
61
63
|
import {
|
|
62
64
|
absoluteOf, ancestorDirsOf, applyClose, baseNameOf, closeAll, closeOthers, closeRight, closeSaved,
|
|
63
|
-
evictPlan, insertTab, isTreeRevealable, normalizeTabs, pickActive, relativeOf, tabPathOf, togglePin,
|
|
65
|
+
evictPlan, insertTab, isTreeRevealable, normalizeTabs, pickActive, relativeOf, remapPathOf, tabPathOf, togglePin,
|
|
64
66
|
} from '../tabActions.js'
|
|
65
67
|
import { getMaxOpenEditors } from '../editorLimit.js'
|
|
66
68
|
import { buildTabMenu } from '../tabMenu.js'
|
|
67
|
-
import { ensureSvnChanges, ensureSvnStatus, getSvnChanges, getSvnStatus, refreshSvnChanges, svnAdd, svnChangeMapOf, svnDiffBase, svnEditorActions, svnRevert, svnTortoise, svnUpdate } from '../svnStatus.js'
|
|
69
|
+
import { ensureSvnChanges, ensureSvnStatus, getSvnChanges, getSvnStatus, refreshSvnChanges, svnAdd, svnChangeMapOf, svnDiffBase, svnDiffLocalPair, svnEditorActions, svnRevert, svnTortoise, svnUpdate } from '../svnStatus.js'
|
|
68
70
|
import { ensureSvnLog, getSvnLog, refreshSvnLog, svnDiffPair, svnDiffRev, svnDiffWorking, svnLogKeyOf, svnLogLoadedLimit, svnLogTruncated, svnWcRev, SVN_LOG_SHOW_ALL_LIMIT } from '../svnLog.js'
|
|
69
71
|
import { runSvnAction } from '../svnActions.js'
|
|
70
72
|
import { dshTrace } from '../svnStore.js'
|
|
@@ -73,8 +75,22 @@ import { isSvnDiffable } from '../../shared/svn.js'
|
|
|
73
75
|
import type { SvnAction } from '../../shared/svn.js'
|
|
74
76
|
import { createSaveTimer } from '../saveDebounce.js'
|
|
75
77
|
import { ContextMenu } from './ContextMenu.js'
|
|
78
|
+
import { promptName } from './PromptDialog.js'
|
|
79
|
+
import { openWithDialog } from '../openWithDialog.js'
|
|
80
|
+
import { copyText as copyClipText } from '../copyText.js'
|
|
76
81
|
import { SvnLogDialog } from './SvnLogDialog.js'
|
|
82
|
+
import { SvnPatchDialog } from './SvnPatchDialog.js'
|
|
83
|
+
import { SvnSumDialog } from './SvnSumDialog.js'
|
|
77
84
|
import { LogDialog } from './LogDialog.js'
|
|
85
|
+
import { dapStore } from '../dap/store.js'
|
|
86
|
+
import { bpDisabledLinesOf, bpLinesOf } from '../dap/breakpoints.js'
|
|
87
|
+
import { applyBpDecorations, setEditingLine, setHintLine, setPausedLine } from '../dap/decorate.js'
|
|
88
|
+
import { hintLineFor } from '../dap/hintLine.js'
|
|
89
|
+
import { createBpWidget } from '../dap/BpWidget.js'
|
|
90
|
+
import { buildBpMenu } from '../dap/bpMenu.js'
|
|
91
|
+
import { clampToolbarPosition, loadToolbarPosition, saveToolbarPosition } from '../dap/toolbarDrag.js'
|
|
92
|
+
import { editorModelPathOf } from '../dap/modelPath.js'
|
|
93
|
+
import { dapTrace } from '../dap/trace.js'
|
|
78
94
|
|
|
79
95
|
/** 日志弹窗每次加载条数(「加载更多」按此步长递增;上限由 host 的 SVN_LOG_SHOW_ALL_CAP 约束)。 */
|
|
80
96
|
const SVN_LOG_PAGE = 100
|
|
@@ -90,6 +106,18 @@ function svnLogOptsOf(svnLog) {
|
|
|
90
106
|
return { stopOnCopy: svnLog.soc === true, showMerged: svnLog.merged === true, range: svnLog.range ?? null }
|
|
91
107
|
}
|
|
92
108
|
|
|
109
|
+
/**
|
|
110
|
+
* 工作区相对路径 → 所在目录相对路径(W2-2 目录对比的默认目标;无目录 = 工作副本根)。
|
|
111
|
+
* @author ddj 2026年09月20号
|
|
112
|
+
* @param relPath 工作区相对路径(可空)
|
|
113
|
+
* @returns 目录相对路径('' = 工作副本根)
|
|
114
|
+
*/
|
|
115
|
+
function dirOfRel(relPath) {
|
|
116
|
+
const value = String(relPath ?? '').replace(/\\/g, '/')
|
|
117
|
+
const cut = value.lastIndexOf('/')
|
|
118
|
+
return cut > 0 ? value.slice(0, cut) : ''
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
/** 跳转目标高亮的保留时长(LSP/搜索跳转落地后给用户的位置提示,到期自动清除)。 */
|
|
94
122
|
const NAV_FLASH_MS = 1200
|
|
95
123
|
|
|
@@ -194,7 +222,9 @@ export function EditorView(props) {
|
|
|
194
222
|
const [tabMenu, setTabMenu] = React.useState(null) // Tab 右键菜单 { x,y,path }(编辑区右键走 Monaco 原生菜单,无此浮层)
|
|
195
223
|
const [svnStatus, setSvnStatus] = React.useState(null) // SVN 能力状态(SvnStatusPayload;null=未加载,SVN 入口隐藏)
|
|
196
224
|
const [svnChanges, setSvnChanges] = React.useState(null) // SVN 变更清单(null=未加载;徽标/菜单判定)
|
|
197
|
-
const [svnDiff, setSvnDiff] = React.useState(null) // 差异视图 { path, base, working, reason, message, leftLabel, rightLabel, scheme }(null
|
|
225
|
+
const [svnDiff, setSvnDiff] = React.useState(null) // 差异视图 { path, base, working, reason, message, leftLabel, rightLabel, scheme, logTarget?, binary?, encodingHint? }(null=关闭;logTarget 存在 = 来自日志弹窗,差异条可返回日志)
|
|
226
|
+
const [patchDialog, setPatchDialog] = React.useState(null) // W2-1 补丁对话框 { path, text, truncated, binary }(null = 关闭)
|
|
227
|
+
const [sumDialog, setSumDialog] = React.useState(null) // W2-2 目录对比对话框 { path }(null = 关闭)
|
|
198
228
|
const [svnLog, setSvnLog] = React.useState(null) // 日志弹窗 { target, limit, soc?, range? }(null=关闭;P1-4/P1-6 增选项维度)
|
|
199
229
|
const [svnLogData, setSvnLogData] = React.useState(null) // 日志条目(null=未加载)
|
|
200
230
|
const [svnLogBusy, setSvnLogBusy] = React.useState(false) // 日志在途
|
|
@@ -262,6 +292,82 @@ export function EditorView(props) {
|
|
|
262
292
|
const batchBusyRef = React.useRef(false) // 批量 Keep All/Undo All 防重入
|
|
263
293
|
const menuHandlersRef = React.useRef(null) // 右键菜单动作的最新闭包(Monaco addAction 空依赖回调读取)
|
|
264
294
|
const svnMenuDisposersRef = React.useRef([]) // Monaco 右键 SVN 组当前 disposables(同步时先注销旧组)
|
|
295
|
+
// 调试(DAP):store 订阅 tick 驱动重渲 + gutter/F9 切换闭包(ensureEditor 空依赖读取)
|
|
296
|
+
const [dapTick, setDapTick] = React.useState(0)
|
|
297
|
+
React.useEffect(() => dapStore.subscribe(() => setDapTick((t) => t + 1)), [])
|
|
298
|
+
const dapSnap = dapStore.getSnapshot()
|
|
299
|
+
const [dapBarPos, setDapBarPos] = React.useState(() => loadToolbarPosition(scope))
|
|
300
|
+
const [dapBarDragging, setDapBarDragging] = React.useState(false)
|
|
301
|
+
const dapBarRef = React.useRef(null)
|
|
302
|
+
const dapDragRef = React.useRef(null)
|
|
303
|
+
React.useEffect(() => {
|
|
304
|
+
setDapBarPos(loadToolbarPosition(scope))
|
|
305
|
+
}, [scope])
|
|
306
|
+
React.useEffect(() => {
|
|
307
|
+
const onMove = (event) => {
|
|
308
|
+
const drag = dapDragRef.current
|
|
309
|
+
const bar = dapBarRef.current
|
|
310
|
+
const area = bar?.parentElement
|
|
311
|
+
if (!drag || !bar || !area) return
|
|
312
|
+
const areaRect = area.getBoundingClientRect()
|
|
313
|
+
const barRect = bar.getBoundingClientRect()
|
|
314
|
+
const next = clampToolbarPosition({
|
|
315
|
+
left: event.clientX - areaRect.left - drag.offsetX,
|
|
316
|
+
top: event.clientY - areaRect.top - drag.offsetY,
|
|
317
|
+
}, {
|
|
318
|
+
width: areaRect.width,
|
|
319
|
+
height: areaRect.height,
|
|
320
|
+
barWidth: barRect.width,
|
|
321
|
+
barHeight: barRect.height,
|
|
322
|
+
})
|
|
323
|
+
setDapBarPos(next)
|
|
324
|
+
}
|
|
325
|
+
const onUp = () => {
|
|
326
|
+
if (!dapDragRef.current) return
|
|
327
|
+
dapDragRef.current = null
|
|
328
|
+
setDapBarDragging(false)
|
|
329
|
+
const latest = dapBarPosRef.current
|
|
330
|
+
if (latest) saveToolbarPosition(scope, latest)
|
|
331
|
+
}
|
|
332
|
+
document.addEventListener('pointermove', onMove)
|
|
333
|
+
document.addEventListener('pointerup', onUp)
|
|
334
|
+
return () => {
|
|
335
|
+
document.removeEventListener('pointermove', onMove)
|
|
336
|
+
document.removeEventListener('pointerup', onUp)
|
|
337
|
+
}
|
|
338
|
+
}, [scope])
|
|
339
|
+
const dapBarPosRef = React.useRef(dapBarPos)
|
|
340
|
+
dapBarPosRef.current = dapBarPos
|
|
341
|
+
const startDapBarDrag = (event) => {
|
|
342
|
+
if (event.button !== 0 || !dapBarRef.current) return
|
|
343
|
+
const rect = dapBarRef.current.getBoundingClientRect()
|
|
344
|
+
event.preventDefault()
|
|
345
|
+
event.stopPropagation()
|
|
346
|
+
dapDragRef.current = { offsetX: event.clientX - rect.left, offsetY: event.clientY - rect.top }
|
|
347
|
+
setDapBarDragging(true)
|
|
348
|
+
event.currentTarget.setPointerCapture?.(event.pointerId)
|
|
349
|
+
}
|
|
350
|
+
// 进程选择器选中项(多候选附加时;候选集变化回落首个)
|
|
351
|
+
const [dapPickPid, setDapPickPid] = React.useState(0)
|
|
352
|
+
React.useEffect(() => { setDapPickPid(dapSnap.candidates?.[0]?.pid ?? 0) }, [dapSnap.candidates])
|
|
353
|
+
// 断点右键菜单(glyph 区右键)与行内断点编辑浮层(Monaco content widget)
|
|
354
|
+
const [dapBpMenu, setDapBpMenu] = React.useState(null)
|
|
355
|
+
const bpWidgetRef = React.useRef(null) // { close, path, line } | null
|
|
356
|
+
const dapScopeRef = React.useRef(scope)
|
|
357
|
+
const dapCwdRef = React.useRef(cwd)
|
|
358
|
+
dapScopeRef.current = scope
|
|
359
|
+
dapCwdRef.current = cwd
|
|
360
|
+
const dapToggleRef = React.useRef(null)
|
|
361
|
+
dapToggleRef.current = (path, line) => {
|
|
362
|
+
if (!cwd || !path || !line) return
|
|
363
|
+
// 严格对齐 CodeBuddy:任意文件都可切换断点(不再限定 .lua 调试目标)
|
|
364
|
+
dapStore.toggleAt(scope, cwd, path, line)
|
|
365
|
+
}
|
|
366
|
+
// 工作区变化同步调试 store(配置清单/启动基准);有 cwd 时预拉 launch.json 配置
|
|
367
|
+
React.useEffect(() => {
|
|
368
|
+
dapStore.setWorkspace(cwd || '')
|
|
369
|
+
if (cwd) void dapStore.ensureConfigs()
|
|
370
|
+
}, [cwd])
|
|
265
371
|
const svnStatusRef = React.useRef(null) // svnStatus 渲染值镜像(ensureEditor 空依赖闭包读取最新状态)
|
|
266
372
|
const [snippetPicker, setSnippetPicker] = React.useState(null) // 'configure' | 'insert' | null(代码片段浮层)
|
|
267
373
|
const doSaveRef = React.useRef(null) // 保存动作的最新闭包(窗口级保存监听读取)
|
|
@@ -527,6 +633,59 @@ export function EditorView(props) {
|
|
|
527
633
|
setStatus(label + '(' + closing.length + ' 个)')
|
|
528
634
|
}
|
|
529
635
|
|
|
636
|
+
/**
|
|
637
|
+
* 删除落盘后的页签收尾:关闭目标及其子树页签。
|
|
638
|
+
* 不走 closeTab 的「先落盘」:文件已删除,落盘会把删除的文件写回磁盘。
|
|
639
|
+
* @author ddj 2026年09月22号
|
|
640
|
+
* @param path 已删除的工作区相对路径
|
|
641
|
+
*/
|
|
642
|
+
const dropDeletedTabs = (path) => {
|
|
643
|
+
const doomed = tabsRef.current.filter((t) => t.path === path || t.path.startsWith(path + '/'))
|
|
644
|
+
if (!doomed.length) return
|
|
645
|
+
for (const tab of doomed) releaseTab(tab.path)
|
|
646
|
+
commitClose(applyClose(tabsRef.current, new Set(doomed.map((t) => t.path)), activeRef.current))
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* 重命名落盘后的页签改写:目标及其子树页签路径整体改写(含脏标与活动页签)。
|
|
651
|
+
* @author ddj 2026年09月22号
|
|
652
|
+
* @param from 原路径
|
|
653
|
+
* @param to 新路径
|
|
654
|
+
*/
|
|
655
|
+
const remapRenamedTabs = (from, to) => {
|
|
656
|
+
setTabs((prev) => prev.map((tab) => ({ ...tab, path: remapPathOf(tab.path, from, to) })))
|
|
657
|
+
setDirtyMap((prev) => {
|
|
658
|
+
const next = {}
|
|
659
|
+
for (const key of Object.keys(prev)) next[remapPathOf(key, from, to)] = prev[key]
|
|
660
|
+
return next
|
|
661
|
+
})
|
|
662
|
+
setActive((prev) => (prev ? remapPathOf(prev, from, to) : prev))
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// 文件树右键的重命名/删除 → 页签同步(动作在 sidebar/menuItems.ts,经窗口事件桥接;
|
|
666
|
+
// 经 ref 读最新闭包,避免空依赖 effect 捕获陈旧状态)
|
|
667
|
+
const tabFsSyncRef = React.useRef({ rename: () => {}, drop: () => {} })
|
|
668
|
+
tabFsSyncRef.current = { rename: remapRenamedTabs, drop: dropDeletedTabs }
|
|
669
|
+
React.useEffect(() => {
|
|
670
|
+
const onRenamed = (event) => {
|
|
671
|
+
const from = event?.detail?.from
|
|
672
|
+
const to = event?.detail?.to
|
|
673
|
+
if (typeof from !== 'string' || !from || typeof to !== 'string' || !to || from === to) return
|
|
674
|
+
tabFsSyncRef.current.rename(from, to)
|
|
675
|
+
}
|
|
676
|
+
const onDeleted = (event) => {
|
|
677
|
+
const path = event?.detail?.path
|
|
678
|
+
if (typeof path !== 'string' || !path) return
|
|
679
|
+
tabFsSyncRef.current.drop(path)
|
|
680
|
+
}
|
|
681
|
+
window.addEventListener('edrv:path-renamed', onRenamed)
|
|
682
|
+
window.addEventListener('edrv:path-deleted', onDeleted)
|
|
683
|
+
return () => {
|
|
684
|
+
window.removeEventListener('edrv:path-renamed', onRenamed)
|
|
685
|
+
window.removeEventListener('edrv:path-deleted', onDeleted)
|
|
686
|
+
}
|
|
687
|
+
}, [])
|
|
688
|
+
|
|
530
689
|
/**
|
|
531
690
|
* 在已打开页签间循环切换(文件分页归编辑器自带页签栏)。
|
|
532
691
|
* @author ddj 2026年09月10号
|
|
@@ -640,8 +799,9 @@ export function EditorView(props) {
|
|
|
640
799
|
setStatus('已加载')
|
|
641
800
|
} else {
|
|
642
801
|
const base = res?.error ? String(res.error) : '读取失败'
|
|
802
|
+
dapTrace('read-failed', { path, error: base, resolvedPath: res?.resolvedPath, cwd })
|
|
643
803
|
// 带出 host 解析后的真实路径:跳转失败时一眼看出是路径解析错还是目标不存在
|
|
644
|
-
const message = res?.resolvedPath ? base + ':' + String(res.resolvedPath) : base
|
|
804
|
+
const message = res?.resolvedPath ? base + ':' + relativeOf(String(res.resolvedPath), cwd) : base
|
|
645
805
|
setLoadError(message)
|
|
646
806
|
setError(message)
|
|
647
807
|
setStatus('读取失败')
|
|
@@ -1324,6 +1484,20 @@ export function EditorView(props) {
|
|
|
1324
1484
|
['edrv.command.svnAdd', () => runSvnAdd(activeRef.current)],
|
|
1325
1485
|
['edrv.command.svnRevertCli', () => runSvnRevertCli(activeRef.current)],
|
|
1326
1486
|
['edrv.command.svnLog', () => openSvnLog(activeRef.current || undefined)],
|
|
1487
|
+
['edrv.command.svnCreatePatch', () => runSvnAction(SVN_ACTION_BY_ID['create-patch'], {
|
|
1488
|
+
sessionId,
|
|
1489
|
+
scope,
|
|
1490
|
+
path: activeRef.current ?? '',
|
|
1491
|
+
notify: (message) => setStatus(message),
|
|
1492
|
+
openPatchDialog: (payload) => setPatchDialog(payload),
|
|
1493
|
+
})],
|
|
1494
|
+
['edrv.command.svnDiffSum', () => runSvnAction(SVN_ACTION_BY_ID['diff-summarize'], {
|
|
1495
|
+
sessionId,
|
|
1496
|
+
scope,
|
|
1497
|
+
path: dirOfRel(activeRef.current ?? ''),
|
|
1498
|
+
notify: (message) => setStatus(message),
|
|
1499
|
+
openSumDialog: (target) => setSumDialog({ path: target }),
|
|
1500
|
+
})],
|
|
1327
1501
|
['edrv.command.svnCleanup', () => runSvnAction(SVN_ACTION_BY_ID.cleanup, {
|
|
1328
1502
|
sessionId,
|
|
1329
1503
|
scope,
|
|
@@ -1335,6 +1509,11 @@ export function EditorView(props) {
|
|
|
1335
1509
|
['edrv.command.svnTortoiseDiff', () => runSvnTortoise('diff', activeRef.current)],
|
|
1336
1510
|
['edrv.command.svnTortoiseBlame', () => runSvnTortoise('blame', activeRef.current)],
|
|
1337
1511
|
['edrv.command.svnTortoiseRevert', () => runSvnTortoise('revert', activeRef.current)],
|
|
1512
|
+
// 转到行:转发 Monaco 原生 action(widget 本体/占位/跳转全原生,插件只补键位与命令栏入口)
|
|
1513
|
+
['edrv.command.goToLine', () => {
|
|
1514
|
+
const ed = editorRef.current
|
|
1515
|
+
if (ed?.getModel?.()) ed.trigger('edrv-goto', 'editor.action.gotoLine', null)
|
|
1516
|
+
}],
|
|
1338
1517
|
// 诊断日志弹窗(同片段选择器:执行一条命令即关命令栏,避免残浮层遮挡)
|
|
1339
1518
|
['edrv.command.showLogs', () => { closeCommandPalette(); setDlogOpen(true) }],
|
|
1340
1519
|
]
|
|
@@ -1428,6 +1607,8 @@ export function EditorView(props) {
|
|
|
1428
1607
|
setupAiInline(m)
|
|
1429
1608
|
// 代码片段补全 provider 注册(幂等;条目按会话工作区懒加载)
|
|
1430
1609
|
setupSnippets(m)
|
|
1610
|
+
// launch.json「添加配置」补全 provider 注册(幂等;仅 launch.json configurations 数组内出条目)
|
|
1611
|
+
setupLaunchJson(m)
|
|
1431
1612
|
}).catch((e) => {
|
|
1432
1613
|
if (alive) {
|
|
1433
1614
|
setMonacoErr(String(e?.message ?? e))
|
|
@@ -1461,10 +1642,7 @@ export function EditorView(props) {
|
|
|
1461
1642
|
monacoRef.current = monaco
|
|
1462
1643
|
|
|
1463
1644
|
/** 从编辑器实例 model URI 解析活动文件路径(工作区相对;无模型返回 null)。 */
|
|
1464
|
-
const modelPathOf = (edx) =>
|
|
1465
|
-
const uriPath = edx?.getModel?.()?.uri?.path
|
|
1466
|
-
return uriPath ? decodeURIComponent(String(uriPath).replace(/^\//, '')) : null
|
|
1467
|
-
}
|
|
1645
|
+
const modelPathOf = (edx) => editorModelPathOf(edx)
|
|
1468
1646
|
|
|
1469
1647
|
/**
|
|
1470
1648
|
* 同步 Monaco 右键菜单 SVN 组:先注销旧组,再按最新 svn 状态注册(空状态 = 全部移除)。
|
|
@@ -1503,6 +1681,7 @@ export function EditorView(props) {
|
|
|
1503
1681
|
path: def.id === 'update' ? (relPath ?? '') : relPath,
|
|
1504
1682
|
notify: (message) => setStatus(message),
|
|
1505
1683
|
openSvnDiff: (p) => runSvnDiffBase(p),
|
|
1684
|
+
openSvnLocalPair: runSvnDiffLocalPair,
|
|
1506
1685
|
openSvnLog: (p) => openSvnLog(p ?? relPath),
|
|
1507
1686
|
refreshChanges: () => refreshSvnChanges(sessionId, scope),
|
|
1508
1687
|
})
|
|
@@ -1887,9 +2066,105 @@ export function EditorView(props) {
|
|
|
1887
2066
|
if (hideTimerRef.current) { clearTimeout(hideTimerRef.current); hideTimerRef.current = null }
|
|
1888
2067
|
setHoverAct({ region: hit, top, right })
|
|
1889
2068
|
})
|
|
2069
|
+
// 调试断点:行首 glyph 圆点区**左键**点击切换(右键走下方 contextmenu 菜单,
|
|
2070
|
+
// 必须区分按键——否则右键会既 toggle 断点又弹菜单)。
|
|
2071
|
+
// ⚠️ 枚举在 monaco.editor 子命名空间(monaco.editor.MouseTargetType,值为 2);
|
|
2072
|
+
// 取顶层 m.MouseTargetType 是 undefined,会让点击永远静默返回(实测踩坑)。
|
|
2073
|
+
ed.onMouseDown((e) => {
|
|
2074
|
+
const target = e?.target
|
|
2075
|
+
if (!e?.event?.leftButton) return
|
|
2076
|
+
const GUTTER = m.editor?.MouseTargetType?.GUTTER_GLYPH_MARGIN ?? 2
|
|
2077
|
+
if (!target || target.type !== GUTTER) return
|
|
2078
|
+
const line = target.position?.lineNumber
|
|
2079
|
+
const path = pathOf(ed)
|
|
2080
|
+
if (line && path) dapToggleRef.current?.(path, line)
|
|
2081
|
+
})
|
|
2082
|
+
// 断点 hover 预览(对齐 VS Code `ensureBreakpointHintDecoration`):
|
|
2083
|
+
// 鼠标移到 glyph 区(type=2)或行号区(type=3)的可下断点行 → 半透明红点 + pointer 光标;
|
|
2084
|
+
// 移开/离开编辑器 → 清除。已有断点的行不显示预览(避免与实心红点重叠)。
|
|
2085
|
+
ed.onMouseMove((e) => {
|
|
2086
|
+
const target = e?.target
|
|
2087
|
+
const path = pathOf(ed)
|
|
2088
|
+
const list = path ? (dapStore.bpMapOf(dapScopeRef.current)[path] ?? []) : []
|
|
2089
|
+
const line = hintLineFor(target?.type, target?.position?.lineNumber, list.map((it) => it.line))
|
|
2090
|
+
setHintLine(ed, line || null)
|
|
2091
|
+
})
|
|
2092
|
+
ed.onMouseLeave(() => setHintLine(ed, null))
|
|
2093
|
+
// 断点右键菜单:glyph 区右键**始终**由本插件接管(严格对齐 CodeBuddy:任意文件都出菜单,
|
|
2094
|
+
// 不再限定 .lua)。命中行改用 Monaco 官方 getTargetAtClientPoint——
|
|
2095
|
+
// 手算 floor((relY+scrollTop)/lineHeight)+1 **未扣编辑器 padding-top**(实测 6px),
|
|
2096
|
+
// 点在某行框下半部会被算成下一行 → 对空行显示"添加"菜单(实测踩坑)。
|
|
2097
|
+
const glyphHitLine = (ev) => {
|
|
2098
|
+
const edi = editorRef.current
|
|
2099
|
+
if (!edi?.getTargetAtClientPoint) return null
|
|
2100
|
+
const rect = edi.getDomNode?.()?.getBoundingClientRect?.()
|
|
2101
|
+
if (!rect) return null
|
|
2102
|
+
const layout = edi.getLayoutInfo?.()
|
|
2103
|
+
if (!layout) return null
|
|
2104
|
+
const relX = ev.clientX - rect.left
|
|
2105
|
+
if (relX < layout.glyphMarginLeft || relX > layout.glyphMarginLeft + layout.glyphMarginWidth) return null
|
|
2106
|
+
const GUTTER = m.editor?.MouseTargetType?.GUTTER_GLYPH_MARGIN ?? 2
|
|
2107
|
+
const target = edi.getTargetAtClientPoint(ev.clientX, ev.clientY)
|
|
2108
|
+
if (!target || target.type !== GUTTER) return null
|
|
2109
|
+
return target.position?.lineNumber ?? null
|
|
2110
|
+
}
|
|
2111
|
+
const onBpCtxCapture = (ev) => {
|
|
2112
|
+
const line = glyphHitLine(ev)
|
|
2113
|
+
if (!line) return
|
|
2114
|
+
const path = pathOf(editorRef.current)
|
|
2115
|
+
if (!path) return
|
|
2116
|
+
const entry = (dapStore.bpMapOf(dapScopeRef.current)[path] ?? []).find((it) => it.line === line)
|
|
2117
|
+
ev.preventDefault()
|
|
2118
|
+
ev.stopPropagation()
|
|
2119
|
+
setDapBpMenu({
|
|
2120
|
+
x: ev.clientX,
|
|
2121
|
+
y: ev.clientY,
|
|
2122
|
+
path,
|
|
2123
|
+
line,
|
|
2124
|
+
hasBp: !!entry,
|
|
2125
|
+
enabled: entry ? entry.enabled !== false : true,
|
|
2126
|
+
isLogpoint: !!(entry && entry.logMessage),
|
|
2127
|
+
})
|
|
2128
|
+
}
|
|
2129
|
+
node.addEventListener('contextmenu', onBpCtxCapture, true)
|
|
1890
2130
|
editorRef.current = ed
|
|
1891
2131
|
}, [])
|
|
1892
2132
|
|
|
2133
|
+
// 调试断点装饰:断点表/活动文件变化时全量替换行首圆点(独立 collection,与差异/下划线互不干扰);
|
|
2134
|
+
// 适配器真实回执 verified:false 的行渲染空心红圈(对齐 VS Code 未验证语义)
|
|
2135
|
+
React.useEffect(() => {
|
|
2136
|
+
const ed = editorRef.current
|
|
2137
|
+
if (!ed || !active) return
|
|
2138
|
+
const map = dapStore.bpMapOf(scope)
|
|
2139
|
+
const unverified = bpLinesOf(map, active).filter((line) => dapStore.ackOf(active, line)?.verified === false)
|
|
2140
|
+
applyBpDecorations(ed, bpLinesOf(map, active), bpDisabledLinesOf(map, active), unverified)
|
|
2141
|
+
}, [scope, active, contentPath, dapSnap.bpVersion, dapSnap.bpAckVersion, monaco])
|
|
2142
|
+
|
|
2143
|
+
// 行内断点编辑浮层:切换文件 / 编辑器重建时关闭(浮层锚定旧 model 的行号,留着会错位)
|
|
2144
|
+
React.useEffect(() => {
|
|
2145
|
+
closeBpWidget()
|
|
2146
|
+
}, [active, contentPath])
|
|
2147
|
+
|
|
2148
|
+
// 编辑浮层的目标断点被删除(如 agent 清理埋点/用户在面板删除)→ 浮层自动关闭
|
|
2149
|
+
React.useEffect(() => {
|
|
2150
|
+
const held = bpWidgetRef.current
|
|
2151
|
+
if (!held) return
|
|
2152
|
+
const entry = (dapStore.bpMapOf(scope)[held.path] ?? []).some((it) => it.line === held.line)
|
|
2153
|
+
if (!entry) closeBpWidget()
|
|
2154
|
+
}, [scope, dapSnap.bpVersion])
|
|
2155
|
+
|
|
2156
|
+
// 编辑器卸载时移除浮层(content widget 不随 React 树回收)
|
|
2157
|
+
React.useEffect(() => () => closeBpWidget(), [])
|
|
2158
|
+
|
|
2159
|
+
// 调试停帧行:paused 且首帧属于当前文件时高亮该行;继续/停止/切走自动清除
|
|
2160
|
+
React.useEffect(() => {
|
|
2161
|
+
const ed = editorRef.current
|
|
2162
|
+
if (!ed) return
|
|
2163
|
+
const top = dapSnap.topFrame
|
|
2164
|
+
const hit = top && top.file && sameFile(top.file, active)
|
|
2165
|
+
setPausedLine(ed, hit ? top.line : null)
|
|
2166
|
+
}, [dapSnap.phase, dapSnap.topFrame, active, contentPath, sameFile])
|
|
2167
|
+
|
|
1893
2168
|
/**
|
|
1894
2169
|
* 跳转落地后给目标区域挂临时高亮(默认 1.2s 后自动清除)。
|
|
1895
2170
|
*
|
|
@@ -1921,6 +2196,14 @@ export function EditorView(props) {
|
|
|
1921
2196
|
|
|
1922
2197
|
// 打开文件后的跳转(导航历史恢复/差异聚焦/搜索与 LSP 行列跳转,目标内容就绪后执行一次)
|
|
1923
2198
|
//
|
|
2199
|
+
// ⚠️ 依赖必须含 monaco:contentReady 在**文本到达**时即为真,可能早于 Monaco 就绪;
|
|
2200
|
+
// 那一帧 editorRef.current 仍是 null → 本 effect 提前返回并保留 pendingFocus,随后
|
|
2201
|
+
// Monaco 载入、ensureEditor 建好编辑器,若 effect 不因 monaco 变化重跑,pendingFocus
|
|
2202
|
+
// 就永久滞留 —— 观感是「首次跳转只打开文件不定位,手动再触发一次才落到目标行」
|
|
2203
|
+
// (实测复现:Monaco 未就绪时派发 line=500,首次停在 1:1,二次触发才到 500)。
|
|
2204
|
+
// ⚠️ 依赖也必须含 mdPreviewing:预览态切换会替换 Monaco host 并**重建**编辑器实例,
|
|
2205
|
+
// 而 active/content 都没变;缺此依赖同样不重跑(同下方 model 同步 effect)。
|
|
2206
|
+
//
|
|
1924
2207
|
// ⚠️ 门控必须用 contentReady(contentPath === active),不能用 `content === null`:
|
|
1925
2208
|
// 跨文件跳转时切页签的那一帧 content 仍是**上一个文件**的内容(非 null),据此判定
|
|
1926
2209
|
// 「已就绪」会提前定位;随后真实内容到达触发 model 重设/内容写入,Monaco 会重置光标,
|
|
@@ -1962,7 +2245,7 @@ export function EditorView(props) {
|
|
|
1962
2245
|
ed.revealLineInCenter(Math.max(1, target.start ?? 1))
|
|
1963
2246
|
ed.setPosition({ lineNumber: Math.max(1, target.start ?? 1), column: 1 })
|
|
1964
2247
|
ed.focus()
|
|
1965
|
-
}, [active, content, contentReady, pendingRegions, focusRequest])
|
|
2248
|
+
}, [active, content, contentReady, pendingRegions, focusRequest, monaco, mdPreviewing])
|
|
1966
2249
|
|
|
1967
2250
|
const jumpTo = (region) => {
|
|
1968
2251
|
if (editorRef.current) {
|
|
@@ -2379,6 +2662,8 @@ export function EditorView(props) {
|
|
|
2379
2662
|
leftLabel: 'BASE',
|
|
2380
2663
|
rightLabel: '工作区',
|
|
2381
2664
|
scheme: 'edrv-svn-base',
|
|
2665
|
+
binary: outcome.binary === true,
|
|
2666
|
+
encodingHint: outcome.encodingHint === true,
|
|
2382
2667
|
})
|
|
2383
2668
|
setStatus(outcome.message)
|
|
2384
2669
|
})
|
|
@@ -2390,8 +2675,9 @@ export function EditorView(props) {
|
|
|
2390
2675
|
* @author ddj 2026年09月16号
|
|
2391
2676
|
* @param relPath 目标工作区相对路径
|
|
2392
2677
|
* @param revision 目标版本
|
|
2678
|
+
* @param logTarget 来自日志弹窗时的日志目标(不传 = 非日志入口)
|
|
2393
2679
|
*/
|
|
2394
|
-
const runSvnDiffRev = (relPath, revision) => {
|
|
2680
|
+
const runSvnDiffRev = (relPath, revision, logTarget) => {
|
|
2395
2681
|
if (!relPath) { setStatus('无目标文件'); return }
|
|
2396
2682
|
setStatus('读取 r' + revision + ' 差异…')
|
|
2397
2683
|
void svnDiffRev(sessionId, relPath, revision).then((outcome) => {
|
|
@@ -2406,6 +2692,9 @@ export function EditorView(props) {
|
|
|
2406
2692
|
leftLabel: 'r' + Math.max(1, revision - 1),
|
|
2407
2693
|
rightLabel: 'r' + revision,
|
|
2408
2694
|
scheme: 'edrv-svn-rev' + revision,
|
|
2695
|
+
binary: outcome.binary === true,
|
|
2696
|
+
encodingHint: outcome.encodingHint === true,
|
|
2697
|
+
logTarget,
|
|
2409
2698
|
})
|
|
2410
2699
|
setStatus('已打开 r' + revision + ' 差异')
|
|
2411
2700
|
})
|
|
@@ -2418,8 +2707,9 @@ export function EditorView(props) {
|
|
|
2418
2707
|
* @param relPath 目标工作区相对路径
|
|
2419
2708
|
* @param revA 左侧版本(选中顺序在前)
|
|
2420
2709
|
* @param revB 右侧版本(选中顺序在后)
|
|
2710
|
+
* @param logTarget 来自日志弹窗时的日志目标(不传 = 非日志入口)
|
|
2421
2711
|
*/
|
|
2422
|
-
const runSvnDiffPair = (relPath, revA, revB) => {
|
|
2712
|
+
const runSvnDiffPair = (relPath, revA, revB, logTarget) => {
|
|
2423
2713
|
if (!relPath) { setStatus('仅文件目标支持比较两个修订'); return }
|
|
2424
2714
|
setStatus('读取 r' + revA + ' ↔ r' + revB + ' 差异…')
|
|
2425
2715
|
void svnDiffPair(sessionId, relPath, revA, revB).then((outcome) => {
|
|
@@ -2436,6 +2726,9 @@ export function EditorView(props) {
|
|
|
2436
2726
|
leftLabel: 'r' + revA,
|
|
2437
2727
|
rightLabel: 'r' + revB,
|
|
2438
2728
|
scheme: 'edrv-svn-pair-' + revA + '-' + revB,
|
|
2729
|
+
binary: outcome.binary === true,
|
|
2730
|
+
encodingHint: outcome.encodingHint === true,
|
|
2731
|
+
logTarget,
|
|
2439
2732
|
})
|
|
2440
2733
|
setStatus('已打开 r' + revA + ' ↔ r' + revB + ' 差异')
|
|
2441
2734
|
})
|
|
@@ -2447,8 +2740,9 @@ export function EditorView(props) {
|
|
|
2447
2740
|
* @author ddj 2026年09月17号
|
|
2448
2741
|
* @param relPath 目标工作区相对路径
|
|
2449
2742
|
* @param revision 左侧版本
|
|
2743
|
+
* @param logTarget 来自日志弹窗时的日志目标(不传 = 非日志入口)
|
|
2450
2744
|
*/
|
|
2451
|
-
const runSvnDiffWorking = (relPath, revision) => {
|
|
2745
|
+
const runSvnDiffWorking = (relPath, revision, logTarget) => {
|
|
2452
2746
|
if (!relPath) { setStatus('仅文件目标支持与工作副本比较'); return }
|
|
2453
2747
|
setStatus('读取 r' + revision + ' ↔ 工作副本 差异…')
|
|
2454
2748
|
void svnDiffWorking(sessionId, relPath, revision).then((outcome) => {
|
|
@@ -2462,11 +2756,42 @@ export function EditorView(props) {
|
|
|
2462
2756
|
leftLabel: 'r' + revision,
|
|
2463
2757
|
rightLabel: '工作区',
|
|
2464
2758
|
scheme: 'edrv-svn-work-' + revision,
|
|
2759
|
+
binary: outcome.binary === true,
|
|
2760
|
+
encodingHint: outcome.encodingHint === true,
|
|
2761
|
+
logTarget,
|
|
2465
2762
|
})
|
|
2466
2763
|
setStatus('已打开 r' + revision + ' ↔ 工作副本 差异')
|
|
2467
2764
|
})
|
|
2468
2765
|
}
|
|
2469
2766
|
|
|
2767
|
+
/**
|
|
2768
|
+
* 打开双侧本地文件差异(W2-4 冲突副本 `.rN` ↔ `.mine`,只读并排;resolve 由用户自行执行)。
|
|
2769
|
+
* @author ddj 2026年09月20号
|
|
2770
|
+
* @param left 左侧工作区相对路径
|
|
2771
|
+
* @param right 右侧工作区相对路径
|
|
2772
|
+
* @param leftLabel 左侧标签(如 `.r293524`)
|
|
2773
|
+
* @param rightLabel 右侧标签(如 `.mine`)
|
|
2774
|
+
* @param title 标题(冲突文件路径)
|
|
2775
|
+
*/
|
|
2776
|
+
const runSvnDiffLocalPair = (left, right, leftLabel, rightLabel, title) => {
|
|
2777
|
+
setStatus('读取冲突副本差异…')
|
|
2778
|
+
void svnDiffLocalPair(sessionId, left, right).then((outcome) => {
|
|
2779
|
+
if (!outcome.ok) { setStatus(outcome.message); return }
|
|
2780
|
+
setSvnDiff({
|
|
2781
|
+
path: title,
|
|
2782
|
+
base: outcome.leftText ?? null,
|
|
2783
|
+
working: outcome.rightText ?? '',
|
|
2784
|
+
message: '解决后执行 svn resolve --accept working(本插件不自动执行)',
|
|
2785
|
+
leftLabel,
|
|
2786
|
+
rightLabel,
|
|
2787
|
+
scheme: 'edrv-svn-local',
|
|
2788
|
+
binary: outcome.binary === true,
|
|
2789
|
+
encodingHint: outcome.encodingHint === true,
|
|
2790
|
+
})
|
|
2791
|
+
setStatus('已打开冲突副本对比')
|
|
2792
|
+
})
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2470
2795
|
/**
|
|
2471
2796
|
* 打开日志弹窗(P3):加载该目标的提交历史。
|
|
2472
2797
|
* @author ddj 2026年09月16号
|
|
@@ -2482,6 +2807,17 @@ export function EditorView(props) {
|
|
|
2482
2807
|
ensureSvnLog(sessionId, scope, target, SVN_LOG_PAGE)
|
|
2483
2808
|
}
|
|
2484
2809
|
|
|
2810
|
+
/**
|
|
2811
|
+
* 从日志弹窗打开的差异视图返回日志:按打开时记录的日志目标重开弹窗。
|
|
2812
|
+
* 日志数据走 scope 缓存(openSvnLog → ensureSvnLog 命中即不重查);选中项不复原。
|
|
2813
|
+
* @author ddj 2026年09月18号
|
|
2814
|
+
*/
|
|
2815
|
+
const backToSvnLog = () => {
|
|
2816
|
+
const target = svnDiff?.logTarget ?? ''
|
|
2817
|
+
setSvnDiff(null)
|
|
2818
|
+
openSvnLog(target)
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2485
2821
|
/** 加入版本控制(活动文件;未纳入版本控制的文件才有意义,host 侧会再校验)。 */
|
|
2486
2822
|
const runSvnAdd = (relPath) => {
|
|
2487
2823
|
if (!relPath) { setStatus('无活动文件'); return }
|
|
@@ -2523,17 +2859,15 @@ export function EditorView(props) {
|
|
|
2523
2859
|
|
|
2524
2860
|
/**
|
|
2525
2861
|
* 复制文本到剪贴板(状态栏反馈;浏览器拒绝时提示,不抛异常)。
|
|
2526
|
-
*
|
|
2862
|
+
* 剪贴板写入与降级文案收敛到 client/copyText.ts(文件树菜单同源共用)。
|
|
2863
|
+
* @author ddj 2026年09月11号 / 2026年09月22号
|
|
2527
2864
|
* @param text 待复制文本
|
|
2528
2865
|
* @param okText 成功文案
|
|
2529
2866
|
*/
|
|
2530
2867
|
const copyText = (text, okText) => {
|
|
2531
2868
|
const value = String(text ?? '')
|
|
2532
2869
|
if (!value) { setStatus('无可复制内容'); return }
|
|
2533
|
-
|
|
2534
|
-
navigator.clipboard.writeText(value)
|
|
2535
|
-
.then(() => setStatus(okText + ':' + value))
|
|
2536
|
-
.catch(() => setStatus('复制失败(浏览器拒绝剪贴板写入)'))
|
|
2870
|
+
void copyClipText(value, okText + ':' + value, setStatus)
|
|
2537
2871
|
}
|
|
2538
2872
|
|
|
2539
2873
|
/**
|
|
@@ -2584,6 +2918,7 @@ export function EditorView(props) {
|
|
|
2584
2918
|
path,
|
|
2585
2919
|
notify: (message) => setStatus(message),
|
|
2586
2920
|
openSvnDiff: (p) => runSvnDiffBase(p),
|
|
2921
|
+
openSvnLocalPair: runSvnDiffLocalPair,
|
|
2587
2922
|
openSvnLog: (p) => openSvnLog(p ?? path),
|
|
2588
2923
|
refreshChanges: () => refreshSvnChanges(sessionId, scope),
|
|
2589
2924
|
})
|
|
@@ -2681,6 +3016,8 @@ export function EditorView(props) {
|
|
|
2681
3016
|
*/
|
|
2682
3017
|
const openFileAt = (path, line, column, endLine, endColumn) => {
|
|
2683
3018
|
if (!path) return
|
|
3019
|
+
const tracePath = tabPathOf(path, cwd)
|
|
3020
|
+
dapTrace('open-file-at', { input: path, normalized: tracePath, line, cwd })
|
|
2684
3021
|
recordNav()
|
|
2685
3022
|
// G9:同 openFile,先归一再进页签,保证待跳转路径与 active 同形态
|
|
2686
3023
|
const normalized = addTabNorm(path, true)
|
|
@@ -2689,6 +3026,51 @@ export function EditorView(props) {
|
|
|
2689
3026
|
setFocusRequest((value) => value + 1)
|
|
2690
3027
|
}
|
|
2691
3028
|
|
|
3029
|
+
// 停帧跳转/调试命令监听(openFileAt 每渲染重建,经 ref 让空依赖监听读最新闭包)
|
|
3030
|
+
const dapOpenRef = React.useRef(null)
|
|
3031
|
+
dapOpenRef.current = openFileAt
|
|
3032
|
+
React.useEffect(() => {
|
|
3033
|
+
const onStopped = (e) => {
|
|
3034
|
+
const detail = e?.detail
|
|
3035
|
+
dapTrace('debug-stopped', detail)
|
|
3036
|
+
if (!detail?.file) {
|
|
3037
|
+
// 适配器只给了 chunkname 且工作区未定位到源文件:明确告知,不留静默无反应
|
|
3038
|
+
setStatus('已暂停:未定位到源文件')
|
|
3039
|
+
return
|
|
3040
|
+
}
|
|
3041
|
+
dapOpenRef.current?.(detail.file, detail.line, 1)
|
|
3042
|
+
}
|
|
3043
|
+
const onToggle = () => {
|
|
3044
|
+
const ed = editorRef.current
|
|
3045
|
+
const path = modelPathOf(ed)
|
|
3046
|
+
const line = ed?.getPosition?.()?.lineNumber
|
|
3047
|
+
if (path && line) dapToggleRef.current?.(path, line)
|
|
3048
|
+
else setStatus('无活动文件,无法切换断点')
|
|
3049
|
+
}
|
|
3050
|
+
const onToggleEvt = () => onToggle()
|
|
3051
|
+
const onStart = () => dapStore.startOrContinue()
|
|
3052
|
+
const onStop = () => { void dapStore.stop() }
|
|
3053
|
+
const onStepOver = () => { void dapStore.action('next') }
|
|
3054
|
+
const onStepIn = () => { void dapStore.action('stepIn') }
|
|
3055
|
+
const onStepOut = () => { void dapStore.action('stepOut') }
|
|
3056
|
+
window.addEventListener('edrv:debug-stopped', onStopped)
|
|
3057
|
+
window.addEventListener('edrv.command.debugToggleBreakpoint', onToggleEvt)
|
|
3058
|
+
window.addEventListener('edrv.command.debugStartContinue', onStart)
|
|
3059
|
+
window.addEventListener('edrv.command.debugStepOver', onStepOver)
|
|
3060
|
+
window.addEventListener('edrv.command.debugStepInto', onStepIn)
|
|
3061
|
+
window.addEventListener('edrv.command.debugStepOut', onStepOut)
|
|
3062
|
+
window.addEventListener('edrv.command.debugStop', onStop)
|
|
3063
|
+
return () => {
|
|
3064
|
+
window.removeEventListener('edrv:debug-stopped', onStopped)
|
|
3065
|
+
window.removeEventListener('edrv.command.debugToggleBreakpoint', onToggleEvt)
|
|
3066
|
+
window.removeEventListener('edrv.command.debugStartContinue', onStart)
|
|
3067
|
+
window.removeEventListener('edrv.command.debugStepOver', onStepOver)
|
|
3068
|
+
window.removeEventListener('edrv.command.debugStepInto', onStepIn)
|
|
3069
|
+
window.removeEventListener('edrv.command.debugStepOut', onStepOut)
|
|
3070
|
+
window.removeEventListener('edrv.command.debugStop', onStop)
|
|
3071
|
+
}
|
|
3072
|
+
}, [])
|
|
3073
|
+
|
|
2692
3074
|
const openPath = () => {
|
|
2693
3075
|
const p = (pathDraft || '').trim()
|
|
2694
3076
|
if (!p) return
|
|
@@ -2900,6 +3282,7 @@ export function EditorView(props) {
|
|
|
2900
3282
|
message: svnDiff.message,
|
|
2901
3283
|
leftLabel: svnDiff.leftLabel,
|
|
2902
3284
|
rightLabel: svnDiff.rightLabel,
|
|
3285
|
+
onBack: svnDiff.logTarget !== undefined ? backToSvnLog : undefined,
|
|
2903
3286
|
onClose: () => setSvnDiff(null),
|
|
2904
3287
|
})
|
|
2905
3288
|
} else if (!monaco && !monacoErr) {
|
|
@@ -3064,14 +3447,140 @@ export function EditorView(props) {
|
|
|
3064
3447
|
svnChanges,
|
|
3065
3448
|
svnChangeMap: svnChangeMapOf(scope),
|
|
3066
3449
|
openSvnDiff: (p) => runSvnDiffBase(p),
|
|
3450
|
+
openSvnLocalPair: runSvnDiffLocalPair,
|
|
3067
3451
|
refreshSvnChanges: () => refreshSvnChanges(sessionId, scope),
|
|
3068
3452
|
openSvnLog: (p) => openSvnLog(p),
|
|
3069
3453
|
confirm: (message) => (typeof window === 'undefined' ? false : window.confirm(message)),
|
|
3070
3454
|
notify: (message) => setStatus(message),
|
|
3455
|
+
// 名称输入弹窗(资源管理器右键的新建文件/新建文件夹/重命名)
|
|
3456
|
+
prompt: promptName,
|
|
3457
|
+
// 「打开方式…」(资源管理器右键、文件目标):打开器选择弹窗(注册表由 index 装配传入)
|
|
3458
|
+
openWith: (p) => {
|
|
3459
|
+
const openers = props.fileOpeners
|
|
3460
|
+
if (!openers) return
|
|
3461
|
+
openWithDialog(openers, { sessionId, cwd: cwd ?? undefined }, p, (opener) => {
|
|
3462
|
+
if (opener) setStatus('已用「' + opener.label + '」打开:' + baseNameOf(p))
|
|
3463
|
+
})
|
|
3464
|
+
},
|
|
3465
|
+
// 「在文件夹中查找…」(资源管理器右键、目录目标):目录过滤种子 + 跳搜索面板
|
|
3466
|
+
searchInFolder: (dir) => {
|
|
3467
|
+
setSearchScope(dir)
|
|
3468
|
+
openSearchPanel()
|
|
3469
|
+
},
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
// 调试工具条(VS Code 浮动条紧凑版,图2 形态):配置下拉 + 启动/继续 + 单步组 + 停止 + 相位。
|
|
3473
|
+
// 配置来自工作区 .dsh/launch.json(插件专属,与 VS Code 互不干扰);无配置时下拉提示且按钮禁用。
|
|
3474
|
+
const dapPhaseTextOf = () => ({
|
|
3475
|
+
idle: '', starting: '启动中…', waiting: '等待目标…', running: '运行中', paused: '已暂停', terminated: '已结束',
|
|
3476
|
+
}[dapSnap.phase] || '')
|
|
3477
|
+
const dapActive = dapSnap.phase !== 'idle' && dapSnap.phase !== 'terminated'
|
|
3478
|
+
const dapPaused = dapSnap.phase === 'paused'
|
|
3479
|
+
const dapBtn = (label, title, disabled, onClick) => React.createElement('button', {
|
|
3480
|
+
key: title, className: 'edrv-dapbar-btn', title, disabled: disabled === true, onClick,
|
|
3481
|
+
}, label)
|
|
3482
|
+
const dapBarVisible = dapActive || Boolean(dapSnap.candidates)
|
|
3483
|
+
const dapBarStyle = dapBarPos
|
|
3484
|
+
? { left: dapBarPos.left, top: dapBarPos.top, right: 'auto' }
|
|
3485
|
+
: undefined
|
|
3486
|
+
const dapBar = dapBarVisible ? React.createElement('div', {
|
|
3487
|
+
ref: dapBarRef,
|
|
3488
|
+
className: 'edrv-dapbar' + (dapBarVisible ? ' on' : '') + (dapBarDragging ? ' dragging' : ''),
|
|
3489
|
+
style: dapBarStyle,
|
|
3490
|
+
},
|
|
3491
|
+
React.createElement('span', {
|
|
3492
|
+
className: 'edrv-dapbar-grip',
|
|
3493
|
+
role: 'button',
|
|
3494
|
+
tabIndex: 0,
|
|
3495
|
+
title: '拖动调试工具条',
|
|
3496
|
+
'aria-label': '拖动调试工具条',
|
|
3497
|
+
onPointerDown: startDapBarDrag,
|
|
3498
|
+
}, '⠿'),
|
|
3499
|
+
(dapSnap.candidates
|
|
3500
|
+
? [
|
|
3501
|
+
React.createElement('select', {
|
|
3502
|
+
key: 'pick', className: 'edrv-dapbar-cfg', title: '选择附加目标进程(processName 命中多个候选)',
|
|
3503
|
+
value: String(dapPickPid || dapSnap.candidates[0]?.pid || ''),
|
|
3504
|
+
onChange: (e) => setDapPickPid(Number(e.target.value)),
|
|
3505
|
+
},
|
|
3506
|
+
dapSnap.candidates.map((it) => React.createElement('option', { key: it.pid, value: String(it.pid) },
|
|
3507
|
+
it.pid + ' : ' + it.name + (it.title ? ' — ' + it.title.slice(0, 44) : '')))),
|
|
3508
|
+
dapBtn('✓', '附加到选中进程', false, () => dapStore.confirmPick(dapPickPid || dapSnap.candidates![0]?.pid || 0)),
|
|
3509
|
+
dapBtn('✕', '取消', false, () => dapStore.cancelPick()),
|
|
3510
|
+
]
|
|
3511
|
+
: React.createElement('select', {
|
|
3512
|
+
className: 'edrv-dapbar-cfg',
|
|
3513
|
+
title: '调试配置(读取工作区 .dsh/launch.json;适配器由扩展清单 contributes.debuggers 提供)',
|
|
3514
|
+
value: dapSnap.selectedConfig ?? '',
|
|
3515
|
+
onChange: (e) => dapStore.selectConfig(e.target.value),
|
|
3516
|
+
},
|
|
3517
|
+
dapSnap.configs.length
|
|
3518
|
+
? dapSnap.configs.map((c) => {
|
|
3519
|
+
// 旧 host 无 adapters 数据时视为全部可用(保持兼容);有数据则按可用性置灰并给原因
|
|
3520
|
+
const adapter = dapSnap.adapters.find((a) => a.type === c.type)
|
|
3521
|
+
const ready = dapSnap.adapters.length === 0 || adapter?.available === true
|
|
3522
|
+
const suffix = ready ? '' : '(' + (adapter?.reason || '未发现该类型适配器') + ')'
|
|
3523
|
+
return React.createElement('option', { key: c.name, value: c.name, disabled: !ready }, c.name + suffix)
|
|
3524
|
+
})
|
|
3525
|
+
: React.createElement('option', { value: '' }, '无可用调试配置'))),
|
|
3526
|
+
dapBtn(dapPaused ? '▶' : '▶', '启动 / 继续(F5)', !dapPaused && (!dapSnap.configs.some((c) => dapSnap.adapters.length === 0 || dapSnap.adapters.find((a) => a.type === c.type)?.available === true) || dapActive), () => dapStore.startOrContinue()),
|
|
3527
|
+
dapBtn('⏸', '暂停', !dapActive, () => { void dapStore.action('pause') }),
|
|
3528
|
+
dapBtn('⤵', '单步跳过(F10)', !dapPaused, () => { void dapStore.action('next') }),
|
|
3529
|
+
dapBtn('↓', '单步步入(F11)', !dapPaused, () => { void dapStore.action('stepIn') }),
|
|
3530
|
+
dapBtn('↑', '单步步出(Shift+F11)', !dapPaused, () => { void dapStore.action('stepOut') }),
|
|
3531
|
+
dapBtn('⏹', '停止(Shift+F5)', !dapActive, () => { void dapStore.stop() }),
|
|
3532
|
+
(dapPhaseTextOf()
|
|
3533
|
+
? React.createElement('span', { className: 'edrv-dapbar-phase' + (dapPaused ? ' paused' : '') },
|
|
3534
|
+
dapPhaseTextOf() + (dapSnap.pid ? ' · pid ' + dapSnap.pid : ''))
|
|
3535
|
+
: null),
|
|
3536
|
+
(dapSnap.error
|
|
3537
|
+
? React.createElement('span', { className: 'edrv-dapbar-err', title: dapSnap.error }, '⚠ ' + dapSnap.error.slice(0, 40))
|
|
3538
|
+
: null)) : null
|
|
3539
|
+
|
|
3540
|
+
/**
|
|
3541
|
+
* 「添加配置」按钮点击:定位 configurations 数组插入点(数组开头行首),
|
|
3542
|
+
* 显式拉起 Monaco 补全下拉选择调试配置片段插入。
|
|
3543
|
+
* @author ddj 2026年09月22号
|
|
3544
|
+
*/
|
|
3545
|
+
const addLaunchConfig = async () => {
|
|
3546
|
+
const ed = editorRef.current
|
|
3547
|
+
if (!ed) { setStatus('无活动编辑器'); return }
|
|
3548
|
+
const path = modelPathOf(ed)
|
|
3549
|
+
if (!path || !LAUNCH_JSON_RE.test(String(path).replace(/\\/g, '/'))) {
|
|
3550
|
+
setStatus('请先打开 .dsh/launch.json')
|
|
3551
|
+
return
|
|
3552
|
+
}
|
|
3553
|
+
const model = ed.getModel && ed.getModel()
|
|
3554
|
+
const pos = model ? findArrayPos(model.getValue()) : null
|
|
3555
|
+
if (!pos) { setStatus('未找到 configurations 数组'); return }
|
|
3556
|
+
// 预拉片段并预检可用条数:全不可用时不弹空 suggest(否则按钮点了无反馈)
|
|
3557
|
+
const snippets = await prefetchSnippets().catch(() => [])
|
|
3558
|
+
if (!snippets.some((snip) => snip.available !== false)) {
|
|
3559
|
+
setStatus('无可用适配器的配置模板(适配器入口缺失或未发现该类型适配器)')
|
|
3560
|
+
return
|
|
3561
|
+
}
|
|
3562
|
+
ed.focus()
|
|
3563
|
+
ed.setPosition({ lineNumber: pos.line, column: pos.column })
|
|
3564
|
+
if (typeof ed.revealPositionInCenterIfOutsideViewport === 'function') {
|
|
3565
|
+
ed.revealPositionInCenterIfOutsideViewport({ lineNumber: pos.line, column: pos.column })
|
|
3566
|
+
}
|
|
3567
|
+
ed.trigger('edrv-addcfg', 'editor.action.triggerSuggest', null)
|
|
3568
|
+
setStatus('选择要插入的调试配置')
|
|
3071
3569
|
}
|
|
3072
3570
|
|
|
3571
|
+
// 「添加配置」按钮(VSCode 同款形态,编辑区右下角):仅插件专属 launch.json 打开且非差异审阅态显示
|
|
3572
|
+
const addCfgBtn = (active && !svnDiff && LAUNCH_JSON_RE.test(String(active).replace(/\\/g, '/')))
|
|
3573
|
+
? React.createElement('button', {
|
|
3574
|
+
className: 'edrv-addcfg',
|
|
3575
|
+
title: '添加调试配置(插入到 configurations 数组开头)',
|
|
3576
|
+
onClick: addLaunchConfig,
|
|
3577
|
+
}, '添加配置…')
|
|
3578
|
+
: null
|
|
3579
|
+
|
|
3073
3580
|
// 主编辑列(侧边栏右侧):pathBar + tabRow + 编辑/差异区(底部整条留给 DSH 对话输入栏)
|
|
3074
3581
|
const editorArea = React.createElement('div', { className: 'edrv-editor-area' },
|
|
3582
|
+
dapBar,
|
|
3583
|
+
addCfgBtn,
|
|
3075
3584
|
body,
|
|
3076
3585
|
hoverEl,
|
|
3077
3586
|
overlay)
|
|
@@ -3216,31 +3725,124 @@ export function EditorView(props) {
|
|
|
3216
3725
|
setSvnLog({ ...svnLog, limit: SVN_LOG_SHOW_ALL_LIMIT })
|
|
3217
3726
|
ensureSvnLog(sessionId, scope, svnLog.target || '', SVN_LOG_SHOW_ALL_LIMIT, true, svnLogOptsOf(svnLog))
|
|
3218
3727
|
},
|
|
3219
|
-
onOpenDiff: (relPath, revision) => { setSvnLog(null); runSvnDiffRev(relPath, revision) },
|
|
3220
|
-
onComparePair: (revA, revB) => { setSvnLog(null); runSvnDiffPair(svnLog.target || '', revA, revB) },
|
|
3221
|
-
onCompareWorking: (revision) => { setSvnLog(null); runSvnDiffWorking(svnLog.target || '', revision) },
|
|
3728
|
+
onOpenDiff: (relPath, revision) => { setSvnLog(null); runSvnDiffRev(relPath, revision, svnLog.target || '') },
|
|
3729
|
+
onComparePair: (revA, revB) => { setSvnLog(null); runSvnDiffPair(svnLog.target || '', revA, revB, svnLog.target || '') },
|
|
3730
|
+
onCompareWorking: (revision) => { setSvnLog(null); runSvnDiffWorking(svnLog.target || '', revision, svnLog.target || '') },
|
|
3222
3731
|
onOpenFile: (relPath) => { setSvnLog(null); openFile(relPath, false) },
|
|
3223
3732
|
onClose: () => { setSvnLog(null); setSvnLogError('') },
|
|
3224
3733
|
})
|
|
3225
3734
|
: null
|
|
3735
|
+
// W2-1 补丁对话框 / W2-2 目录对比对话框:portal 到 body,与日志弹窗同一挂载方式
|
|
3736
|
+
const patchDialogEl = patchDialog
|
|
3737
|
+
? React.createElement(SvnPatchDialog, {
|
|
3738
|
+
key: 'edrv-svn-patch',
|
|
3739
|
+
path: patchDialog.path,
|
|
3740
|
+
text: patchDialog.text,
|
|
3741
|
+
truncated: patchDialog.truncated === true,
|
|
3742
|
+
binary: patchDialog.binary === true,
|
|
3743
|
+
onNote: (message) => setStatus(message),
|
|
3744
|
+
onClose: () => setPatchDialog(null),
|
|
3745
|
+
})
|
|
3746
|
+
: null
|
|
3747
|
+
const sumDialogEl = sumDialog
|
|
3748
|
+
? React.createElement(SvnSumDialog, {
|
|
3749
|
+
key: 'edrv-svn-sum',
|
|
3750
|
+
sessionId,
|
|
3751
|
+
path: sumDialog.path,
|
|
3752
|
+
onNote: (message) => setStatus(message),
|
|
3753
|
+
onClose: () => setSumDialog(null),
|
|
3754
|
+
})
|
|
3755
|
+
: null
|
|
3226
3756
|
// 诊断日志弹窗:portal 到 body,与命令栏/SVN 日志弹窗同一挂载方式
|
|
3227
3757
|
const dlogEl = dlogOpen
|
|
3228
3758
|
? React.createElement(LogDialog, { key: 'edrv-dlog', sessionId, onClose: () => setDlogOpen(false) })
|
|
3229
3759
|
: null
|
|
3760
|
+
// 断点右键菜单(逐条对齐 CodeBuddy:4 项添加类 / 删除·编辑·启停 / 记录点名词 / 暂停追加运行到行)
|
|
3761
|
+
const runBpMenuAction = (action) => {
|
|
3762
|
+
const scope = dapScopeRef.current
|
|
3763
|
+
const cwd = dapCwdRef.current
|
|
3764
|
+
const menu = dapBpMenu
|
|
3765
|
+
if (!menu) return
|
|
3766
|
+
const { path, line } = menu
|
|
3767
|
+
if (action === 'add') { dapStore.toggleAt(scope, cwd, path, line); return }
|
|
3768
|
+
if (action === 'delete') { if (menu.hasBp) dapStore.toggleAt(scope, cwd, path, line); return }
|
|
3769
|
+
if (action === 'disable' || action === 'enable') { dapStore.setBpEnabled(scope, cwd, path, line, action === 'enable'); return }
|
|
3770
|
+
if (action === 'edit') { openBpEditor(path, line); return }
|
|
3771
|
+
if (action === 'run-to-line') { dapStore.runTo(cwd, path, line); return }
|
|
3772
|
+
if (action === 'add-triggered') { setStatus('当前 Lua 调试器不支持触发的断点'); return }
|
|
3773
|
+
// 添加条件断点 / 添加记录点:先建点,再开浮层并选中对应模式(0 表达式 / 2 日志消息)
|
|
3774
|
+
dapStore.toggleAt(scope, cwd, path, line)
|
|
3775
|
+
openBpEditor(path, line, action === 'add-logpoint' ? 2 : 0)
|
|
3776
|
+
}
|
|
3777
|
+
const openBpEditor = (path, line, mode) => {
|
|
3778
|
+
const entry = (dapStore.bpMapOf(dapScopeRef.current)[path] ?? []).find((it) => it.line === line)
|
|
3779
|
+
if (!entry) return
|
|
3780
|
+
const ed = editorRef.current
|
|
3781
|
+
if (!ed?.changeViewZones) return
|
|
3782
|
+
// 行内浮层(View Zone,整编辑区宽 + 推开内容,对齐 VS Code breakpointWidget):同一时刻只挂一个
|
|
3783
|
+
closeBpWidget()
|
|
3784
|
+
bpWidgetRef.current = { ...createBpWidget(ed, {
|
|
3785
|
+
line,
|
|
3786
|
+
entry,
|
|
3787
|
+
mode,
|
|
3788
|
+
onSave: (fields) => {
|
|
3789
|
+
dapStore.applyBpFields(dapScopeRef.current, dapCwdRef.current, path, line, fields)
|
|
3790
|
+
setStatus('断点已更新')
|
|
3791
|
+
closeBpWidget()
|
|
3792
|
+
},
|
|
3793
|
+
onClose: () => closeBpWidget(),
|
|
3794
|
+
}), path, line }
|
|
3795
|
+
}
|
|
3796
|
+
const closeBpWidget = () => {
|
|
3797
|
+
const held = bpWidgetRef.current
|
|
3798
|
+
if (!held) return
|
|
3799
|
+
bpWidgetRef.current = null
|
|
3800
|
+
try { held.close() } catch { /* 编辑器可能已销毁 */ }
|
|
3801
|
+
}
|
|
3802
|
+
const dapBpMenuEl = dapBpMenu
|
|
3803
|
+
? React.createElement(ContextMenu, {
|
|
3804
|
+
x: dapBpMenu.x,
|
|
3805
|
+
y: dapBpMenu.y,
|
|
3806
|
+
onClose: () => setDapBpMenu(null),
|
|
3807
|
+
entries: buildBpMenu({
|
|
3808
|
+
hasBreakpoint: dapBpMenu.hasBp,
|
|
3809
|
+
enabled: dapBpMenu.enabled,
|
|
3810
|
+
isLogpoint: dapBpMenu.isLogpoint,
|
|
3811
|
+
paused: dapSnap.phase === 'paused',
|
|
3812
|
+
canRunTo: true,
|
|
3813
|
+
canTriggered: false,
|
|
3814
|
+
}).map((item) => ({
|
|
3815
|
+
id: 'dap-bp-' + item.id,
|
|
3816
|
+
label: item.label,
|
|
3817
|
+
hint: item.hint,
|
|
3818
|
+
separator: item.separator,
|
|
3819
|
+
onClick: () => runBpMenuAction(item.id),
|
|
3820
|
+
})),
|
|
3821
|
+
})
|
|
3822
|
+
: null
|
|
3823
|
+
const dapBpEditEl = null
|
|
3230
3824
|
const rootEl = layout === 'side'
|
|
3231
3825
|
? React.createElement('div', { ref: viewRootRef, 'data-edrv-view': '1', 'data-edrv-layout': 'side', className: 'edrv-view-side', style: Object.assign({}, baseStyle, { height: '100%' }) },
|
|
3232
3826
|
editorRow,
|
|
3233
3827
|
tabMenuEl,
|
|
3234
3828
|
paletteEl,
|
|
3235
3829
|
svnLogEl,
|
|
3830
|
+
patchDialogEl,
|
|
3831
|
+
sumDialogEl,
|
|
3236
3832
|
dlogEl,
|
|
3237
|
-
snippetPickerEl
|
|
3833
|
+
snippetPickerEl,
|
|
3834
|
+
dapBpMenuEl,
|
|
3835
|
+
dapBpEditEl)
|
|
3238
3836
|
: React.createElement('div', { ref: viewRootRef, 'data-edrv-view': '1', style: Object.assign({}, baseStyle, { height: 'var(--edrv-editor-height, 100%)', maxHeight: 'var(--edrv-editor-height, 100%)' }) },
|
|
3239
3837
|
editorRow,
|
|
3240
3838
|
tabMenuEl,
|
|
3241
3839
|
paletteEl,
|
|
3242
3840
|
svnLogEl,
|
|
3841
|
+
patchDialogEl,
|
|
3842
|
+
sumDialogEl,
|
|
3243
3843
|
dlogEl,
|
|
3244
|
-
snippetPickerEl
|
|
3844
|
+
snippetPickerEl,
|
|
3845
|
+
dapBpMenuEl,
|
|
3846
|
+
dapBpEditEl)
|
|
3245
3847
|
return rootEl
|
|
3246
3848
|
}
|