dsh-vscode-mode 0.13.0 → 0.14.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.
- package/README.md +57 -15
- package/lib/client.js +22666 -22351
- package/lib/client.js.map +1 -1
- package/lib/index.js +228 -12
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/client/index.ts +83 -13
- package/src/client/navHistory.ts +45 -0
- package/src/client/officialSidebar.ts +59 -1
- package/src/client/paths.ts +6 -0
- package/src/client/shortcutsOfficial.ts +26 -0
- package/src/client/state/mdPreviewCache.ts +74 -0
- package/src/client/state/navStateCache.ts +83 -0
- package/src/client/state/uiStatePersist.ts +562 -0
- package/src/client/styles/mcp.css +1 -19
- package/src/client/ui/EditorView.ts +65 -10
- package/src/client/ui/McpSettings.ts +26 -4
- package/src/paths.ts +59 -3
- package/src/rpc.ts +50 -3
- package/src/shared/rpc.ts +24 -0
- package/src/uiState.ts +165 -0
- package/src/client/shortcutRecorder.ts +0 -161
- package/src/client/ui/ShortcutSettings.ts +0 -274
package/lib/index.js
CHANGED
|
@@ -2349,6 +2349,29 @@ function pluginLogRoot(home = dshHome()) {
|
|
|
2349
2349
|
function treeCacheFile(cwd, home = dshHome(), schema = 1) {
|
|
2350
2350
|
return join(workspaceCacheDir(hashOf(cwd), home), "tree.v" + schema + ".json");
|
|
2351
2351
|
}
|
|
2352
|
+
/**
|
|
2353
|
+
* 每工作区 UI 状态镜像文件(工作区级:workspace/<id>/ui.v<schema>.json)。
|
|
2354
|
+
* 存编辑器页签/侧栏/预览态等界面状态的 localStorage 原值副本,供换 origin 后回填。
|
|
2355
|
+
* @author ddj 2026年09月28号
|
|
2356
|
+
* @param cwd 工作区绝对路径
|
|
2357
|
+
* @param home DSH home(测试可注入)
|
|
2358
|
+
* @param schema schema 版本(缺省 UI_STATE_SCHEMA)
|
|
2359
|
+
* @returns UI 状态镜像文件绝对路径
|
|
2360
|
+
*/
|
|
2361
|
+
function uiStateFile(cwd, home = dshHome(), schema = 1) {
|
|
2362
|
+
return join(workspaceCacheDir(hashOf(cwd), home), "ui.v" + schema + ".json");
|
|
2363
|
+
}
|
|
2364
|
+
/**
|
|
2365
|
+
* 用户级 UI 状态镜像文件(跨工作区共享:user/ui-globals.v<schema>.json)。
|
|
2366
|
+
* 存与工作区无关的全局键(侧栏提示已忽略、工作区折叠态等)。
|
|
2367
|
+
* @author ddj 2026年09月28号
|
|
2368
|
+
* @param home DSH home(测试可注入)
|
|
2369
|
+
* @param schema schema 版本(缺省 UI_STATE_SCHEMA)
|
|
2370
|
+
* @returns 全局 UI 状态镜像文件绝对路径
|
|
2371
|
+
*/
|
|
2372
|
+
function uiGlobalsFile(home = dshHome(), schema = 1) {
|
|
2373
|
+
return join(userCacheDir(home), "ui-globals.v" + schema + ".json");
|
|
2374
|
+
}
|
|
2352
2375
|
/** 每工作区 debug 日志文件(用户级日志根下,按 cwd hash 隔离)。 */
|
|
2353
2376
|
function debugLogFile(cwd, home = dshHome()) {
|
|
2354
2377
|
return join(pluginLogRoot(home), "debug." + hashOf(cwd) + ".log");
|
|
@@ -2377,10 +2400,36 @@ function imageDirOf(config, moduleUrl) {
|
|
|
2377
2400
|
if (cfg && typeof cfg.imageDir === "string" && cfg.imageDir) return cfg.imageDir.replace(/\\/g, "/");
|
|
2378
2401
|
return assetsDirOf(moduleUrl);
|
|
2379
2402
|
}
|
|
2380
|
-
/**
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2403
|
+
/**
|
|
2404
|
+
* 已知缓存文件种类 → 当前 schema 版本(sweep 判旧依据)。
|
|
2405
|
+
* 新增缓存文件必须登记在此,否则被当作未知残留清理。
|
|
2406
|
+
*/
|
|
2407
|
+
const CACHE_SCHEMAS = {
|
|
2408
|
+
tree: 1,
|
|
2409
|
+
"lsp-specs": 4,
|
|
2410
|
+
ui: 1,
|
|
2411
|
+
"ui-globals": 1
|
|
2412
|
+
};
|
|
2413
|
+
/** 缓存文件名 → 种类与 schema 版本(`<kind>.v<schema>.json`;未登记种类/命名不合规 → null)。 */
|
|
2414
|
+
function parseCacheName(filename) {
|
|
2415
|
+
const m = /^([a-z-]+)\.v(\d+)\.json$/.exec(filename);
|
|
2416
|
+
if (!m) return null;
|
|
2417
|
+
const kind = m[1];
|
|
2418
|
+
if (CACHE_SCHEMAS[kind] === void 0) return null;
|
|
2419
|
+
return {
|
|
2420
|
+
kind,
|
|
2421
|
+
schema: Number(m[2])
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* 该缓存文件名是否为已登记种类的当前 schema 版本(sweep 判旧/判残留依据)。
|
|
2426
|
+
* @author ddj 2026年09月28号
|
|
2427
|
+
* @param filename 缓存文件名(不含目录)
|
|
2428
|
+
* @returns 已登记种类且版本为当前版本
|
|
2429
|
+
*/
|
|
2430
|
+
function isCurrentCacheFile(filename) {
|
|
2431
|
+
const parsed = parseCacheName(filename);
|
|
2432
|
+
return parsed !== null && parsed.schema === CACHE_SCHEMAS[parsed.kind];
|
|
2384
2433
|
}
|
|
2385
2434
|
/** 按「schema 版本 + 保留期」清扫某目录内文件(目录级,返回相对路径清单)。 */
|
|
2386
2435
|
async function sweepDirFiles(dir, removed, now, dropAll) {
|
|
@@ -2389,9 +2438,7 @@ async function sweepDirFiles(dir, removed, now, dropAll) {
|
|
|
2389
2438
|
const full = join(dir, name);
|
|
2390
2439
|
let drop = dropAll;
|
|
2391
2440
|
if (!drop) {
|
|
2392
|
-
|
|
2393
|
-
if (schema === null) drop = true;
|
|
2394
|
-
else if (schema !== 1) drop = true;
|
|
2441
|
+
if (!isCurrentCacheFile(name)) drop = true;
|
|
2395
2442
|
else try {
|
|
2396
2443
|
if (now - (await stat(full)).mtimeMs > 2592e6) drop = true;
|
|
2397
2444
|
} catch (error) {
|
|
@@ -9214,6 +9261,125 @@ async function revealInExplorer(absPath, isDir) {
|
|
|
9214
9261
|
};
|
|
9215
9262
|
}
|
|
9216
9263
|
}
|
|
9264
|
+
/**
|
|
9265
|
+
* 合并增量补丁到键值表(不改入参)。
|
|
9266
|
+
* @author ddj 2026年09月28号
|
|
9267
|
+
* @param base 已有键值表
|
|
9268
|
+
* @param patch 增量补丁(null/undefined 值删除该键;空键跳过)
|
|
9269
|
+
* @returns 合并后的新键值表
|
|
9270
|
+
*/
|
|
9271
|
+
function mergeUiStateKeys(base, patch) {
|
|
9272
|
+
const merged = { ...base };
|
|
9273
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
9274
|
+
if (!key) continue;
|
|
9275
|
+
if (value === null || value === void 0) delete merged[key];
|
|
9276
|
+
else merged[key] = String(value);
|
|
9277
|
+
}
|
|
9278
|
+
return merged;
|
|
9279
|
+
}
|
|
9280
|
+
/**
|
|
9281
|
+
* 解析镜像文件文本;损坏/版本不符 → null(调用方视为空)。
|
|
9282
|
+
* @author ddj 2026年09月28号
|
|
9283
|
+
* @param text 文件文本(可为空)
|
|
9284
|
+
* @returns 解析后的文档;不可用时 null
|
|
9285
|
+
*/
|
|
9286
|
+
function parseUiState(text) {
|
|
9287
|
+
if (!text) return null;
|
|
9288
|
+
let raw;
|
|
9289
|
+
try {
|
|
9290
|
+
raw = JSON.parse(text);
|
|
9291
|
+
} catch (error) {
|
|
9292
|
+
return null;
|
|
9293
|
+
}
|
|
9294
|
+
if (!raw || typeof raw !== "object") return null;
|
|
9295
|
+
const doc = raw;
|
|
9296
|
+
if (doc.version !== 1) return null;
|
|
9297
|
+
if (!doc.keys || typeof doc.keys !== "object") return null;
|
|
9298
|
+
const keys = {};
|
|
9299
|
+
for (const [key, value] of Object.entries(doc.keys)) {
|
|
9300
|
+
if (!key || typeof value !== "string") continue;
|
|
9301
|
+
keys[key] = value;
|
|
9302
|
+
}
|
|
9303
|
+
return {
|
|
9304
|
+
version: 1,
|
|
9305
|
+
updatedAt: typeof doc.updatedAt === "string" ? doc.updatedAt : "",
|
|
9306
|
+
keys
|
|
9307
|
+
};
|
|
9308
|
+
}
|
|
9309
|
+
/**
|
|
9310
|
+
* 序列化为镜像文件文本。
|
|
9311
|
+
* @author ddj 2026年09月28号
|
|
9312
|
+
* @param keys 键值表
|
|
9313
|
+
* @param updatedAt 更新时间(缺省当前时间)
|
|
9314
|
+
* @returns 文件文本
|
|
9315
|
+
*/
|
|
9316
|
+
function serializeUiState(keys, updatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
9317
|
+
return JSON.stringify({
|
|
9318
|
+
version: 1,
|
|
9319
|
+
updatedAt,
|
|
9320
|
+
keys
|
|
9321
|
+
});
|
|
9322
|
+
}
|
|
9323
|
+
/** 同文件写队列尾(串行化并发写入;队列清空即移除条目)。 */
|
|
9324
|
+
const writeChains = /* @__PURE__ */ new Map();
|
|
9325
|
+
/**
|
|
9326
|
+
* 把写任务挂到该文件的队尾串行执行(前序失败不影响后续)。
|
|
9327
|
+
* @author ddj 2026年09月28号
|
|
9328
|
+
* @param file 目标文件绝对路径
|
|
9329
|
+
* @param run 实际写操作
|
|
9330
|
+
* @returns 本次写操作结果
|
|
9331
|
+
*/
|
|
9332
|
+
function chainWrite(file, run) {
|
|
9333
|
+
const next = (writeChains.get(file) ?? Promise.resolve()).then(run, run);
|
|
9334
|
+
const tail = next.then(() => void 0, () => void 0);
|
|
9335
|
+
writeChains.set(file, tail);
|
|
9336
|
+
tail.then(() => {
|
|
9337
|
+
if (writeChains.get(file) === tail) writeChains.delete(file);
|
|
9338
|
+
});
|
|
9339
|
+
return next;
|
|
9340
|
+
}
|
|
9341
|
+
/**
|
|
9342
|
+
* 读取镜像文件的键值表(文件缺失/损坏/不可读 → 空表,静默)。
|
|
9343
|
+
* @author ddj 2026年09月28号
|
|
9344
|
+
* @param file 镜像文件绝对路径
|
|
9345
|
+
* @returns 键值表(localStorage 键 → 字符串原值)
|
|
9346
|
+
*/
|
|
9347
|
+
async function readUiState(file) {
|
|
9348
|
+
try {
|
|
9349
|
+
const doc = parseUiState(await readFile(file, "utf8"));
|
|
9350
|
+
return doc ? doc.keys : {};
|
|
9351
|
+
} catch (error) {
|
|
9352
|
+
return {};
|
|
9353
|
+
}
|
|
9354
|
+
}
|
|
9355
|
+
/**
|
|
9356
|
+
* 合并写入镜像文件(读旧 → 合并补丁 → 容量校验 → 原子替换),同文件写操作串行。
|
|
9357
|
+
* @author ddj 2026年09月28号
|
|
9358
|
+
* @param file 镜像文件绝对路径
|
|
9359
|
+
* @param patch 增量补丁(null/undefined 值删除该键)
|
|
9360
|
+
* @returns ok;超限或写入失败时 ok:false + 中文错误文案
|
|
9361
|
+
*/
|
|
9362
|
+
async function writeUiState(file, patch) {
|
|
9363
|
+
return chainWrite(file, async () => {
|
|
9364
|
+
try {
|
|
9365
|
+
const json = serializeUiState(mergeUiStateKeys(await readUiState(file), patch));
|
|
9366
|
+
if (Buffer$1.byteLength(json, "utf8") > 524288) return {
|
|
9367
|
+
ok: false,
|
|
9368
|
+
error: "界面状态镜像超出容量上限(524288 字节)"
|
|
9369
|
+
};
|
|
9370
|
+
await mkdir(dirname(file), { recursive: true });
|
|
9371
|
+
const tmp = file + ".tmp";
|
|
9372
|
+
await writeFile(tmp, json, "utf8");
|
|
9373
|
+
await rename(tmp, file);
|
|
9374
|
+
return { ok: true };
|
|
9375
|
+
} catch (error) {
|
|
9376
|
+
return {
|
|
9377
|
+
ok: false,
|
|
9378
|
+
error: "界面状态写入失败: " + (error instanceof Error ? error.message : String(error))
|
|
9379
|
+
};
|
|
9380
|
+
}
|
|
9381
|
+
});
|
|
9382
|
+
}
|
|
9217
9383
|
//#endregion
|
|
9218
9384
|
//#region src/debugLog.ts
|
|
9219
9385
|
/**
|
|
@@ -9931,6 +10097,12 @@ function settingsDocPath(configuredPath, requestedPath, platform = process.platf
|
|
|
9931
10097
|
const staleCheckedAt = /* @__PURE__ */ new Map();
|
|
9932
10098
|
/** stale 自动清理最小间隔。 */
|
|
9933
10099
|
const STALE_CHECK_MIN_MS = 1e4;
|
|
10100
|
+
/**
|
|
10101
|
+
* 无会话期诊断的伪 cwd:冷启动早期本插件客户端就会上报界面状态镜像事件
|
|
10102
|
+
* (此时 `sessions` 可能尚未就绪,`requireSession` 会失败),退回本固定值落盘
|
|
10103
|
+
* `logs/debug.<hashOf(该值)>.log`,保证「换 origin 后没恢复」这类问题的诊断不丢。
|
|
10104
|
+
*/
|
|
10105
|
+
const UI_AUDIT_CWD = "ui-state";
|
|
9934
10106
|
/** 记录 → 客户端视图(不含 before 全文,仅长度);远程工作区附远端展示路径(issue #7)。 */
|
|
9935
10107
|
function recView(record, ws = null) {
|
|
9936
10108
|
return {
|
|
@@ -10296,6 +10468,49 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
10296
10468
|
records: out
|
|
10297
10469
|
};
|
|
10298
10470
|
},
|
|
10471
|
+
/**
|
|
10472
|
+
* 界面状态镜像读取:回填换 origin(Web GUI 重启换端口)后丢失的 localStorage 界面状态。
|
|
10473
|
+
* 文件缺失/损坏 → 空表(客户端自然不动作)。
|
|
10474
|
+
* @author ddj 2026年09月28号
|
|
10475
|
+
*/
|
|
10476
|
+
"edrv.uiState.get": async (args) => {
|
|
10477
|
+
const sc = await requireSession(ctx, args.sessionId);
|
|
10478
|
+
if ("err" in sc) return {
|
|
10479
|
+
ok: false,
|
|
10480
|
+
error: sc.err
|
|
10481
|
+
};
|
|
10482
|
+
return {
|
|
10483
|
+
ok: true,
|
|
10484
|
+
keys: await readUiState(uiStateFile(sc.cwd)),
|
|
10485
|
+
globals: await readUiState(uiGlobalsFile())
|
|
10486
|
+
};
|
|
10487
|
+
},
|
|
10488
|
+
/**
|
|
10489
|
+
* 界面状态镜像写入:把客户端改动过的 `edrv.*` 键增量同步到 host 文件(空补丁不落盘)。
|
|
10490
|
+
* 失败时写一条诊断日志(默认静默的客户端 dbg 之外的可查证据),并如实回错误由客户端逐键回退。
|
|
10491
|
+
* @author ddj 2026年09月28号 / 2026年09月29号
|
|
10492
|
+
*/
|
|
10493
|
+
"edrv.uiState.set": async (args) => {
|
|
10494
|
+
const sc = await requireSession(ctx, args.sessionId);
|
|
10495
|
+
if ("err" in sc) return {
|
|
10496
|
+
ok: false,
|
|
10497
|
+
error: sc.err
|
|
10498
|
+
};
|
|
10499
|
+
const tasks = [];
|
|
10500
|
+
if (args.keys && Object.keys(args.keys).length) tasks.push(writeUiState(uiStateFile(sc.cwd), args.keys));
|
|
10501
|
+
if (args.globals && Object.keys(args.globals).length) tasks.push(writeUiState(uiGlobalsFile(), args.globals));
|
|
10502
|
+
const failed = (await Promise.all(tasks)).find((item) => !item.ok);
|
|
10503
|
+
if (failed && !failed.ok) {
|
|
10504
|
+
const keyCount = Object.keys(args.keys ?? {}).length;
|
|
10505
|
+
const globalCount = Object.keys(args.globals ?? {}).length;
|
|
10506
|
+
debugRecord(ctx, sc.cwd, "界面状态镜像写入失败: " + failed.error + "(keys=" + keyCount + " 键, globals=" + globalCount + " 键)", "warn");
|
|
10507
|
+
return {
|
|
10508
|
+
ok: false,
|
|
10509
|
+
error: failed.error
|
|
10510
|
+
};
|
|
10511
|
+
}
|
|
10512
|
+
return { ok: true };
|
|
10513
|
+
},
|
|
10299
10514
|
"edrv.accept": async (args) => {
|
|
10300
10515
|
const sc = await requireSession(ctx, args.sessionId);
|
|
10301
10516
|
if ("err" in sc) return {
|
|
@@ -10784,12 +10999,13 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
10784
10999
|
};
|
|
10785
11000
|
},
|
|
10786
11001
|
"edrv.debug": async (args) => {
|
|
11002
|
+
const text = String(args.text ?? "");
|
|
10787
11003
|
const sc = await requireSession(ctx, args.sessionId);
|
|
10788
|
-
if ("err" in sc)
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
}
|
|
10792
|
-
debugRecord(ctx, sc.cwd,
|
|
11004
|
+
if ("err" in sc) {
|
|
11005
|
+
debugRecord(ctx, UI_AUDIT_CWD, text, args.level);
|
|
11006
|
+
return { ok: true };
|
|
11007
|
+
}
|
|
11008
|
+
debugRecord(ctx, sc.cwd, text, args.level);
|
|
10793
11009
|
return { ok: true };
|
|
10794
11010
|
},
|
|
10795
11011
|
"edrv.dlog.list": async (args) => {
|