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/README.md +5 -3
- package/lib/client.js +52 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +4 -3
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/sidebar/panels/SearchPanel.ts +58 -1
- package/src/rpc.ts +1 -0
- package/src/search/content.ts +6 -2
- package/src/shared/rpc.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.15.
|
|
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
package/src/search/content.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 }
|