dsh-vscode-mode 0.9.0 → 0.11.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.
@@ -56,6 +56,7 @@ import { statusOfAdd } from '../addToConversation.js'
56
56
  import { setSearchScope, setSearchSeed } from '../searchSeed.js'
57
57
  import { CACHE_KEY } from '../paths.js'
58
58
  import { runGoToDefinition, runFindReferences, hideReferencesOverlay } from '../monaco/lsp/providers.js'
59
+ import { isLuaTypeSlot } from '../monaco/lsp/keywords.js'
59
60
  import { bindLspUnderline } from '../monaco/lsp/underline.js'
60
61
  import { onLspProgress, refreshStatus, setSession as setLspSession } from '../monaco/lsp/index.js'
61
62
  import { setupAiInline, trackAiEditor, aiInlineEnabled } from '../ai/inlineProvider.js'
@@ -69,7 +70,7 @@ import {
69
70
  } from '../tabActions.js'
70
71
  import { getMaxOpenEditors } from '../editorLimit.js'
71
72
  import { buildTabMenu } from '../tabMenu.js'
72
- import { ensureSvnChanges, ensureSvnStatus, getSvnChanges, getSvnStatus, refreshSvnChanges, svnAdd, svnChangeMapOf, svnDiffBase, svnDiffLocalPair, svnEditorActions, svnRevert, svnTortoise, svnUpdate } from '../svnStatus.js'
73
+ import { ensureSvnChanges, ensureSvnStatus, getSvnChanges, getSvnStatus, refreshSvnChanges, svnAdd, svnChangeMapOf, svnDiffBase, svnDiffLocalPair, svnEditorActions, svnRevert, svnTortoise } from '../svnStatus.js'
73
74
  import { ensureSvnLog, getSvnLog, refreshSvnLog, svnDiffPair, svnDiffRev, svnDiffWorking, svnLogKeyOf, svnLogLoadedLimit, svnLogTruncated, svnWcRev, SVN_LOG_SHOW_ALL_LIMIT } from '../svnLog.js'
74
75
  import { runSvnAction } from '../svnActions.js'
75
76
  import { dshTrace } from '../svnStore.js'
@@ -82,6 +83,9 @@ import { promptName } from './PromptDialog.js'
82
83
  import { openWithDialog } from '../openWithDialog.js'
83
84
  import { copyText as copyClipText } from '../copyText.js'
84
85
  import { SvnLogDialog } from './SvnLogDialog.js'
86
+ import { SvnUpdateDialog } from './SvnUpdateDialog.js'
87
+ import { sameUpdateOwner, updateDone } from './svnUpdateModel.js'
88
+ import { IconRefreshOutline16 } from './icons.js'
85
89
  import { SvnPatchDialog } from './SvnPatchDialog.js'
86
90
  import { SvnSumDialog } from './SvnSumDialog.js'
87
91
  import { LogDialog } from './LogDialog.js'
@@ -95,6 +99,23 @@ import { clampToolbarPosition, loadToolbarPosition, saveToolbarPosition } from '
95
99
  import { editorModelPathOf } from '../dap/modelPath.js'
96
100
  import { dapTrace } from '../dap/trace.js'
97
101
 
102
+ /**
103
+ * 只在 Lua 注解类型槽开启注释自动建议;跨文件/离开类型槽立即恢复默认。
104
+ * @author ddj 2026年09月24号
105
+ * @param ed 当前 Monaco 编辑器
106
+ * @param monaco Monaco API(读取 quickSuggestions 枚举)
107
+ */
108
+ function syncTypeSuggest(ed, monaco) {
109
+ const model = ed.getModel()
110
+ const position = ed.getPosition()
111
+ const typeSlot = model?.getLanguageId() === 'lua' && position
112
+ && isLuaTypeSlot(model, position, model.getWordUntilPosition(position))
113
+ const options = ed.getOption(monaco.editor.EditorOption.quickSuggestions)
114
+ const comments = typeSlot ? 'on' : 'off'
115
+ if (options.comments === comments) return
116
+ ed.updateOptions({ quickSuggestions: { ...options, comments } })
117
+ }
118
+
98
119
  /** 日志弹窗每次加载条数(「加载更多」按此步长递增;上限由 host 的 SVN_LOG_SHOW_ALL_CAP 约束)。 */
99
120
  const SVN_LOG_PAGE = 100
100
121
 
@@ -252,7 +273,30 @@ export function EditorView(props) {
252
273
  const [svnLogError, setSvnLogError] = React.useState('') // 日志错误文案
253
274
  const [svnLogWcRev, setSvnLogWcRev] = React.useState(null) // P1-9:文件目标的工作副本版号(null = 不加粗)
254
275
  const [dlogOpen, setDlogOpen] = React.useState(false) // 诊断日志弹窗(自取数据,null 语义无需状态层)
255
- const [svnUpdateResult, setSvnUpdateResult] = React.useState(null) // update 条目级结果条(null=不显示)
276
+ const [svnUpdateDialog, setSvnUpdateDialog] = React.useState(null) // {path,sessionId,scope,resumeJobId?};窗口内自取增量
277
+ const [svnUpdateReturn, setSvnUpdateReturn] = React.useState(null) // 从日志/差异返回时恢复同一 job
278
+ const [recentUpdate, setRecentUpdate] = React.useState(null) // {jobId,path,sessionId,scope};只读恢复入口
279
+ const [updateOpenWith, setUpdateOpenWith] = React.useState(false) // 二级弹窗 Esc 不联动关闭父窗口
280
+ const svnUpdateDoneRef = React.useRef(new Set()) // job 终态刷新一次,往返日志/差异不重复刷新
281
+ // @author ddj 2026年09月24号:切换作用域只关闭窗口,不取消旧工作副本中已启动的任务
282
+ React.useEffect(() => {
283
+ setSvnUpdateDialog(null)
284
+ setSvnUpdateReturn(null)
285
+ setRecentUpdate(null)
286
+ setUpdateOpenWith(false)
287
+ }, [sessionId, scope])
288
+ // @author ddj 2026年09月24号:只读恢复原工作区的活动更新,不调用 updateStart
289
+ React.useEffect(() => {
290
+ if (!sessionId || svnStatus?.managed !== true) return undefined
291
+ let alive = true
292
+ void rpc('svn.updateActive', { sessionId }).then((outcome) => {
293
+ if (!alive || !outcome.ok || !outcome.job) return
294
+ const recovered = { path: outcome.job.target, sessionId, scope, resumeJobId: outcome.job.jobId }
295
+ if (updateDone(outcome.job.phase)) setRecentUpdate(recovered)
296
+ else setSvnUpdateDialog((current) => current ?? recovered)
297
+ }).catch(() => {})
298
+ return () => { alive = false }
299
+ }, [sessionId, scope, svnStatus?.managed])
256
300
  const [sidebarOn, setSidebarOn] = React.useState(layout !== 'side') // 侧边栏显隐(侧栏形态默认收起)
257
301
  const [sidebarW, setSidebarW] = React.useState(() => getSidebarMinWidth()) // 侧边栏宽度(初值 = 最小宽度,默认 300)
258
302
  const [activePanel, setActivePanel] = React.useState('explorer') // 激活面板 id
@@ -1727,6 +1771,7 @@ export function EditorView(props) {
1727
1771
  sessionId,
1728
1772
  scope,
1729
1773
  path: def.id === 'update' ? (relPath ?? '') : relPath,
1774
+ openSvnUpdate: (p) => runSvnUpdate(p),
1730
1775
  notify: (message) => setStatus(message),
1731
1776
  openSvnDiff: (p) => runSvnDiffBase(p),
1732
1777
  openSvnLocalPair: runSvnDiffLocalPair,
@@ -1904,6 +1949,7 @@ export function EditorView(props) {
1904
1949
  const ed = editorRef.current
1905
1950
  const model = getModel(active, content)
1906
1951
  if (ed.getModel() !== model) ed.setModel(model)
1952
+ syncTypeSuggest(ed, monaco)
1907
1953
  restoreViewState(active)
1908
1954
  setLoadStage((prev) => ({ progress: Math.max(96, prev.progress), message: '创建编辑器视图…' }))
1909
1955
  }, [monaco, active, content, contentReady, mdPreviewing])
@@ -1997,9 +2043,11 @@ export function EditorView(props) {
1997
2043
  // 保存改由窗口级快捷键监听执行(键位可配置;见上方 edrv.save 监听)
1998
2044
  ed.onDidChangeModelContent(() => {
1999
2045
  if (!ed.getModel() || programmaticRef.current) return
2046
+ syncTypeSuggest(ed, m)
2000
2047
  onEditRef.current?.()
2001
2048
  })
2002
2049
  ed.onDidChangeCursorPosition((e) => {
2050
+ syncTypeSuggest(ed, m)
2003
2051
  setCursor('Ln ' + e.position.lineNumber + ', Col ' + e.position.column)
2004
2052
  // 整行移动(↓↑)刚定位时保留期望列;其余光标移动(点击/打字/方向键)清空期望列
2005
2053
  if (rowNavMoveRef.current) rowNavMoveRef.current = false
@@ -2740,17 +2788,15 @@ export function EditorView(props) {
2740
2788
 
2741
2789
  // 供 Monaco 原生右键菜单 addAction 读取的最新动作闭包(空依赖回调不随渲染重建)
2742
2790
  /**
2743
- * SVN 更新(CLI,全平台):页签菜单/命令面板/编辑区右键共用;path 缺省 = 工作区根。
2744
- * P3:结果含条目级明细,成功后展示可关闭的结果条(冲突红字高亮)。
2791
+ * 四入口共用的更新窗口:仅显式点击触发,不在背景静默更新。
2792
+ * @author ddj 2026年09月24号
2793
+ * @param relPath 工作区相对目标;空串代表根
2745
2794
  */
2746
2795
  const runSvnUpdate = (relPath) => {
2747
- void svnUpdate(sessionId, relPath ?? '').then((o) => {
2748
- setStatus(o.message)
2749
- // 更新会改工作副本状态:成功后重查变更清单(徽标/面板跟随)
2750
- if (o.ok) refreshSvnChanges(sessionId, scope)
2751
- // P3:有条目级明细时展示结果条(无变化则清空,不留旧结果)
2752
- setSvnUpdateResult(o.ok ? { summary: o.message, entries: o.entries ?? [], conflicts: o.conflicts ?? [] } : null)
2753
- })
2796
+ closeCommandPalette()
2797
+ setSvnUpdateReturn(null)
2798
+ setRecentUpdate(null)
2799
+ setSvnUpdateDialog({ path: relPath ?? '', sessionId, scope })
2754
2800
  }
2755
2801
  /** 强制重查 SVN 变更清单(命令栏「SVN 刷新变更」)。 */
2756
2802
  const runSvnReload = () => {
@@ -2857,8 +2903,9 @@ export function EditorView(props) {
2857
2903
  * @param relPath 目标工作区相对路径
2858
2904
  * @param revision 左侧版本
2859
2905
  * @param logTarget 来自日志弹窗时的日志目标(不传 = 非日志入口)
2906
+ * @param onOpened 差异视图成功打开后触发(更新结果返回用)
2860
2907
  */
2861
- const runSvnDiffWorking = (relPath, revision, logTarget) => {
2908
+ const runSvnDiffWorking = (relPath, revision, logTarget, onOpened) => {
2862
2909
  if (!relPath) { setStatus('仅文件目标支持与工作副本比较'); return }
2863
2910
  setStatus('读取 r' + revision + ' ↔ 工作副本 差异…')
2864
2911
  void svnDiffWorking(sessionId, relPath, revision).then((outcome) => {
@@ -2877,6 +2924,7 @@ export function EditorView(props) {
2877
2924
  logTarget,
2878
2925
  })
2879
2926
  setStatus('已打开 r' + revision + ' ↔ 工作副本 差异')
2927
+ onOpened?.()
2880
2928
  })
2881
2929
  }
2882
2930
 
@@ -3032,6 +3080,7 @@ export function EditorView(props) {
3032
3080
  sessionId,
3033
3081
  scope,
3034
3082
  path,
3083
+ openSvnUpdate: (p) => runSvnUpdate(p),
3035
3084
  notify: (message) => setStatus(message),
3036
3085
  openSvnDiff: (p) => runSvnDiffBase(p),
3037
3086
  openSvnLocalPair: runSvnDiffLocalPair,
@@ -3239,6 +3288,12 @@ export function EditorView(props) {
3239
3288
  const pathBar = React.createElement('div', { className: 'edrv-pathbar', title: active || '' },
3240
3289
  React.createElement('span', { className: 'edrv-pb-name' }, active ? String(active).split(/[\\/]/).pop() : '未打开文件'),
3241
3290
  React.createElement('span', { className: 'edrv-pb-full' }, active || '使用右上搜索框 (' + (chordOf('edrv.quickOpen') ?? 'Ctrl+P') + ') 打开文件'),
3291
+ (recentUpdate && !svnUpdateDialog && sameUpdateOwner(recentUpdate, sessionId, scope)
3292
+ ? React.createElement('button', {
3293
+ className: 'edrv-pb-update', type: 'button', title: '查看最近 SVN 更新结果(不重新更新)',
3294
+ onClick: () => setSvnUpdateDialog(recentUpdate),
3295
+ }, React.createElement(IconRefreshOutline16, { size: 14 }), '最近更新')
3296
+ : null),
3242
3297
  // Markdown 预览/源码切换(需求 4):仅 .md 文件出现;命令栏与 Ctrl+Shift+V 为同一动作
3243
3298
  (isMdActive ? React.createElement('button', {
3244
3299
  className: 'edrv-pill edrv-pill-ghost edrv-pb-mdbtn',
@@ -3406,7 +3461,13 @@ export function EditorView(props) {
3406
3461
  leftLabel: svnDiff.leftLabel,
3407
3462
  rightLabel: svnDiff.rightLabel,
3408
3463
  onBack: svnDiff.logTarget !== undefined ? backToSvnLog : undefined,
3409
- onClose: () => setSvnDiff(null),
3464
+ onClose: () => {
3465
+ setSvnDiff(null)
3466
+ if (svnUpdateReturn?.from === 'diff') {
3467
+ setSvnUpdateDialog(svnUpdateReturn.dialog)
3468
+ setSvnUpdateReturn(null)
3469
+ }
3470
+ },
3410
3471
  })
3411
3472
  } else if (!monaco && !monacoErr) {
3412
3473
  body = loadingBody(loadStage.message, loadStage.progress)
@@ -3574,6 +3635,7 @@ export function EditorView(props) {
3574
3635
  svnChanges,
3575
3636
  svnChangeMap: svnChangeMapMemo,
3576
3637
  openSvnDiff: (p) => runSvnDiffBase(p),
3638
+ openSvnUpdate: (p) => runSvnUpdate(p),
3577
3639
  openSvnLocalPair: runSvnDiffLocalPair,
3578
3640
  refreshSvnChanges: () => refreshSvnChanges(sessionId, scope),
3579
3641
  openSvnLog: (p) => openSvnLog(p),
@@ -3726,49 +3788,11 @@ export function EditorView(props) {
3726
3788
  React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '复制安装命令', onClick: copyInstallCmd }, '复制命令'),
3727
3789
  React.createElement('button', { className: 'edrv-side-hint-close', title: '关闭提示', 'aria-label': '关闭提示', onClick: dismissHint }, '×'))
3728
3790
  : null
3729
- /**
3730
- * SVN 更新结果条(P3):逐条展示 update 的条目级状态,冲突红字高亮,可关闭。
3731
- * 只在本次 update 有条目明细时渲染(无明细/失败时不占用编辑区高度)。
3732
- * @author ddj 2026年09月16号
3733
- * @returns 结果条元素或 null
3734
- */
3735
- const svnResultBar = () => {
3736
- const result = svnUpdateResult
3737
- if (!result || !result.entries.length) return null
3738
- const conflicts = new Set(result.conflicts ?? [])
3739
- return React.createElement('div', { className: 'edrv-svnres' },
3740
- React.createElement('span', { className: 'edrv-svnres-head' }, 'SVN 更新'),
3741
- React.createElement('div', { className: 'edrv-svnres-list' },
3742
- result.entries.map((entry, idx) => React.createElement('span', {
3743
- key: entry.path + ':' + idx,
3744
- className: 'edrv-svnres-item' + (conflicts.has(entry.path) ? ' edrv-svnres-conflict' : ''),
3745
- title: entry.label + ' ' + entry.path,
3746
- }, entry.action + ' ' + entry.path))),
3747
- React.createElement('span', { style: { flex: 1 } }),
3748
- (conflicts.size
3749
- ? React.createElement('button', {
3750
- className: 'edrv-svn-act',
3751
- title: '在工作区中显示第一个冲突文件',
3752
- onClick: () => {
3753
- const first = [...conflicts][0]
3754
- const hit = result.entries.find((entry) => entry.path === first)
3755
- if (hit?.path) openFile(hit.path, false)
3756
- },
3757
- }, '查看冲突文件')
3758
- : null),
3759
- React.createElement('button', {
3760
- className: 'edrv-svn-act',
3761
- title: '关闭结果条',
3762
- onClick: () => setSvnUpdateResult(null),
3763
- }, '✕'))
3764
- }
3765
-
3766
3791
  const mainCol = React.createElement('div', { className: 'edrv-main-col', style: { flex: 1, minWidth: 0, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' } },
3767
3792
  pathBar,
3768
3793
  tabRow,
3769
3794
  sideHintEl,
3770
3795
  diskBanner(),
3771
- svnResultBar(),
3772
3796
  editorArea,
3773
3797
  statusBar)
3774
3798
  // 编辑器根节点按 composer 顶部边界动态限高,底部对话区域继续由 DSH 原生渲染。
@@ -3856,7 +3880,61 @@ export function EditorView(props) {
3856
3880
  onComparePair: (revA, revB) => { setSvnLog(null); runSvnDiffPair(svnLog.target || '', revA, revB, svnLog.target || '') },
3857
3881
  onCompareWorking: (revision) => { setSvnLog(null); runSvnDiffWorking(svnLog.target || '', revision, svnLog.target || '') },
3858
3882
  onOpenFile: (relPath) => { setSvnLog(null); openFile(relPath, false) },
3859
- onClose: () => { setSvnLog(null); setSvnLogError('') },
3883
+ onClose: () => {
3884
+ setSvnLog(null)
3885
+ setSvnLogError('')
3886
+ if (svnUpdateReturn?.from === 'log') {
3887
+ setSvnUpdateDialog(svnUpdateReturn.dialog)
3888
+ setSvnUpdateReturn(null)
3889
+ }
3890
+ },
3891
+ })
3892
+ : null
3893
+ /**
3894
+ * 更新终态只刷新一次目录树与变更缓存,切到日志再返回不重复执行。
3895
+ * @author ddj 2026年09月24号
3896
+ * @param result 已结束的更新任务
3897
+ */
3898
+ const onSvnUpdateDone = (result) => {
3899
+ if (svnUpdateDoneRef.current.has(result.jobId)) return
3900
+ svnUpdateDoneRef.current.add(result.jobId)
3901
+ const message = result.phase === 'completed' ? 'SVN 更新完成'
3902
+ : result.phase === 'cancelled' ? 'SVN 更新已取消(可能部分完成)' : 'SVN 更新失败:' + result.error
3903
+ setStatus(message)
3904
+ setRecentUpdate({ path: result.target, sessionId, scope, resumeJobId: result.jobId })
3905
+ refreshSvnChanges(sessionId, scope)
3906
+ emitRefresh()
3907
+ }
3908
+ const svnUpdateEl = sameUpdateOwner(svnUpdateDialog, sessionId, scope)
3909
+ ? React.createElement(SvnUpdateDialog, {
3910
+ key: 'edrv-svn-update:' + svnUpdateDialog.path + ':' + (svnUpdateDialog.resumeJobId || ''),
3911
+ sessionId, cwd, path: svnUpdateDialog.path, resumeJobId: svnUpdateDialog.resumeJobId,
3912
+ childOverlay: updateOpenWith,
3913
+ onDone: onSvnUpdateDone, onNote: (message) => setStatus(message),
3914
+ onClose: () => setSvnUpdateDialog(null),
3915
+ onShowLog: (target, jobId) => {
3916
+ setSvnUpdateReturn({ from: 'log', dialog: { ...svnUpdateDialog, resumeJobId: jobId } })
3917
+ setSvnUpdateDialog(null)
3918
+ openSvnLog(target)
3919
+ },
3920
+ onCompare: (path, revision, jobId) => runSvnDiffWorking(path, revision, undefined, () => {
3921
+ setSvnUpdateReturn({ from: 'diff', dialog: { ...svnUpdateDialog, resumeJobId: jobId } })
3922
+ setSvnUpdateDialog(null)
3923
+ }),
3924
+ onOpenFile: (path) => openFile(path, false),
3925
+ onOpenWith: (path) => {
3926
+ if (!props.fileOpeners) { setStatus('打开方式不可用'); return }
3927
+ setUpdateOpenWith(true)
3928
+ openWithDialog(props.fileOpeners, { sessionId, cwd: cwd ?? undefined }, path, (opener) => {
3929
+ setUpdateOpenWith(false)
3930
+ if (opener) setStatus('已用「' + opener.label + '」打开:' + baseNameOf(path))
3931
+ })
3932
+ },
3933
+ onOpenParent: (path) => {
3934
+ void revealPathInExplorer(sessionId, path).then((outcome) => {
3935
+ setStatus(outcome.ok ? '已打开父目录' : '打开父目录失败:' + (outcome.error ?? '未知错误'))
3936
+ })
3937
+ },
3860
3938
  })
3861
3939
  : null
3862
3940
  // W2-1 补丁对话框 / W2-2 目录对比对话框:portal 到 body,与日志弹窗同一挂载方式
@@ -3954,6 +4032,7 @@ export function EditorView(props) {
3954
4032
  tabMenuEl,
3955
4033
  paletteEl,
3956
4034
  svnLogEl,
4035
+ svnUpdateEl,
3957
4036
  patchDialogEl,
3958
4037
  sumDialogEl,
3959
4038
  dlogEl,
@@ -3965,6 +4044,7 @@ export function EditorView(props) {
3965
4044
  tabMenuEl,
3966
4045
  paletteEl,
3967
4046
  svnLogEl,
4047
+ svnUpdateEl,
3968
4048
  patchDialogEl,
3969
4049
  sumDialogEl,
3970
4050
  dlogEl,