dsh-vscode-mode 0.15.2 → 0.15.3

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.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 DSH 0.1.5+ 官方右侧 Sidebar 与对话同屏)+ 命令栏(Ctrl+Shift+P)与可扩展指令系统 + VS Code 兼容代码片段(.code-snippets 全局/项目,IntelliSense 展开)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ SVN 集成(侧栏变更面板/日志弹窗/并排差异)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车,界面状态(页签/光标/侧栏/编辑 Tab/导航历史)镜像到 DSH home 跨重启恢复",
5
5
  "icon": "assets/icon.svg",
6
6
  "keywords": [
@@ -15,6 +15,8 @@ import { workspaceScopeOf } from '../../state/scopeStore.js'
15
15
  import { takeSearchSeed, takeSearchScope } from '../../searchSeed.js'
16
16
 
17
17
  const DEBOUNCE_MS = 250
18
+ /** 文件变更后的重搜去抖:合并一次写盘的 refresh+file-changed 双事件与批量保存。 */
19
+ const REFRESH_DEBOUNCE_MS = 300
18
20
  const INCLUDE_PLACEHOLDER = '例如 *.ts, src/**/include'
19
21
 
20
22
  /**
@@ -56,6 +58,30 @@ export function savedSearchOf(key: string) {
56
58
  }
57
59
  }
58
60
 
61
+ /**
62
+ * 判断文件变化通知是否属于本面板作用域。
63
+ * @author ddj 2026年10月14号
64
+ * @param detail 事件 detail(scope 可缺省)
65
+ * @param scope 本面板作用域
66
+ * @returns 是否触发重搜
67
+ */
68
+ export function noticeInScope(detail: any, scope: string): boolean {
69
+ const changed = detail?.scope
70
+ return !changed || changed === scope
71
+ }
72
+
73
+ /**
74
+ * 判断文件变化通知是否可能改变内容搜索命中。
75
+ * 文件增删改名(structure)会增删命中,一并重搜;SVN 状态刷新(svn)不代表内容变化,跳过以免无谓全库扫描。
76
+ * @author ddj 2026年10月14号
77
+ * @param detail 事件 detail(kind 可缺省)
78
+ * @returns 是否触发重搜
79
+ */
80
+ export function noticeTouchesContent(detail: any): boolean {
81
+ const kind = detail?.kind
82
+ return !kind || kind === 'text' || kind === 'structure'
83
+ }
84
+
59
85
  /**
60
86
  * 命中行文本三段切分(before/命中/after,列为 1-based UTF-16)。
61
87
  * @param text 行文本
@@ -103,6 +129,8 @@ export function SearchPanel(props) {
103
129
  queryRef.current = query
104
130
  const activePathRef = React.useRef(activePath)
105
131
  activePathRef.current = activePath
132
+ // 文件变化后的重搜定时器(与输入防抖分开:两者互不取消对方)
133
+ const refreshTimerRef = React.useRef(null)
106
134
  // 请求参数 ref:防抖/立即重搜闭包统一读最新值,避免陈旧选项
107
135
  const requestRef = React.useRef({ matchCase, wholeWord, regex, include: [], exclude: [] })
108
136
  requestRef.current = {
@@ -152,9 +180,11 @@ export function SearchPanel(props) {
152
180
 
153
181
  /**
154
182
  * 执行内容搜索(请求参数从 ref 读最新值)。
183
+ * @author ddj 2026年10月14号
155
184
  * @param text 搜索词
185
+ * @param force 是否强制绕过 host 短期缓存(文件变更后的重搜必须读最新磁盘内容)
156
186
  */
157
- const runSearch = (text) => {
187
+ const runSearch = (text, force) => {
158
188
  const s = ++seqRef.current
159
189
  const clean = String(text ?? '').trim()
160
190
  if (clean.length < 2) { setMatches(null); setStatus('idle'); return }
@@ -174,6 +204,8 @@ export function SearchPanel(props) {
174
204
  sessionId, query: clean,
175
205
  matchCase: req.matchCase, wholeWord: req.wholeWord, regex: req.regex,
176
206
  include: req.include, exclude: req.exclude,
207
+ // 仅重搜路径带上 force:普通搜索的请求载荷与修复前保持完全一致。
208
+ ...(force === true ? { force: true } : {}),
177
209
  }).then((res) => {
178
210
  if (s !== seqRef.current) return
179
211
  if (res && res.ok) {
@@ -194,6 +226,10 @@ export function SearchPanel(props) {
194
226
  })
195
227
  }
196
228
 
229
+ // 事件监听闭包经 ref 取最新搜索实现与最新查询词(事件注册只做一次)
230
+ const runSearchRef = React.useRef(runSearch)
231
+ runSearchRef.current = runSearch
232
+
197
233
  /**
198
234
  * 输入变化:防抖触发搜索。
199
235
  * @param value 新输入
@@ -303,6 +339,27 @@ export function SearchPanel(props) {
303
339
  return () => window.removeEventListener('edrv:search-focus', onFocus)
304
340
  }, [])
305
341
 
342
+ // 文件内容变化(保存/外部写盘/文件增删改名)→ 去抖强制重搜,避免面板残留已失效的旧命中。
343
+ // 两种事件都监听:编辑器内保存只发 edrv:refresh,外部写盘只发 edrv:file-changed。
344
+ React.useEffect(() => {
345
+ /** @private @author ddj 2026年10月14号 @description 合并同一次写盘的多条通知后强制重搜 */
346
+ const onChanged = (event) => {
347
+ const detail = event?.detail ?? {}
348
+ if (!noticeInScope(detail, scope)) return
349
+ if (!noticeTouchesContent(detail)) return
350
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current)
351
+ refreshTimerRef.current = setTimeout(() => runSearchRef.current(queryRef.current, true), REFRESH_DEBOUNCE_MS)
352
+ }
353
+ window.addEventListener('edrv:file-changed', onChanged)
354
+ window.addEventListener('edrv:refresh', onChanged)
355
+ return () => {
356
+ window.removeEventListener('edrv:file-changed', onChanged)
357
+ window.removeEventListener('edrv:refresh', onChanged)
358
+ if (refreshTimerRef.current) { clearTimeout(refreshTimerRef.current); refreshTimerRef.current = null }
359
+ }
360
+ // eslint-disable-next-line react-hooks/exhaustive-deps
361
+ }, [])
362
+
306
363
  // 一次意图对应一次提交后选区;无需定时回调,卸载后不会抢焦点。
307
364
  React.useLayoutEffect(() => {
308
365
  if (!focusTick) return
package/src/rpc.ts CHANGED
@@ -1034,6 +1034,7 @@ export function buildHandlers(
1034
1034
  maxResults: args.maxResults,
1035
1035
  include: args.include,
1036
1036
  exclude: args.exclude,
1037
+ force: args.force,
1037
1038
  })
1038
1039
  return { ok: true, ...result }
1039
1040
  } catch (error) {
@@ -45,6 +45,8 @@ export interface ContentSearchRequest {
45
45
  include?: string[]
46
46
  /** 排除 glob(这些文件不参与搜索)。 */
47
47
  exclude?: string[]
48
+ /** 强制绕过短期缓存:文件变更后的重搜必须看到最新磁盘内容,每次都跑真实 rg。 */
49
+ force?: boolean
48
50
  }
49
51
 
50
52
  type ContentSearchResponse = { matches: ContentMatch[]; truncated: boolean; warning?: string }
@@ -257,7 +259,8 @@ export class ContentSearcher {
257
259
  const includeKey = (request.include ?? []).join(',')
258
260
  const excludeKey = (request.exclude ?? []).join(',')
259
261
  const key = [rootKey, query, request.matchCase ? 'mc' : '', request.wholeWord ? 'ww' : '', request.regex ? 'rx' : '', includeKey, excludeKey, PROVIDER_VERSION].join('|')
260
- const cached = this.cache.get(key)
262
+ // force(文件变更后的重搜)必须看到最新磁盘内容:跳过缓存读,见下方跳过缓存写。
263
+ const cached = request.force ? undefined : this.cache.get(key)
261
264
  if (cached) return { matches: cached.matches, truncated: cached.truncated, warning: cached.warning }
262
265
  this.inflight.get(root)?.abort()
263
266
  const controller = new AbortController()
@@ -278,7 +281,8 @@ export class ContentSearcher {
278
281
  root,
279
282
  })
280
283
  if (controller.signal.aborted) return { matches: [], truncated: false }
281
- this.cache.set(key, result)
284
+ // force 结果不回填缓存:本次是「读最新」,不代表该键的稳定内容。
285
+ if (!request.force) this.cache.set(key, result)
282
286
  return { matches: result.matches, truncated: result.truncated, warning: result.warning }
283
287
  } catch (error) {
284
288
  if (controller.signal.aborted) return { matches: [], truncated: false }
package/src/shared/rpc.ts CHANGED
@@ -377,7 +377,7 @@ export interface RpcRequestMap {
377
377
  'edrv.dlog.clear': { sessionId?: string; file?: string }
378
378
  'edrv.dlog.reveal': { sessionId?: string }
379
379
  'edrv.searchFiles': { sessionId?: string; query: string }
380
- 'edrv.searchContent': { sessionId?: string; query: string; matchCase?: boolean; wholeWord?: boolean; regex?: boolean; maxResults?: number; include?: string[]; exclude?: string[] }
380
+ 'edrv.searchContent': { sessionId?: string; query: string; matchCase?: boolean; wholeWord?: boolean; regex?: boolean; maxResults?: number; include?: string[]; exclude?: string[]; force?: boolean }
381
381
  'edrv.listDir': { sessionId?: string; path: string; force?: boolean }
382
382
  'edrv.revealInExplorer': { sessionId?: string; path: string }
383
383
  'edrv.fsCreateFile': { sessionId?: string; path: string }