dsh-vscode-mode 0.1.60 → 0.1.61
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/lib/client.js +356 -88
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +19 -1
- package/src/client/navHistory.ts +28 -1
- package/src/client/officialSidebar.ts +72 -1
- package/src/client/paths.ts +3 -3
- package/src/client/sidebar/panels/FileExplorer.ts +14 -11
- package/src/client/sidebar/panels/SearchPanel.ts +10 -7
- package/src/client/sidebar/types.ts +2 -0
- package/src/client/state/explorerCache.ts +9 -9
- package/src/client/state/explorerEntriesCache.ts +31 -31
- package/src/client/state/modelCache.ts +77 -0
- package/src/client/state/scopeStore.ts +96 -0
- package/src/client/state/viewStateCache.ts +9 -9
- package/src/client/ui/EditorView.ts +57 -26
- package/src/client/ui/OfficialSideTab.ts +21 -2
package/lib/client.js
CHANGED
|
@@ -3058,7 +3058,7 @@ window.__ModuleLoader__.load({
|
|
|
3058
3058
|
* 浏览器不涉 ~/.dsh 文件系统,这里只管键名 + shared 路由常量。
|
|
3059
3059
|
* 作者 ddj 2026-09-01
|
|
3060
3060
|
*/
|
|
3061
|
-
/** localStorage
|
|
3061
|
+
/** localStorage 键前缀表(按功能隔离;与 scopeStore 作用域键拼接:工作区优先,无 cwd 回退会话)。 */
|
|
3062
3062
|
const CACHE_KEY = {
|
|
3063
3063
|
/** 展开状态(explorerCache v1,当前使用)。 */
|
|
3064
3064
|
expanded: "edrv.cache.explorer.v1.",
|
|
@@ -3072,7 +3072,7 @@ window.__ModuleLoader__.load({
|
|
|
3072
3072
|
sidebar: "edrv.sidebar.",
|
|
3073
3073
|
/** 搜索面板条件(SearchPanel v1)。 */
|
|
3074
3074
|
search: "edrv.search.v1.",
|
|
3075
|
-
/**
|
|
3075
|
+
/** 侧边栏提示已关闭标记(全局,无作用域)。 */
|
|
3076
3076
|
sideHint: "edrv.side-hint-dismissed",
|
|
3077
3077
|
/** 「性能优化」页工作区栏目展开状态(workspaceFoldCache v1,全局不按会话)。 */
|
|
3078
3078
|
workspaceFold: "edrv.ws-fold.v1.",
|
|
@@ -3088,7 +3088,7 @@ window.__ModuleLoader__.load({
|
|
|
3088
3088
|
* 键前缀统一走 paths.ts PathConst(CACHE_KEY.viewstate)。
|
|
3089
3089
|
* 作者 ddj 2026-08-28
|
|
3090
3090
|
*/
|
|
3091
|
-
/** localStorage
|
|
3091
|
+
/** localStorage 键前缀(按工作区作用域隔离,无 cwd 会话回退会话键)。 */
|
|
3092
3092
|
const KEY_PREFIX$2 = CACHE_KEY.viewstate;
|
|
3093
3093
|
/**
|
|
3094
3094
|
* 判断对象是否为合法的 Monaco view state(保守形状校验,非深校验)。
|
|
@@ -3160,29 +3160,173 @@ window.__ModuleLoader__.load({
|
|
|
3160
3160
|
return next;
|
|
3161
3161
|
}
|
|
3162
3162
|
/**
|
|
3163
|
-
*
|
|
3163
|
+
* 读取某作用域的视图状态缓存(localStorage 不可用 → 空映射)。
|
|
3164
3164
|
* @author ddj 2026年08月28号
|
|
3165
|
-
* @param
|
|
3165
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
3166
3166
|
* @returns path → viewState 映射
|
|
3167
3167
|
*/
|
|
3168
|
-
function viewStatesLoad(
|
|
3168
|
+
function viewStatesLoad(scope) {
|
|
3169
3169
|
try {
|
|
3170
|
-
return parseViewStates(window.localStorage.getItem(KEY_PREFIX$2 + String(
|
|
3170
|
+
return parseViewStates(window.localStorage.getItem(KEY_PREFIX$2 + String(scope))) ?? {};
|
|
3171
3171
|
} catch (error) {
|
|
3172
3172
|
return {};
|
|
3173
3173
|
}
|
|
3174
3174
|
}
|
|
3175
3175
|
/**
|
|
3176
|
-
*
|
|
3176
|
+
* 写入某作用域的视图状态缓存(损坏/配额满 → 忽略)。
|
|
3177
3177
|
* @author ddj 2026年08月28号
|
|
3178
|
-
* @param
|
|
3178
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
3179
3179
|
* @param states path → viewState 映射
|
|
3180
3180
|
*/
|
|
3181
|
-
function viewStatesSave(
|
|
3181
|
+
function viewStatesSave(scope, states) {
|
|
3182
3182
|
try {
|
|
3183
|
-
window.localStorage.setItem(KEY_PREFIX$2 + String(
|
|
3183
|
+
window.localStorage.setItem(KEY_PREFIX$2 + String(scope), serializeViewStates(states));
|
|
3184
3184
|
} catch (error) {}
|
|
3185
3185
|
}
|
|
3186
|
+
//#endregion
|
|
3187
|
+
//#region src/client/state/scopeStore.ts
|
|
3188
|
+
/**
|
|
3189
|
+
* dsh-vscode-mode client — 编辑区状态作用域键(scope key)。
|
|
3190
|
+
* 编辑区 UI 状态(页签/展开树/视图状态/侧边栏等)按「工作区」隔离:同一工作区下
|
|
3191
|
+
* 切换对话恢复同一份状态,无体感差别;会话没有 cwd 时回退按会话隔离(旧行为)。
|
|
3192
|
+
* 另提供旧「会话键」数据到「工作区键」的一次性迁移(只复制不删除,幂等)。
|
|
3193
|
+
* 键前缀统一走 paths.ts PathConst(CACHE_KEY),本模块只产出拼接用的作用域串。
|
|
3194
|
+
* 作者 ddj 2026-09-09
|
|
3195
|
+
*/
|
|
3196
|
+
/** Windows 盘符绝对路径(如 D:/ 或 d:\),大小写不敏感。 */
|
|
3197
|
+
const WIN_DRIVE_RE = /^[a-z]:\//i;
|
|
3198
|
+
/**
|
|
3199
|
+
* 归一化工作区路径:trim、反斜杠→斜杠、去尾部斜杠;
|
|
3200
|
+
* Windows 盘符绝对路径整体小写(文件系统大小写不敏感),POSIX 路径保留大小写。
|
|
3201
|
+
* @author ddj 2026年09月09号
|
|
3202
|
+
* @param cwd 工作区目录(sessions 快照 byId[sessionId].cwd)
|
|
3203
|
+
* @returns 归一化路径;空值返回 ''
|
|
3204
|
+
*/
|
|
3205
|
+
function normalizeWsPath(cwd) {
|
|
3206
|
+
const s = String(cwd ?? "").trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
3207
|
+
if (!s) return "";
|
|
3208
|
+
return WIN_DRIVE_RE.test(s) ? s.toLowerCase() : s;
|
|
3209
|
+
}
|
|
3210
|
+
/**
|
|
3211
|
+
* 计算编辑区状态作用域键:有 cwd → 'ws:' + 归一化路径(同工作区共享);
|
|
3212
|
+
* 无 cwd → 'sid:' + sessionId(回退按会话隔离,对齐旧行为)。
|
|
3213
|
+
* @author ddj 2026年09月09号
|
|
3214
|
+
* @param cwd 工作区目录(可为 null/undefined)
|
|
3215
|
+
* @param sessionId 会话 id(cwd 缺失时的隔离键)
|
|
3216
|
+
* @returns 作用域键字符串
|
|
3217
|
+
*/
|
|
3218
|
+
function workspaceScopeOf(cwd, sessionId) {
|
|
3219
|
+
const ws = normalizeWsPath(cwd);
|
|
3220
|
+
if (ws) return "ws:" + ws;
|
|
3221
|
+
return "sid:" + String(sessionId ?? "");
|
|
3222
|
+
}
|
|
3223
|
+
/** 参与迁移的 localStorage 键前缀(编辑区全部按会话持久化的旧键)。 */
|
|
3224
|
+
const MIGRATE_PREFIXES = [
|
|
3225
|
+
CACHE_KEY.editor,
|
|
3226
|
+
CACHE_KEY.viewstate,
|
|
3227
|
+
CACHE_KEY.sidebar + "side.",
|
|
3228
|
+
CACHE_KEY.sidebar,
|
|
3229
|
+
CACHE_KEY.search,
|
|
3230
|
+
CACHE_KEY.expanded,
|
|
3231
|
+
CACHE_KEY.entries
|
|
3232
|
+
];
|
|
3233
|
+
/** 已迁移过的 scope+sessionId 组合(按 storage 实例分组;每页生命周期只跑一次)。 */
|
|
3234
|
+
const migratedPairs = /* @__PURE__ */ new WeakMap();
|
|
3235
|
+
/**
|
|
3236
|
+
* 把旧「前缀+sessionId」键的数据一次性复制到「前缀+scope」键(目标已存在则跳过)。
|
|
3237
|
+
* 只复制不删除旧键;幂等,可安全重复调用。升级后首个工作区键为空时兜底恢复旧数据。
|
|
3238
|
+
* @author ddj 2026年09月09号
|
|
3239
|
+
* @param scope 目标作用域键
|
|
3240
|
+
* @param sessionId 旧数据所属会话 id
|
|
3241
|
+
* @param storage localStorage 注入口(缺省 window.localStorage,测试可传内存桩)
|
|
3242
|
+
*/
|
|
3243
|
+
function migrateScopedKeys(scope, sessionId, storage) {
|
|
3244
|
+
if (!scope || !sessionId) return;
|
|
3245
|
+
let store = null;
|
|
3246
|
+
try {
|
|
3247
|
+
store = storage ?? window.localStorage;
|
|
3248
|
+
} catch (error) {
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
if (!store) return;
|
|
3252
|
+
let pairs = migratedPairs.get(store);
|
|
3253
|
+
if (!pairs) {
|
|
3254
|
+
pairs = /* @__PURE__ */ new Set();
|
|
3255
|
+
migratedPairs.set(store, pairs);
|
|
3256
|
+
}
|
|
3257
|
+
const pair = scope + "|" + sessionId;
|
|
3258
|
+
if (pairs.has(pair)) return;
|
|
3259
|
+
pairs.add(pair);
|
|
3260
|
+
const legacy = String(sessionId);
|
|
3261
|
+
for (const prefix of MIGRATE_PREFIXES) {
|
|
3262
|
+
const target = prefix + scope;
|
|
3263
|
+
try {
|
|
3264
|
+
if (store.getItem(target) !== null) continue;
|
|
3265
|
+
const raw = store.getItem(prefix + legacy);
|
|
3266
|
+
if (raw === null) continue;
|
|
3267
|
+
store.setItem(target, raw);
|
|
3268
|
+
} catch (error) {}
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
/** scope →(path → model)缓存表。 */
|
|
3272
|
+
const caches = /* @__PURE__ */ new Map();
|
|
3273
|
+
/** 当前激活作用域(切换时释放上一个)。 */
|
|
3274
|
+
let activeScope = null;
|
|
3275
|
+
/** 模型是否仍附着在某个编辑器实例上(缺方法视为未附着)。 */
|
|
3276
|
+
function isAttached(model) {
|
|
3277
|
+
const fn = model?.isAttachedToEditor;
|
|
3278
|
+
return typeof fn === "function" ? fn.call(model) === true : false;
|
|
3279
|
+
}
|
|
3280
|
+
/** 释放模型(仅未附着编辑器时真正 dispose;附着中的保留给编辑器自毁)。 */
|
|
3281
|
+
function disposeIfFree(model) {
|
|
3282
|
+
if (!model || isAttached(model)) return;
|
|
3283
|
+
try {
|
|
3284
|
+
model.dispose();
|
|
3285
|
+
} catch {}
|
|
3286
|
+
}
|
|
3287
|
+
/**
|
|
3288
|
+
* 取(或切)某作用域的 model 缓存:同作用域重复调用复用同一 Map(跨挂载存活);
|
|
3289
|
+
* 作用域变化时释放上一作用域的模型(未附着的 dispose,附着中的仅移出缓存)。
|
|
3290
|
+
* @author ddj 2026年09月09号
|
|
3291
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
3292
|
+
* @returns 该作用域的 path → model Map
|
|
3293
|
+
*/
|
|
3294
|
+
function modelsForScope(scope) {
|
|
3295
|
+
const existing = caches.get(scope);
|
|
3296
|
+
if (existing && activeScope === scope) return existing;
|
|
3297
|
+
if (activeScope !== null && activeScope !== scope) {
|
|
3298
|
+
const prev = caches.get(activeScope);
|
|
3299
|
+
if (prev) {
|
|
3300
|
+
for (const model of prev.values()) disposeIfFree(model);
|
|
3301
|
+
prev.clear();
|
|
3302
|
+
caches.delete(activeScope);
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
activeScope = scope;
|
|
3306
|
+
if (existing) return existing;
|
|
3307
|
+
const map = /* @__PURE__ */ new Map();
|
|
3308
|
+
caches.set(scope, map);
|
|
3309
|
+
return map;
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* 记入模型并刷新 LRU 序(先删后插保证最新),超上限逐出最旧(未附着才 dispose)。
|
|
3313
|
+
* @author ddj 2026年09月09号
|
|
3314
|
+
* @param map 作用域缓存 Map
|
|
3315
|
+
* @param path 工作区相对路径
|
|
3316
|
+
* @param model Monaco model(或测试桩)
|
|
3317
|
+
*/
|
|
3318
|
+
function rememberModel(map, path, model) {
|
|
3319
|
+
if (!path || !model) return;
|
|
3320
|
+
map.delete(path);
|
|
3321
|
+
map.set(path, model);
|
|
3322
|
+
while (map.size > 40) {
|
|
3323
|
+
const oldest = map.keys().next().value;
|
|
3324
|
+
if (oldest === void 0) break;
|
|
3325
|
+
const evicted = map.get(oldest);
|
|
3326
|
+
map.delete(oldest);
|
|
3327
|
+
disposeIfFree(evicted);
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3186
3330
|
let currentMin = 300;
|
|
3187
3331
|
/**
|
|
3188
3332
|
* 归一化最小宽度:非法(NaN/非有限/负数)回退默认,越界夹取到 [180, 560]。
|
|
@@ -3213,6 +3357,8 @@ window.__ModuleLoader__.load({
|
|
|
3213
3357
|
function getSidebarMinWidth() {
|
|
3214
3358
|
return currentMin;
|
|
3215
3359
|
}
|
|
3360
|
+
/** scope → 导航历史实例(同工作区跨会话复用同一份历史)。 */
|
|
3361
|
+
const scopeHistories = /* @__PURE__ */ new Map();
|
|
3216
3362
|
/** 路径一致(容忍相对路径大小写差异,对齐既有 sameFile 语义常用写法)。 */
|
|
3217
3363
|
function samePath(a, b) {
|
|
3218
3364
|
if (a === b) return true;
|
|
@@ -3276,6 +3422,26 @@ window.__ModuleLoader__.load({
|
|
|
3276
3422
|
}
|
|
3277
3423
|
};
|
|
3278
3424
|
}
|
|
3425
|
+
/**
|
|
3426
|
+
* 取(或建)某工作区作用域的导航历史实例:同 scope 跨会话复用同一实例,
|
|
3427
|
+
* 后退/前进历史在切换对话后保留(对齐「同一工作区同一编辑区」体感)。
|
|
3428
|
+
* 超出 NAV_SCOPE_CAP 时逐出最旧的实例(Map 插入序近似 LRU)。
|
|
3429
|
+
* @author ddj 2026年09月09号
|
|
3430
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
3431
|
+
* @param cap 后退栈容量上限(透传 createNavHistory)
|
|
3432
|
+
* @returns 导航历史实例
|
|
3433
|
+
*/
|
|
3434
|
+
function navHistoryFor(scope, cap = 200) {
|
|
3435
|
+
const existing = scopeHistories.get(scope);
|
|
3436
|
+
if (existing) return existing;
|
|
3437
|
+
const created = createNavHistory(cap);
|
|
3438
|
+
scopeHistories.set(scope, created);
|
|
3439
|
+
if (scopeHistories.size > 6) {
|
|
3440
|
+
const oldest = scopeHistories.keys().next().value;
|
|
3441
|
+
if (typeof oldest === "string") scopeHistories.delete(oldest);
|
|
3442
|
+
}
|
|
3443
|
+
return created;
|
|
3444
|
+
}
|
|
3279
3445
|
//#endregion
|
|
3280
3446
|
//#region src/client/addToConversation.ts
|
|
3281
3447
|
/** 把追加引用结果映射为可读文案(ok 用 okText;busy 提示已降级纯文本;unavailable 提示不可用)。
|
|
@@ -4399,6 +4565,8 @@ window.__ModuleLoader__.load({
|
|
|
4399
4565
|
* dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
|
|
4400
4566
|
* 迁移自原 src/client/index.ts 的 EditorView,语义不改;差异自绘已抽到 monaco/diffRender。
|
|
4401
4567
|
* 职责:页签/QuickOpen/Monaco 编辑器/差异审查(DiffBox/Launcher/Badge 事件装配)/状态栏/自动保存。
|
|
4568
|
+
* 状态作用域:页签/视图状态/侧边栏/展开树等 UI 状态按工作区作用域(cwd)隔离,
|
|
4569
|
+
* 同一工作区切换对话恢复同一份状态;无 cwd 会话回退按会话隔离(scopeStore)。
|
|
4402
4570
|
* 作者 ddj 2026-08-20
|
|
4403
4571
|
*/
|
|
4404
4572
|
/**
|
|
@@ -4415,6 +4583,13 @@ window.__ModuleLoader__.load({
|
|
|
4415
4583
|
const addToConversation = props.addToConversation;
|
|
4416
4584
|
const layout = props?.layout === "side" ? "side" : "tab";
|
|
4417
4585
|
const sideHint = props?.sideHint;
|
|
4586
|
+
const sessionsList = props?.sessions?.list;
|
|
4587
|
+
const cwd = react.default.useSyncExternalStore(react.default.useCallback((onStoreChange) => typeof sessionsList?.subscribe === "function" ? sessionsList.subscribe(onStoreChange) : () => {}, [sessionsList]), () => sessionsList?.getSnapshot?.()?.byId?.[sessionId]?.cwd ?? null, () => null);
|
|
4588
|
+
const scope = react.default.useMemo(() => {
|
|
4589
|
+
const s = workspaceScopeOf(cwd, sessionId);
|
|
4590
|
+
migrateScopedKeys(s, sessionId);
|
|
4591
|
+
return s;
|
|
4592
|
+
}, [cwd, sessionId]);
|
|
4418
4593
|
const [monaco, setMonaco] = react.default.useState(null);
|
|
4419
4594
|
const [monacoErr, setMonacoErr] = react.default.useState(null);
|
|
4420
4595
|
const [records, setRecords] = react.default.useState({});
|
|
@@ -4469,15 +4644,16 @@ window.__ModuleLoader__.load({
|
|
|
4469
4644
|
const editorRef = react.default.useRef(null);
|
|
4470
4645
|
const viewRootRef = react.default.useRef(null);
|
|
4471
4646
|
const monacoRef = react.default.useRef(null);
|
|
4472
|
-
const modelsRef = react.default.useRef(
|
|
4647
|
+
const modelsRef = react.default.useRef(null);
|
|
4648
|
+
if (!modelsRef.current) modelsRef.current = modelsForScope(scope);
|
|
4473
4649
|
const viewStatesRef = react.default.useRef({});
|
|
4474
4650
|
const saveTimerRef = react.default.useRef(null);
|
|
4475
4651
|
const loadSeqRef = react.default.useRef(0);
|
|
4476
4652
|
const programmaticRef = react.default.useRef(false);
|
|
4477
|
-
const
|
|
4653
|
+
const restoredScopeRef = react.default.useRef(null);
|
|
4478
4654
|
const pendingFocusRef = react.default.useRef(null);
|
|
4479
4655
|
const navRef = react.default.useRef(null);
|
|
4480
|
-
if (!navRef.current) navRef.current =
|
|
4656
|
+
if (!navRef.current) navRef.current = navHistoryFor(scope);
|
|
4481
4657
|
const navPendingRef = react.default.useRef(null);
|
|
4482
4658
|
const navCursorTimerRef = react.default.useRef(null);
|
|
4483
4659
|
const navBackRef = react.default.useRef(null);
|
|
@@ -4494,6 +4670,7 @@ window.__ModuleLoader__.load({
|
|
|
4494
4670
|
const batchBusyRef = react.default.useRef(false);
|
|
4495
4671
|
const menuHandlersRef = react.default.useRef(null);
|
|
4496
4672
|
const doSaveRef = react.default.useRef(null);
|
|
4673
|
+
const saveViewStateRef = react.default.useRef(null);
|
|
4497
4674
|
const diffRendererRef = react.default.useRef(null);
|
|
4498
4675
|
const layoutRef = react.default.useRef(layout);
|
|
4499
4676
|
layoutRef.current = layout;
|
|
@@ -4533,7 +4710,7 @@ window.__ModuleLoader__.load({
|
|
|
4533
4710
|
if (select) setActive(path);
|
|
4534
4711
|
};
|
|
4535
4712
|
/**
|
|
4536
|
-
*
|
|
4713
|
+
* 保存当前活动文件的视图状态(光标/滚动/折叠)到工作区作用域缓存。
|
|
4537
4714
|
* @author ddj 2026年08月28号
|
|
4538
4715
|
* @param path 要保存的文件路径(缺省 = 当前 active)
|
|
4539
4716
|
*/
|
|
@@ -4545,9 +4722,10 @@ window.__ModuleLoader__.load({
|
|
|
4545
4722
|
const state = ed.saveViewState();
|
|
4546
4723
|
if (!state) return;
|
|
4547
4724
|
viewStatesRef.current = upsertViewState(viewStatesRef.current, target, state);
|
|
4548
|
-
viewStatesSave(
|
|
4725
|
+
viewStatesSave(scope, viewStatesRef.current);
|
|
4549
4726
|
} catch (e) {}
|
|
4550
4727
|
};
|
|
4728
|
+
saveViewStateRef.current = saveViewState;
|
|
4551
4729
|
/**
|
|
4552
4730
|
* 恢复指定文件的视图状态(内容就绪、setModel 后调用;恢复后即消费删除)。
|
|
4553
4731
|
* @author ddj 2026年08月28号
|
|
@@ -5038,11 +5216,12 @@ window.__ModuleLoader__.load({
|
|
|
5038
5216
|
};
|
|
5039
5217
|
}, [sessionId, layout]);
|
|
5040
5218
|
react.default.useEffect(() => {
|
|
5041
|
-
if (
|
|
5042
|
-
|
|
5043
|
-
|
|
5219
|
+
if (!sessionId || restoredScopeRef.current === scope) return;
|
|
5220
|
+
restoredScopeRef.current = scope;
|
|
5221
|
+
modelsRef.current = modelsForScope(scope);
|
|
5222
|
+
viewStatesRef.current = viewStatesLoad(scope);
|
|
5044
5223
|
try {
|
|
5045
|
-
const raw = localStorage.getItem(CACHE_KEY.editor + String(
|
|
5224
|
+
const raw = localStorage.getItem(CACHE_KEY.editor + String(scope));
|
|
5046
5225
|
if (raw) {
|
|
5047
5226
|
const saved = JSON.parse(raw);
|
|
5048
5227
|
if (Array.isArray(saved.tabs) && saved.tabs.length) {
|
|
@@ -5051,20 +5230,20 @@ window.__ModuleLoader__.load({
|
|
|
5051
5230
|
}
|
|
5052
5231
|
}
|
|
5053
5232
|
} catch (e) {}
|
|
5054
|
-
}, [sessionId]);
|
|
5233
|
+
}, [sessionId, scope]);
|
|
5055
5234
|
react.default.useEffect(() => {
|
|
5056
|
-
navRef.current =
|
|
5235
|
+
navRef.current = navHistoryFor(scope);
|
|
5057
5236
|
navPendingRef.current = null;
|
|
5058
5237
|
if (navCursorTimerRef.current) {
|
|
5059
5238
|
clearTimeout(navCursorTimerRef.current);
|
|
5060
5239
|
navCursorTimerRef.current = null;
|
|
5061
5240
|
}
|
|
5062
5241
|
setNavTick((v) => v + 1);
|
|
5063
|
-
}, [
|
|
5242
|
+
}, [scope]);
|
|
5064
5243
|
react.default.useEffect(() => {
|
|
5065
5244
|
if (!sessionId) return;
|
|
5066
5245
|
try {
|
|
5067
|
-
localStorage.setItem(CACHE_KEY.editor + String(
|
|
5246
|
+
localStorage.setItem(CACHE_KEY.editor + String(scope), JSON.stringify({
|
|
5068
5247
|
tabs: tabs.map((t) => t.path),
|
|
5069
5248
|
active
|
|
5070
5249
|
}));
|
|
@@ -5072,9 +5251,10 @@ window.__ModuleLoader__.load({
|
|
|
5072
5251
|
}, [
|
|
5073
5252
|
tabs,
|
|
5074
5253
|
active,
|
|
5075
|
-
sessionId
|
|
5254
|
+
sessionId,
|
|
5255
|
+
scope
|
|
5076
5256
|
]);
|
|
5077
|
-
const sidebarKey = CACHE_KEY.sidebar + (layout === "side" ? "side." : "") + String(
|
|
5257
|
+
const sidebarKey = CACHE_KEY.sidebar + (layout === "side" ? "side." : "") + String(scope);
|
|
5078
5258
|
react.default.useEffect(() => {
|
|
5079
5259
|
if (!sessionId) return;
|
|
5080
5260
|
try {
|
|
@@ -5259,8 +5439,9 @@ window.__ModuleLoader__.load({
|
|
|
5259
5439
|
const cache = modelsRef.current;
|
|
5260
5440
|
let model = cache.get(path);
|
|
5261
5441
|
if (!model) {
|
|
5262
|
-
|
|
5263
|
-
|
|
5442
|
+
const uri = window.monaco.Uri.parse("edrv:///" + encodeURI(path));
|
|
5443
|
+
model = window.monaco.editor.getModel(uri) || window.monaco.editor.createModel(text ?? "", langOf(path), uri);
|
|
5444
|
+
rememberModel(cache, path, model);
|
|
5264
5445
|
} else if (text !== void 0 && model.getValue() !== text) {
|
|
5265
5446
|
programmaticRef.current = true;
|
|
5266
5447
|
model.setValue(text);
|
|
@@ -5382,14 +5563,13 @@ window.__ModuleLoader__.load({
|
|
|
5382
5563
|
]);
|
|
5383
5564
|
react.default.useEffect(() => () => {
|
|
5384
5565
|
flushSave();
|
|
5385
|
-
|
|
5566
|
+
flushNavCursor();
|
|
5567
|
+
saveViewStateRef.current?.();
|
|
5386
5568
|
diffRendererRef.current?.dispose?.();
|
|
5387
5569
|
if (editorRef.current) {
|
|
5388
5570
|
editorRef.current.dispose();
|
|
5389
5571
|
editorRef.current = null;
|
|
5390
5572
|
}
|
|
5391
|
-
for (const m of modelsRef.current.values()) m.dispose();
|
|
5392
|
-
modelsRef.current.clear();
|
|
5393
5573
|
for (const ctl of pdfCtlRef.current.values()) ctl.destroy();
|
|
5394
5574
|
pdfCtlRef.current.clear();
|
|
5395
5575
|
const root = editorRef.current && editorRef.current.getDomNode ? editorRef.current.getDomNode() : null;
|
|
@@ -6317,7 +6497,8 @@ window.__ModuleLoader__.load({
|
|
|
6317
6497
|
const sidebarPanels = props.sidebarPanels;
|
|
6318
6498
|
const sidebarCtx = {
|
|
6319
6499
|
sessionId,
|
|
6320
|
-
cwd:
|
|
6500
|
+
cwd: cwd ?? null,
|
|
6501
|
+
scope,
|
|
6321
6502
|
openFile: (p) => openFile(p, false),
|
|
6322
6503
|
openFileAt,
|
|
6323
6504
|
activePath: active,
|
|
@@ -7120,6 +7301,59 @@ window.__ModuleLoader__.load({
|
|
|
7120
7301
|
if (typeof bodyDisposer === "function") bodyDisposer();
|
|
7121
7302
|
};
|
|
7122
7303
|
}
|
|
7304
|
+
/** 编辑 Tab 是否处于激活态(正文挂载 = 激活;模块级,跨会话存活)。 */
|
|
7305
|
+
let editorTabActive = false;
|
|
7306
|
+
/** 正文挂载纪元(每次挂载自增;延迟判定期间有新挂载则放弃清除)。 */
|
|
7307
|
+
let mountEpoch = 0;
|
|
7308
|
+
/**
|
|
7309
|
+
* 标记编辑 Tab 激活并推进挂载纪元(正文挂载时调用)。
|
|
7310
|
+
* @author ddj 2026年09月09号
|
|
7311
|
+
*/
|
|
7312
|
+
function markEditorMounted() {
|
|
7313
|
+
mountEpoch += 1;
|
|
7314
|
+
markEditorActive$1(true);
|
|
7315
|
+
}
|
|
7316
|
+
/**
|
|
7317
|
+
* 直接设置激活标记(正文卸载延迟判定用;测试 also 用作状态编排入口)。
|
|
7318
|
+
* @author ddj 2026年09月09号
|
|
7319
|
+
* @param active 是否激活
|
|
7320
|
+
*/
|
|
7321
|
+
function markEditorActive$1(active) {
|
|
7322
|
+
editorTabActive = active;
|
|
7323
|
+
}
|
|
7324
|
+
/**
|
|
7325
|
+
* 读编辑 Tab 激活标记(会话切换时决定是否自动恢复编辑栏)。
|
|
7326
|
+
* @author ddj 2026年09月09号
|
|
7327
|
+
* @returns 上次标记的激活状态
|
|
7328
|
+
*/
|
|
7329
|
+
function isEditorTabActive() {
|
|
7330
|
+
return editorTabActive;
|
|
7331
|
+
}
|
|
7332
|
+
/**
|
|
7333
|
+
* 读当前挂载纪元(卸载延迟判定用:纪元已推进 = 有新正文接管,不清标记)。
|
|
7334
|
+
* @author ddj 2026年09月09号
|
|
7335
|
+
* @returns 当前纪元
|
|
7336
|
+
*/
|
|
7337
|
+
function editorMountEpoch() {
|
|
7338
|
+
return mountEpoch;
|
|
7339
|
+
}
|
|
7340
|
+
/**
|
|
7341
|
+
* 在挂载会话恢复编辑 Tab:已激活跳过(官方幂等);seat 未就绪抛错时有界重试。
|
|
7342
|
+
* openTab 官方语义「打开即展开侧栏」,一次调用同时完成展开 + 激活。
|
|
7343
|
+
* @author ddj 2026年09月09号
|
|
7344
|
+
* @param service 官方 sidebarRight 服务(缺失/未装配直接返回)
|
|
7345
|
+
* @param schedule 延时调度(ctx.timeout;重试节拍)
|
|
7346
|
+
* @param attempts 剩余尝试次数(默认 10 次 × 150ms ≈ 1.5s 窗口)
|
|
7347
|
+
*/
|
|
7348
|
+
function restoreEditorTab(service, schedule, attempts = 10) {
|
|
7349
|
+
if (!service || typeof service.openTab !== "function" || !editorTabActive) return;
|
|
7350
|
+
try {
|
|
7351
|
+
if (service.active?.()?.kind === "edrvEditor") return;
|
|
7352
|
+
service.openTab(OFFICIAL_TAB_KIND, {});
|
|
7353
|
+
} catch {
|
|
7354
|
+
if (attempts > 1) schedule(() => restoreEditorTab(service, schedule, attempts - 1), 150);
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7123
7357
|
//#endregion
|
|
7124
7358
|
//#region src/client/fileOpeners.ts
|
|
7125
7359
|
/**
|
|
@@ -9874,9 +10108,24 @@ window.__ModuleLoader__.load({
|
|
|
9874
10108
|
}
|
|
9875
10109
|
const revision = typeof navigation?.revision === "number" ? navigation.revision : -1;
|
|
9876
10110
|
const revisionRef = react.default.useRef(-1);
|
|
10111
|
+
const sessionIdRef = react.default.useRef(sessionId);
|
|
10112
|
+
sessionIdRef.current = sessionId;
|
|
9877
10113
|
react.default.useEffect(() => {
|
|
9878
10114
|
setSideEditorMounted(true);
|
|
9879
|
-
|
|
10115
|
+
markEditorMounted();
|
|
10116
|
+
return () => {
|
|
10117
|
+
setSideEditorMounted(false);
|
|
10118
|
+
const epoch = editorMountEpoch();
|
|
10119
|
+
const self = sessionIdRef.current;
|
|
10120
|
+
const schedule = props?.schedule;
|
|
10121
|
+
const decide = () => {
|
|
10122
|
+
if (editorMountEpoch() !== epoch) return;
|
|
10123
|
+
const current = props?.sessions?.list?.getSnapshot?.()?.current;
|
|
10124
|
+
markEditorActive(current === self);
|
|
10125
|
+
};
|
|
10126
|
+
if (typeof schedule === "function") schedule(decide, 0);
|
|
10127
|
+
else decide();
|
|
10128
|
+
};
|
|
9880
10129
|
}, []);
|
|
9881
10130
|
react.default.useEffect(() => {
|
|
9882
10131
|
if (revision === revisionRef.current) return;
|
|
@@ -10103,7 +10352,7 @@ window.__ModuleLoader__.load({
|
|
|
10103
10352
|
* 键前缀统一走 paths.ts PathConst(CACHE_KEY.expanded)。
|
|
10104
10353
|
* 作者 ddj 2026-08-28
|
|
10105
10354
|
*/
|
|
10106
|
-
/** localStorage
|
|
10355
|
+
/** localStorage 键前缀(按工作区作用域隔离,无 cwd 会话回退会话键)。 */
|
|
10107
10356
|
const KEY_PREFIX$1 = CACHE_KEY.expanded;
|
|
10108
10357
|
/**
|
|
10109
10358
|
* 归一化展开路径:去空白、反斜杠→斜杠、去首尾斜杠,`..` 段拒绝。
|
|
@@ -10175,27 +10424,27 @@ window.__ModuleLoader__.load({
|
|
|
10175
10424
|
}
|
|
10176
10425
|
}
|
|
10177
10426
|
/**
|
|
10178
|
-
*
|
|
10427
|
+
* 读取某作用域的展开状态缓存(localStorage 不可用 → null)。
|
|
10179
10428
|
* @author ddj 2026年08月28号
|
|
10180
|
-
* @param
|
|
10429
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
10181
10430
|
* @returns 缓存数据或 null
|
|
10182
10431
|
*/
|
|
10183
|
-
function explorerLoad(
|
|
10432
|
+
function explorerLoad(scope) {
|
|
10184
10433
|
try {
|
|
10185
|
-
return parseExplorer(window.localStorage.getItem(KEY_PREFIX$1 + String(
|
|
10434
|
+
return parseExplorer(window.localStorage.getItem(KEY_PREFIX$1 + String(scope)));
|
|
10186
10435
|
} catch (error) {
|
|
10187
10436
|
return null;
|
|
10188
10437
|
}
|
|
10189
10438
|
}
|
|
10190
10439
|
/**
|
|
10191
|
-
*
|
|
10440
|
+
* 写入某作用域的展开状态缓存(损坏/配额满 → 忽略)。
|
|
10192
10441
|
* @author ddj 2026年08月28号
|
|
10193
|
-
* @param
|
|
10442
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
10194
10443
|
* @param data 缓存数据
|
|
10195
10444
|
*/
|
|
10196
|
-
function explorerSave(
|
|
10445
|
+
function explorerSave(scope, data) {
|
|
10197
10446
|
try {
|
|
10198
|
-
window.localStorage.setItem(KEY_PREFIX$1 + String(
|
|
10447
|
+
window.localStorage.setItem(KEY_PREFIX$1 + String(scope), serializeExplorer(data));
|
|
10199
10448
|
} catch (error) {}
|
|
10200
10449
|
}
|
|
10201
10450
|
//#endregion
|
|
@@ -10260,9 +10509,9 @@ window.__ModuleLoader__.load({
|
|
|
10260
10509
|
return null;
|
|
10261
10510
|
}
|
|
10262
10511
|
}
|
|
10263
|
-
/**
|
|
10512
|
+
/** scope → 缓存镜像(同步读,避免每次渲染 parse)。 */
|
|
10264
10513
|
const mirrors = /* @__PURE__ */ new Map();
|
|
10265
|
-
/**
|
|
10514
|
+
/** scope → 落盘防抖计时器。 */
|
|
10266
10515
|
const saveTimers = /* @__PURE__ */ new Map();
|
|
10267
10516
|
/** 单调时钟:连续 put 的 ts 严格递增(同毫秒不并列,逐出顺序可预期)。 */
|
|
10268
10517
|
let clock = 0;
|
|
@@ -10272,82 +10521,82 @@ window.__ModuleLoader__.load({
|
|
|
10272
10521
|
return clock;
|
|
10273
10522
|
}
|
|
10274
10523
|
/** localStorage 读取(不可用 → null)。 */
|
|
10275
|
-
function readRaw(
|
|
10524
|
+
function readRaw(scope) {
|
|
10276
10525
|
try {
|
|
10277
|
-
return window.localStorage.getItem(KEY_PREFIX +
|
|
10526
|
+
return window.localStorage.getItem(KEY_PREFIX + scope);
|
|
10278
10527
|
} catch (error) {
|
|
10279
10528
|
return null;
|
|
10280
10529
|
}
|
|
10281
10530
|
}
|
|
10282
10531
|
/** localStorage 写入(配额/隐私模式失败静默)。 */
|
|
10283
|
-
function writeRaw(
|
|
10532
|
+
function writeRaw(scope, text) {
|
|
10284
10533
|
try {
|
|
10285
|
-
window.localStorage.setItem(KEY_PREFIX +
|
|
10534
|
+
window.localStorage.setItem(KEY_PREFIX + scope, text);
|
|
10286
10535
|
} catch (error) {}
|
|
10287
10536
|
}
|
|
10288
|
-
/**
|
|
10289
|
-
function mirrorOf(
|
|
10290
|
-
const existing = mirrors.get(
|
|
10537
|
+
/** 取(或建)作用域缓存镜像。 */
|
|
10538
|
+
function mirrorOf(scope) {
|
|
10539
|
+
const existing = mirrors.get(scope);
|
|
10291
10540
|
if (existing) return existing;
|
|
10292
|
-
const data = entriesParse(readRaw(
|
|
10541
|
+
const data = entriesParse(readRaw(scope)) ?? {
|
|
10293
10542
|
v: 2,
|
|
10294
10543
|
root: null,
|
|
10295
10544
|
dirs: {}
|
|
10296
10545
|
};
|
|
10297
|
-
mirrors.set(
|
|
10546
|
+
mirrors.set(scope, data);
|
|
10298
10547
|
return data;
|
|
10299
10548
|
}
|
|
10300
10549
|
/** 调度防抖落盘(重置已有计时器)。 */
|
|
10301
|
-
function scheduleSave(
|
|
10302
|
-
const prev = saveTimers.get(
|
|
10550
|
+
function scheduleSave(scope) {
|
|
10551
|
+
const prev = saveTimers.get(scope);
|
|
10303
10552
|
if (prev) clearTimeout(prev);
|
|
10304
|
-
saveTimers.set(
|
|
10305
|
-
saveTimers.delete(
|
|
10306
|
-
const data = mirrors.get(
|
|
10307
|
-
if (data) writeRaw(
|
|
10553
|
+
saveTimers.set(scope, setTimeout(() => {
|
|
10554
|
+
saveTimers.delete(scope);
|
|
10555
|
+
const data = mirrors.get(scope);
|
|
10556
|
+
if (data) writeRaw(scope, entriesSerialize(entriesTrim(data)));
|
|
10308
10557
|
}, 300));
|
|
10309
10558
|
}
|
|
10310
10559
|
/**
|
|
10311
10560
|
* 读某目录缓存条目(无 → null;展示不受新鲜度限制)。
|
|
10312
10561
|
* @author ddj 2026年08月31号
|
|
10313
|
-
* @param
|
|
10562
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
10314
10563
|
* @param rel 目录相对路径('' = 根)
|
|
10315
10564
|
* @returns 缓存条目或 null
|
|
10316
10565
|
*/
|
|
10317
|
-
function entriesCacheGet(
|
|
10318
|
-
if (!
|
|
10319
|
-
const dir = mirrorOf(
|
|
10566
|
+
function entriesCacheGet(scope, rel) {
|
|
10567
|
+
if (!scope) return null;
|
|
10568
|
+
const dir = mirrorOf(scope).dirs[rel];
|
|
10320
10569
|
if (!dir || !dir.entries.length) return null;
|
|
10321
10570
|
return dir.entries;
|
|
10322
10571
|
}
|
|
10323
10572
|
/**
|
|
10324
10573
|
* 某目录缓存是否新鲜(预取去重用)。
|
|
10325
10574
|
* @author ddj 2026年08月31号
|
|
10326
|
-
* @param
|
|
10575
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
10327
10576
|
* @param rel 目录相对路径
|
|
10328
10577
|
* @returns 新鲜返回 true
|
|
10329
10578
|
*/
|
|
10330
|
-
function entriesCacheIsFresh(
|
|
10331
|
-
if (!
|
|
10332
|
-
const dir = mirrorOf(
|
|
10579
|
+
function entriesCacheIsFresh(scope, rel) {
|
|
10580
|
+
if (!scope) return false;
|
|
10581
|
+
const dir = mirrorOf(scope).dirs[rel];
|
|
10333
10582
|
return Boolean(dir && Date.now() - dir.ts < 3e4);
|
|
10334
10583
|
}
|
|
10335
10584
|
/**
|
|
10336
10585
|
* 写入某目录缓存条目(内存镜像 + 防抖落盘;单目录条目截断)。
|
|
10337
10586
|
* @author ddj 2026年08月31号
|
|
10338
|
-
* @param
|
|
10587
|
+
* @param scope 作用域键(scopeStore.workspaceScopeOf 产物)
|
|
10339
10588
|
* @param rel 目录相对路径
|
|
10340
10589
|
* @param entries 目录条目
|
|
10341
10590
|
*/
|
|
10342
|
-
function entriesCachePut(
|
|
10343
|
-
if (!
|
|
10344
|
-
const data = mirrorOf(
|
|
10591
|
+
function entriesCachePut(scope, rel, entries) {
|
|
10592
|
+
if (!scope) return;
|
|
10593
|
+
const data = mirrorOf(scope);
|
|
10345
10594
|
data.dirs[rel] = {
|
|
10346
10595
|
ts: nextTs(),
|
|
10347
10596
|
entries: entries.slice(0, 500)
|
|
10348
10597
|
};
|
|
10349
10598
|
data.dirs = entriesTrim(data).dirs;
|
|
10350
|
-
scheduleSave(
|
|
10599
|
+
scheduleSave(scope);
|
|
10351
10600
|
}
|
|
10352
10601
|
//#endregion
|
|
10353
10602
|
//#region src/client/sidebar/panels/FileExplorer.ts
|
|
@@ -10379,6 +10628,7 @@ window.__ModuleLoader__.load({
|
|
|
10379
10628
|
function FileExplorer(props) {
|
|
10380
10629
|
const ctx = props?.ctx;
|
|
10381
10630
|
const sessionId = ctx?.sessionId;
|
|
10631
|
+
const scope = ctx?.scope ?? workspaceScopeOf(null, sessionId);
|
|
10382
10632
|
const openFile = ctx?.openFile;
|
|
10383
10633
|
const activePath = ctx?.activePath ?? null;
|
|
10384
10634
|
const pendingByPath = ctx?.pendingByPath ?? {};
|
|
@@ -10399,14 +10649,14 @@ window.__ModuleLoader__.load({
|
|
|
10399
10649
|
const loadDirRef = react.default.useRef(null);
|
|
10400
10650
|
const refreshRef = react.default.useRef(null);
|
|
10401
10651
|
/** 渲染取数:内存态 → 本地条目缓存 → null(显示加载态)。 */
|
|
10402
|
-
const entriesOf = (rel) => dirsRef.current[rel] ?? entriesCacheGet(
|
|
10652
|
+
const entriesOf = (rel) => dirsRef.current[rel] ?? entriesCacheGet(scope, rel) ?? null;
|
|
10403
10653
|
/** 预取子目录:≤4 个、排除重型目录、缓存新鲜跳过、已加载跳过、不级联。 */
|
|
10404
10654
|
const prefetchDirs = (rel, entries) => {
|
|
10405
10655
|
const want = [];
|
|
10406
10656
|
for (const e of entries) {
|
|
10407
10657
|
if (e.type !== "directory") continue;
|
|
10408
10658
|
if (PREFETCH_EXCLUDED.has(e.name)) continue;
|
|
10409
|
-
if (entriesCacheIsFresh(
|
|
10659
|
+
if (entriesCacheIsFresh(scope, e.path)) continue;
|
|
10410
10660
|
if (dirsRef.current[e.path] !== void 0) continue;
|
|
10411
10661
|
want.push(e.path);
|
|
10412
10662
|
if (want.length >= PREFETCH_MAX) break;
|
|
@@ -10439,7 +10689,7 @@ window.__ModuleLoader__.load({
|
|
|
10439
10689
|
if (res && res.ok && Array.isArray(res.entries)) {
|
|
10440
10690
|
setDirs((prev) => Object.assign({}, prev, { [rel]: res.entries }));
|
|
10441
10691
|
if (res.root) setRoot((prev) => prev || res.root);
|
|
10442
|
-
entriesCachePut(
|
|
10692
|
+
entriesCachePut(scope, rel, res.entries);
|
|
10443
10693
|
if (doPrefetch) prefetchDirs(rel, res.entries);
|
|
10444
10694
|
} else if (!entriesOf(rel)) setErrors((prev) => Object.assign({}, prev, { [rel]: res?.error ? String(res.error) : "读取目录失败" }));
|
|
10445
10695
|
}).catch((e) => {
|
|
@@ -10492,7 +10742,7 @@ window.__ModuleLoader__.load({
|
|
|
10492
10742
|
setErrors({});
|
|
10493
10743
|
setMenu(null);
|
|
10494
10744
|
setRoot(null);
|
|
10495
|
-
const cached =
|
|
10745
|
+
const cached = scope ? explorerLoad(scope) : null;
|
|
10496
10746
|
const restored = cached?.expanded ?? [];
|
|
10497
10747
|
if (cached?.root) setRoot(cached.root);
|
|
10498
10748
|
if (restored.length) {
|
|
@@ -10502,17 +10752,17 @@ window.__ModuleLoader__.load({
|
|
|
10502
10752
|
}
|
|
10503
10753
|
loadDir("", { prefetch: true });
|
|
10504
10754
|
for (const rel of restored) if (rel) loadDir(rel, { prefetch: true });
|
|
10505
|
-
}, [
|
|
10755
|
+
}, [scope]);
|
|
10506
10756
|
react.default.useEffect(() => {
|
|
10507
10757
|
if (saveTimerRef.current) {
|
|
10508
10758
|
clearTimeout(saveTimerRef.current);
|
|
10509
10759
|
saveTimerRef.current = null;
|
|
10510
10760
|
}
|
|
10511
|
-
if (!
|
|
10761
|
+
if (!scope) return;
|
|
10512
10762
|
saveTimerRef.current = setTimeout(() => {
|
|
10513
10763
|
saveTimerRef.current = null;
|
|
10514
10764
|
const expandedList = Object.keys(expanded).filter((k) => expanded[k] === true);
|
|
10515
|
-
explorerSave(
|
|
10765
|
+
explorerSave(scope, {
|
|
10516
10766
|
root,
|
|
10517
10767
|
expanded: expandedList
|
|
10518
10768
|
});
|
|
@@ -10526,7 +10776,7 @@ window.__ModuleLoader__.load({
|
|
|
10526
10776
|
}, [
|
|
10527
10777
|
expanded,
|
|
10528
10778
|
root,
|
|
10529
|
-
|
|
10779
|
+
scope
|
|
10530
10780
|
]);
|
|
10531
10781
|
react.default.useEffect(() => {
|
|
10532
10782
|
const onRefresh = () => refreshRef.current?.();
|
|
@@ -10534,14 +10784,14 @@ window.__ModuleLoader__.load({
|
|
|
10534
10784
|
return () => window.removeEventListener("edrv:refresh", onRefresh);
|
|
10535
10785
|
}, []);
|
|
10536
10786
|
react.default.useEffect(() => {
|
|
10537
|
-
if (!
|
|
10787
|
+
if (!scope) return;
|
|
10538
10788
|
const timer = setInterval(() => {
|
|
10539
10789
|
const open = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
|
|
10540
10790
|
if (!open.length) return;
|
|
10541
10791
|
for (const rel of open) loadDirRef.current?.(rel, {});
|
|
10542
10792
|
}, FOLLOW_INTERVAL_MS);
|
|
10543
10793
|
return () => clearInterval(timer);
|
|
10544
|
-
}, [
|
|
10794
|
+
}, [scope]);
|
|
10545
10795
|
const rootName = root ? String(root).split(/[\\/]/).pop() || root : "";
|
|
10546
10796
|
const rowEl = (e, depth, isDir, isOpen) => {
|
|
10547
10797
|
const pending = isDir ? 0 : pendingByPath[e.path] ?? 0;
|
|
@@ -10680,6 +10930,7 @@ window.__ModuleLoader__.load({
|
|
|
10680
10930
|
function SearchPanel(props) {
|
|
10681
10931
|
const ctx = props?.ctx;
|
|
10682
10932
|
const sessionId = ctx?.sessionId;
|
|
10933
|
+
const scope = ctx?.scope ?? workspaceScopeOf(null, sessionId);
|
|
10683
10934
|
const openFileAt = ctx?.openFileAt ?? (() => {});
|
|
10684
10935
|
const activePath = ctx?.activePath ?? null;
|
|
10685
10936
|
const [query, setQuery] = react.default.useState("");
|
|
@@ -10719,9 +10970,9 @@ window.__ModuleLoader__.load({
|
|
|
10719
10970
|
exclude: excludeOn ? splitGlobs(excludeText) : []
|
|
10720
10971
|
};
|
|
10721
10972
|
react.default.useEffect(() => {
|
|
10722
|
-
if (!
|
|
10973
|
+
if (!scope) return;
|
|
10723
10974
|
try {
|
|
10724
|
-
const saved = JSON.parse(localStorage.getItem(CACHE_KEY.search + String(
|
|
10975
|
+
const saved = JSON.parse(localStorage.getItem(CACHE_KEY.search + String(scope)) || "null");
|
|
10725
10976
|
if (saved && typeof saved === "object") {
|
|
10726
10977
|
if (typeof saved.query === "string") setQuery(saved.query);
|
|
10727
10978
|
if (typeof saved.matchCase === "boolean") setMatchCase(saved.matchCase);
|
|
@@ -10734,11 +10985,11 @@ window.__ModuleLoader__.load({
|
|
|
10734
10985
|
if (typeof saved.sectionOpen === "boolean") setSectionOpen(saved.sectionOpen);
|
|
10735
10986
|
}
|
|
10736
10987
|
} catch (e) {}
|
|
10737
|
-
}, [
|
|
10988
|
+
}, [scope]);
|
|
10738
10989
|
react.default.useEffect(() => {
|
|
10739
|
-
if (!
|
|
10990
|
+
if (!scope) return;
|
|
10740
10991
|
try {
|
|
10741
|
-
localStorage.setItem(CACHE_KEY.search + String(
|
|
10992
|
+
localStorage.setItem(CACHE_KEY.search + String(scope), JSON.stringify({
|
|
10742
10993
|
query,
|
|
10743
10994
|
matchCase,
|
|
10744
10995
|
wholeWord,
|
|
@@ -10751,7 +11002,7 @@ window.__ModuleLoader__.load({
|
|
|
10751
11002
|
}));
|
|
10752
11003
|
} catch (e) {}
|
|
10753
11004
|
}, [
|
|
10754
|
-
|
|
11005
|
+
scope,
|
|
10755
11006
|
query,
|
|
10756
11007
|
matchCase,
|
|
10757
11008
|
wholeWord,
|
|
@@ -12622,6 +12873,23 @@ window.__ModuleLoader__.load({
|
|
|
12622
12873
|
setSession(list.getSnapshot()?.current);
|
|
12623
12874
|
}), "vscode-mode: lsp session sync");
|
|
12624
12875
|
}
|
|
12876
|
+
if (monacoList && typeof monacoList.subscribe === "function") {
|
|
12877
|
+
const list = monacoList;
|
|
12878
|
+
ctx.effect(() => {
|
|
12879
|
+
let lastCurrent = list.getSnapshot?.()?.current;
|
|
12880
|
+
const check = () => {
|
|
12881
|
+
const current = list.getSnapshot?.()?.current;
|
|
12882
|
+
if (!current || current === lastCurrent) return;
|
|
12883
|
+
lastCurrent = current;
|
|
12884
|
+
if (!isEditorTabActive() || officialService === void 0) return;
|
|
12885
|
+
try {
|
|
12886
|
+
officialService.service.openTabIn?.(current, OFFICIAL_TAB_KIND, {});
|
|
12887
|
+
} catch {}
|
|
12888
|
+
restoreEditorTab(officialService.service, schedule);
|
|
12889
|
+
};
|
|
12890
|
+
return list.subscribe(() => schedule(check, 0));
|
|
12891
|
+
}, "vscode-mode: editor tab restore");
|
|
12892
|
+
}
|
|
12625
12893
|
const originalOpenPath = workspaces?.openPath;
|
|
12626
12894
|
const binder = pickSettingsBinder(ctx);
|
|
12627
12895
|
const settings = binder.scope;
|