dsh-vscode-mode 0.18.0 → 0.19.1
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 +8 -4
- package/lib/client.debug.js +934 -0
- package/lib/client.debug.js.map +1 -0
- package/lib/client.js +990 -1705
- package/lib/client.js.map +1 -1
- package/lib/client.rules.js +793 -0
- package/lib/client.rules.js.map +1 -0
- package/lib/index.js +236 -42
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/externalOpen.ts +7 -1
- package/src/client/index.ts +9 -0
- package/src/client/lazyPanel.ts +68 -0
- package/src/client/monaco/loader.ts +2 -0
- package/src/client/monaco/theme.ts +11 -0
- package/src/client/openReceipt.ts +4 -1
- package/src/client/requestId.ts +20 -0
- package/src/client/sidebar/PanelBoundary.ts +57 -0
- package/src/client/sidebar/SidebarView.ts +58 -21
- package/src/client/sidebar/panels/FileExplorer.ts +80 -27
- package/src/client/sidebar/panels/SvnPanel.ts +24 -8
- package/src/client/sidebar/panels/index.ts +3 -4
- package/src/client/sidebar/panels/treeWindow.ts +106 -0
- package/src/client/sidebar/panels/useExplorerReveal.ts +5 -2
- package/src/client/sidebar/registry.ts +4 -1
- package/src/client/state/uiStatePersist.ts +84 -23
- package/src/client/styles/editor.css +8 -0
- package/src/client/textPages.ts +135 -0
- package/src/client/ui/EditorView.ts +74 -16
- package/src/client/ui/LargeTextView.ts +94 -0
- package/src/rpc.ts +29 -7
- package/src/shared/rpc.ts +3 -1
- package/src/shared/snippets.ts +1 -1
- package/src/shared/textPages.ts +28 -0
- package/src/textPages.ts +149 -0
- package/src/treeIndex.ts +62 -37
package/src/treeIndex.ts
CHANGED
|
@@ -21,9 +21,9 @@ import { DirListError, listDirCheap } from './treeRead.js'
|
|
|
21
21
|
import { LEGACY_TREE_SIDECARS, treeCacheFile } from './paths.js'
|
|
22
22
|
|
|
23
23
|
// --region 常量
|
|
24
|
-
/**
|
|
24
|
+
/** 硬 TTL:距最近实列确认达到此时间即过期,命中不续期。 */
|
|
25
25
|
export const TREE_TTL_MS = 60_000
|
|
26
|
-
/**
|
|
26
|
+
/** 内存目录上限:超出按独立运行时 LRU 逐出,不改变确认时间。 */
|
|
27
27
|
export const MEM_MAX_DIRS = 2000
|
|
28
28
|
/** 落盘目录上限(按 ts 裁旧)。 */
|
|
29
29
|
export const PERSIST_MAX_DIRS = 400
|
|
@@ -65,6 +65,8 @@ interface IndexState {
|
|
|
65
65
|
cwd: string
|
|
66
66
|
data: TreeIndexData
|
|
67
67
|
stale: Set<string>
|
|
68
|
+
/** 插入顺序即访问顺序,仅运行时使用,不写入 v1 快照。 */
|
|
69
|
+
lru: Map<string, true>
|
|
68
70
|
dirty: boolean
|
|
69
71
|
persistTimer: ReturnType<typeof setTimeout> | null
|
|
70
72
|
healTimer: ReturnType<typeof setTimeout> | null
|
|
@@ -75,14 +77,6 @@ interface IndexState {
|
|
|
75
77
|
|
|
76
78
|
/** cwd → 索引状态(随会话销毁清理)。 */
|
|
77
79
|
const states = new Map<string, IndexState>()
|
|
78
|
-
/** 单调时钟:连续 put 的 ts 严格递增(同毫秒不并列,逐出/裁剪顺序可预期)。 */
|
|
79
|
-
let clock = 0
|
|
80
|
-
|
|
81
|
-
/** 取下一时间戳(不小于真实时间且严格递增)。 */
|
|
82
|
-
function nextTs(): number {
|
|
83
|
-
clock = Math.max(Date.now(), clock + 1)
|
|
84
|
-
return clock
|
|
85
|
-
}
|
|
86
80
|
|
|
87
81
|
// --region 纯函数(可单测)
|
|
88
82
|
/** 条目原始值 → 合法条目(非法返回 null)。 */
|
|
@@ -147,8 +141,9 @@ export function indexParse(text: string | null): TreeIndexData | null {
|
|
|
147
141
|
if (!e) continue
|
|
148
142
|
entries.push({ name: e.name, path: rel ? rel + '/' + e.name : e.name, type: e.type })
|
|
149
143
|
}
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
// 丢弃非法条目后不能再声称快照完整;空目录本身仍是有效快照。
|
|
145
|
+
const full = dir.full === true && entries.length === dir.entries.length
|
|
146
|
+
dirs[rel] = { ts: typeof dir.ts === 'number' ? dir.ts : 0, full, entries }
|
|
152
147
|
}
|
|
153
148
|
return { v: 1, root: typeof raw.root === 'string' ? raw.root : null, dirs }
|
|
154
149
|
} catch (error) {
|
|
@@ -162,15 +157,16 @@ export function trimPersist(data: TreeIndexData, maxDirs = PERSIST_MAX_DIRS, max
|
|
|
162
157
|
const sorted = Object.entries(data.dirs).sort((a, b) => b[1].ts - a[1].ts)
|
|
163
158
|
for (const [rel, dir] of sorted) {
|
|
164
159
|
if (Object.keys(dirs).length >= maxDirs) break
|
|
165
|
-
const
|
|
166
|
-
|
|
160
|
+
const truncated = dir.entries.length > maxEntries
|
|
161
|
+
const full = dir.full && !truncated
|
|
162
|
+
dirs[rel] = { ts: dir.ts, full, entries: truncated ? dir.entries.slice(0, maxEntries) : dir.entries }
|
|
167
163
|
}
|
|
168
164
|
return { v: 1, root: data.root, dirs }
|
|
169
165
|
}
|
|
170
166
|
// --endregion
|
|
171
167
|
|
|
172
168
|
// --region 状态机
|
|
173
|
-
/** 取(或建)某 cwd
|
|
169
|
+
/** 取(或建)某 cwd 的索引状态并惰性加载快照;加载期间真实列取优先,不被旧快照覆盖。 */
|
|
174
170
|
function ensureState(ctx: Ctx, cwd: string): IndexState {
|
|
175
171
|
const existing = states.get(cwd)
|
|
176
172
|
if (existing) return existing
|
|
@@ -179,6 +175,7 @@ function ensureState(ctx: Ctx, cwd: string): IndexState {
|
|
|
179
175
|
cwd,
|
|
180
176
|
data: { v: 1, root: null, dirs: {} },
|
|
181
177
|
stale: new Set(),
|
|
178
|
+
lru: new Map(),
|
|
182
179
|
dirty: false,
|
|
183
180
|
persistTimer: null,
|
|
184
181
|
healTimer: null,
|
|
@@ -209,9 +206,12 @@ async function loadIndex(state: IndexState): Promise<void> {
|
|
|
209
206
|
try {
|
|
210
207
|
const parsed = indexParse(await readFile(treeCacheFile(state.cwd), 'utf8'))
|
|
211
208
|
if (!parsed) return
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
// 旧快照先进入 LRU;加载期间完成的真实列取优先,不被磁盘覆盖。
|
|
210
|
+
const snapshots = Object.entries(parsed.dirs).sort((a, b) => a[1].ts - b[1].ts)
|
|
211
|
+
for (const [rel, dir] of snapshots) {
|
|
212
|
+
if (state.data.dirs[rel]) continue
|
|
213
|
+
state.data.dirs[rel] = dir
|
|
214
|
+
touchDir(state, rel)
|
|
215
215
|
}
|
|
216
216
|
if (parsed.root && !state.data.root) state.data.root = parsed.root
|
|
217
217
|
} catch (error) { /* 缺失/损坏/失败均按空索引 */ }
|
|
@@ -220,34 +220,57 @@ async function loadIndex(state: IndexState): Promise<void> {
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
/**
|
|
223
|
+
/**
|
|
224
|
+
* @private
|
|
225
|
+
* @author ddj 2026年10月22号
|
|
226
|
+
* @description 更新运行时访问顺序,超限时同步清理目录及失效标记。
|
|
227
|
+
* @param state 工作区索引状态
|
|
228
|
+
* @param rel 本次访问的相对目录
|
|
229
|
+
*/
|
|
230
|
+
function touchDir(state: IndexState, rel: string): void {
|
|
231
|
+
state.lru.delete(rel)
|
|
232
|
+
state.lru.set(rel, true)
|
|
233
|
+
if (state.lru.size <= MEM_MAX_DIRS) return
|
|
234
|
+
|
|
235
|
+
const oldest = state.lru.keys().next().value
|
|
236
|
+
if (oldest === undefined) return
|
|
237
|
+
state.lru.delete(oldest)
|
|
238
|
+
state.stale.delete(oldest)
|
|
239
|
+
delete state.data.dirs[oldest]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @public
|
|
244
|
+
* @author ddj 2026年10月22号
|
|
245
|
+
* @description 完整、新鲜且未失效才命中;访问仅更新 LRU,不续期硬 TTL。
|
|
246
|
+
* @param cwd 工作区根
|
|
247
|
+
* @param rel 相对目录
|
|
248
|
+
* @returns 完整条目,或未命中时的 null
|
|
249
|
+
*/
|
|
224
250
|
export function hitIndex(cwd: string, rel: string): TreeEntry[] | null {
|
|
225
251
|
const state = states.get(cwd)
|
|
226
252
|
if (!state) return null
|
|
227
253
|
const dir = state.data.dirs[rel]
|
|
228
|
-
if (!dir || state.stale.has(rel)) return null
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
dir.ts = now
|
|
254
|
+
if (!dir || !dir.full || state.stale.has(rel)) return null
|
|
255
|
+
if (Date.now() - dir.ts >= TREE_TTL_MS) return null
|
|
256
|
+
touchDir(state, rel)
|
|
232
257
|
return dir.entries
|
|
233
258
|
}
|
|
234
259
|
|
|
235
|
-
/**
|
|
260
|
+
/**
|
|
261
|
+
* @public
|
|
262
|
+
* @author ddj 2026年10月22号
|
|
263
|
+
* @description 保存完整实列结果与确认时间,更新 LRU、撤销 stale 并防抖落盘。
|
|
264
|
+
* @param ctx 工作区上下文
|
|
265
|
+
* @param cwd 工作区根
|
|
266
|
+
* @param rel 相对目录
|
|
267
|
+
* @param entries 完整实列条目
|
|
268
|
+
*/
|
|
236
269
|
export function putIndex(ctx: Ctx, cwd: string, rel: string, entries: TreeEntry[]): void {
|
|
237
270
|
const state = ensureState(ctx, cwd)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
let oldestTs = Infinity
|
|
242
|
-
for (const [k, v] of Object.entries(dirs)) {
|
|
243
|
-
if (v.ts < oldestTs) {
|
|
244
|
-
oldestTs = v.ts
|
|
245
|
-
oldest = k
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
delete dirs[oldest]
|
|
249
|
-
}
|
|
250
|
-
dirs[rel] = { ts: nextTs(), full: entries.length <= PERSIST_DIR_ENTRIES, entries }
|
|
271
|
+
// 实列结果在内存中始终完整;条目上限仅约束持久化副本。
|
|
272
|
+
state.data.dirs[rel] = { ts: Date.now(), full: true, entries }
|
|
273
|
+
touchDir(state, rel)
|
|
251
274
|
state.stale.delete(rel)
|
|
252
275
|
state.dirty = true
|
|
253
276
|
schedulePersist(state)
|
|
@@ -319,6 +342,7 @@ export function disposeIndex(cwd: string): void {
|
|
|
319
342
|
state.healTimer = null
|
|
320
343
|
}
|
|
321
344
|
if (state.dirty) void persistNow(state)
|
|
345
|
+
state.lru.clear()
|
|
322
346
|
states.delete(cwd)
|
|
323
347
|
}
|
|
324
348
|
// --endregion
|
|
@@ -379,6 +403,7 @@ export async function listDirCached(
|
|
|
379
403
|
if (error instanceof DirListError && error.code === 'not-found') {
|
|
380
404
|
if (state.data.dirs[rel]) {
|
|
381
405
|
delete state.data.dirs[rel]
|
|
406
|
+
state.lru.delete(rel)
|
|
382
407
|
state.dirty = true
|
|
383
408
|
schedulePersist(state)
|
|
384
409
|
}
|