dsh-vscode-mode 0.19.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 CHANGED
@@ -460,18 +460,18 @@ DSH 每次启动监听随机端口,`http://127.0.0.1:<port>` 就是浏览器 o
460
460
 
461
461
  官方 `dsh plugin` 方式,三选一。下面以 `web` profile 为例;Desktop 或其他命名
462
462
  profile 请替换为实际目标,不要把示例目录当成当前运行 profile。升级前可阅读
463
- [0.19.0 更新摘要](#更新日志)(侧栏隔离、树虚拟化及只读大文本分页;按需面板与分页依赖宿主对应能力,真实 GUI 尚未验收;升级后须重启 Host 并强制刷新页面);从 0.14.2 或更旧版本升级还须阅读
463
+ [0.19.1 更新摘要](#更新日志)(浏览器安全随机 ID 兼容、界面状态镜像卸载清理、FPGA 语言映射及编辑器默认字号跟随;字号在编辑器创建时读取,真实 pocket/LAN 环境尚未验收;升级后须重启 Host 并强制刷新页面);从 0.14.2 或更旧版本升级还须阅读
464
464
  [0.14.3 数据迁移与回退说明](docs/release-0.14.3.md)。
465
465
 
466
466
  ```bash
467
467
  # ① Git 安装(clone + prepare 构建;推荐打固定 tag)
468
- dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.19.0
468
+ dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.19.1
469
469
 
470
470
  # ② npm 注册表(发布到 npm 后)
471
- dsh plugin --profile web add dsh-vscode-mode@0.19.0
471
+ dsh plugin --profile web add dsh-vscode-mode@0.19.1
472
472
 
473
473
  # ③ GitHub Release tgz 直装(Release asset 可用后)
474
- dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.19.0/dsh-vscode-mode-0.19.0.tgz
474
+ dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.19.1/dsh-vscode-mode-0.19.1.tgz
475
475
  ```
476
476
 
477
477
  > **DSH 版本政策(0.17.0 起)**:六项 DSH peer 仍统一声明为 `*`,另以
@@ -812,6 +812,8 @@ Keep All / Undo All 却是亮的。修复:单文件 Keep / Undo 覆盖冲突
812
812
  完整变更见 [GitHub Releases](https://github.com/Lenonss/DSH_VsCodeMode/releases)。
813
813
  近期关键版本:
814
814
 
815
+ - **v0.19.1**:**浏览器兼容与编辑器默认值修复**——外部打开与编辑器回执优先使用 `crypto.randomUUID()`,不可用或抛错时回落安全的 `getRandomValues()` UUID;无安全随机源时停用对应能力,不拖垮插件启动。界面状态镜像卸载时清理定时器、存储包装与页面监听,阻止迟到水合回填及卸载后重试。补全 Verilog/SystemVerilog 与 Tcl/FPGA 约束扩展名映射和片段语言目录。焦点与被动编辑器创建时统一读取宿主正文字号,非法或不可用时回退 16px,行高保持原比例;不实时修改已创建实例。已在当前本地 Web 验证受影响入口,真实 pocket/LAN 环境仍未验收。升级须重启 DSH Host 并强制刷新页面;发布不自动安装或重启当前实例。
816
+
815
817
  - **v0.19.0**:**侧栏可靠性与大工作区读取**——扩展面板渲染、图标、可见性及徽标异常隔离;规则与调试面板按需加载并复用根模块单例。目录树采用完整逻辑索引与窗口化 DOM,缓存采用独立 LRU 和不续期的 60 秒硬 TTL,修正不完整持久化快照的命中;修复侧栏拖动清理及 SVN 配对请求过期结果。超出整读限制的文本可显式进入只读 UTF-8 分页,通过官方 `readByteRange/stat` 能力读取,不创建可保存的部分文档;旧宿主能力缺失时明确提示不支持。按需加载要求宿主提供 `require.async`。真实 GUI、HTTP 资源及浏览器滚动验收尚未完成,用户接受该发布缺口;发布不自动安装或重启当前实例。详见 [扩展与读取契约](docs/sidebar-extension-contract.md)。
816
818
 
817
819
  - **v0.18.0**:**项目 MCP 按需启动与会话状态**——默认按需连接,不再等待服务握手才完成新会话创建;通过 `discover_project_mcp` 获取目录并连接后使用真实工具 schema,保留原工具执行及作用域隔离。支持显式后台预热、逐会话隔离或工作区共享连接(共享服务内部状态须自行确认),冷启动并发上限为 2,共享调用串行、最后租约释放后保温 60 秒。会话列表新增项目 MCP 初始化阶段与耗时详情,状态轮询只读内存,不激活历史会话;初始化完成不代表连接健康已验证。**编辑器**:计划预览页签并入分组页签行,工具固定在行尾,关闭计划恢复先前磁盘页签;单一定义返回前尝试预热目标 Monaco model,减少 Ctrl+hover 定义预览的 `Model not found` 异常。升级须重启 DSH Host 并强制刷新页面;发布本身不更新运行实例。
package/lib/client.js CHANGED
@@ -2530,6 +2530,27 @@ window.__ModuleLoader__.load({
2530
2530
  window.dispatchEvent(new CustomEvent("edrv:show-launcher", { detail: { tab } }));
2531
2531
  }
2532
2532
  //#endregion
2533
+ //#region src/client/requestId.ts
2534
+ /** @public @author ddj 2026年10月23号
2535
+ * Generate an unpredictable UUID, including on insecure LAN HTTP origins.
2536
+ * @returns UUID v4, or undefined when secure randomness is unavailable.
2537
+ */
2538
+ function createRequestId() {
2539
+ try {
2540
+ if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
2541
+ } catch {}
2542
+ try {
2543
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
2544
+ globalThis.crypto.getRandomValues(bytes);
2545
+ bytes[6] = bytes[6] & 15 | 64;
2546
+ bytes[8] = bytes[8] & 63 | 128;
2547
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2548
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
2549
+ } catch {
2550
+ return;
2551
+ }
2552
+ }
2553
+ //#endregion
2533
2554
  //#region src/client/openReceipt.ts
2534
2555
  const waiters = /* @__PURE__ */ new Map();
2535
2556
  /** @public @author ddj 2026年09月28号
@@ -2564,8 +2585,10 @@ window.__ModuleLoader__.load({
2564
2585
  */
2565
2586
  function requestOpen(path, line, column, sessionId = "", options = {}) {
2566
2587
  if (typeof window === "undefined" || !sessionId || options.signal?.aborted) return Promise.resolve(false);
2588
+ const requestId = createRequestId();
2589
+ if (!requestId) return Promise.resolve(false);
2567
2590
  const request = {
2568
- requestId: crypto.randomUUID(),
2591
+ requestId,
2569
2592
  sessionId,
2570
2593
  path,
2571
2594
  line,
@@ -4004,6 +4027,19 @@ window.__ModuleLoader__.load({
4004
4027
  ];
4005
4028
  /** 已应用的官方主题序号(模块级计数,保证主题 id 每次唯一)。 */
4006
4029
  let officialSeq = 0;
4030
+ /** @public @author ddj 2026年10月23号
4031
+ * Read the host content font on editor creation, preserving the original line density.
4032
+ * @returns Monaco font options; invalid or unavailable CSS falls back to 16px.
4033
+ */
4034
+ function editorFont() {
4035
+ const raw = readCssVar("--dsh-content-font-size") ?? "";
4036
+ const parsed = /^\d+(?:\.\d+)?px$/i.test(raw) ? Number(raw.slice(0, -2)) : NaN;
4037
+ const fontSize = Number.isFinite(parsed) && parsed > 0 ? parsed : 16;
4038
+ return {
4039
+ fontSize,
4040
+ lineHeight: Math.round(fontSize * 21 / 16)
4041
+ };
4042
+ }
4007
4043
  /**
4008
4044
  * 读官方 CSS 变量(先 body 后文档根;未定义/空值/未解析 var() 返回 undefined)。
4009
4045
  * 依据运行态实测:DSH 的 --dsw-alias-* 定义在 body 上,仅 --shiki-token-* 在根。
@@ -4419,6 +4455,14 @@ window.__ModuleLoader__.load({
4419
4455
  plist: "xml",
4420
4456
  lua51: "lua",
4421
4457
  luac: "lua",
4458
+ v: "verilog",
4459
+ vh: "verilog",
4460
+ sv: "systemverilog",
4461
+ svh: "systemverilog",
4462
+ tcl: "tcl",
4463
+ xdc: "tcl",
4464
+ sdc: "tcl",
4465
+ ucf: "tcl",
4422
4466
  gitattributes: "ini",
4423
4467
  editorconfig: "ini",
4424
4468
  env: "ini",
@@ -12048,20 +12092,29 @@ window.__ModuleLoader__.load({
12048
12092
  let suppressed = 0;
12049
12093
  let done = false;
12050
12094
  let installed = false;
12095
+ let disposed = false;
12096
+ let restoreStorage;
12051
12097
  let lastError = "";
12052
12098
  /** 水合尝试次数(失败重试;超过 `HYDRATE_RETRY_MAX` 放弃并解禁)。 */
12053
12099
  let attempts = 0;
12100
+ /** 重试定时器(与水合无关的 flush 去抖计时器分开,避免互相取消)。 */
12101
+ let retryTimer = null;
12054
12102
  let settleDone = false;
12055
12103
  /** 记入待推送批次并安排去抖上送(水合期间或非镜像键 → 跳过)。 */
12056
12104
  function queue(key, value) {
12057
- if (suppressed > 0 || !isMirroredKey(key)) return;
12105
+ if (disposed || suppressed > 0 || !isMirroredKey(key)) return;
12058
12106
  const bucket = isGlobalKey(key) ? pending.globals : pending.keys;
12059
12107
  bucket[key] = value;
12060
12108
  if (timer !== null) cancel(timer);
12061
- timer = schedule(() => {
12109
+ try {
12110
+ timer = schedule(() => {
12111
+ timer = null;
12112
+ if (!disposed) flush();
12113
+ }, 400);
12114
+ } catch (error) {
12062
12115
  timer = null;
12063
- flush();
12064
- }, 400);
12116
+ log("界面状态调度失败: " + String(error));
12117
+ }
12065
12118
  }
12066
12119
  /** 上送一批补丁(失败重试一次)。 */
12067
12120
  async function pushBatch(payload) {
@@ -12114,7 +12167,7 @@ window.__ModuleLoader__.load({
12114
12167
  }
12115
12168
  /** @private @author ddj 2026年10月12号 @returns Durable refresh snapshot. @throws When state is unreadable, unsettled or rejected by Host. @sideEffects Resends current local state after waiting for inflight writes. */
12116
12169
  async function flushStrict() {
12117
- if (!settleDone || !installed) throw new Error("界面状态镜像尚未就绪");
12170
+ if (disposed || !settleDone || !installed) throw new Error("界面状态镜像尚未就绪");
12118
12171
  if (inflight) await inflight;
12119
12172
  for (let index = 0; index < storage.length; index++) {
12120
12173
  const key = storage.key(index);
@@ -12176,7 +12229,7 @@ window.__ModuleLoader__.load({
12176
12229
  * 未结束前界面不得写存档,否则空初值会覆盖 host 上的真实状态。
12177
12230
  */
12178
12231
  function settle() {
12179
- if (settleDone) return;
12232
+ if (disposed || settleDone) return;
12180
12233
  settleDone = true;
12181
12234
  emitReady();
12182
12235
  report("水合阶段结束(就绪;" + (done ? "已回填" : "未回填,退回纯 localStorage") + ")");
@@ -12186,9 +12239,11 @@ window.__ModuleLoader__.load({
12186
12239
  * @returns 本次回填键数(失败/重试中为 0)
12187
12240
  */
12188
12241
  async function hydrate() {
12242
+ if (disposed) return 0;
12189
12243
  try {
12190
12244
  report("水合开始(origin=" + originOf() + ",第 " + (attempts + 1) + " 次)");
12191
12245
  const snapshot = await deps.fetch(deps.sessionIdOf?.());
12246
+ if (disposed) return 0;
12192
12247
  const seeded = seedLocal();
12193
12248
  const filled = applyHydration(snapshot);
12194
12249
  const stored = Object.keys({
@@ -12200,44 +12255,81 @@ window.__ModuleLoader__.load({
12200
12255
  if (seeded > 0) {
12201
12256
  const lost = await flush();
12202
12257
  report("首次搬运输送完成:丢弃 " + lost + " 键");
12203
- if (lost === 0) markSeeded();
12258
+ if (!disposed && lost === 0) markSeeded();
12204
12259
  }
12205
12260
  settle();
12206
12261
  return filled;
12207
12262
  } catch (error) {
12263
+ if (disposed) return 0;
12208
12264
  attempts++;
12209
12265
  if (attempts < 8) {
12210
12266
  report("水合失败(第 " + attempts + " 次,800ms 后重试): " + String(error), "warn");
12211
- schedule(() => {
12212
- hydrate();
12213
- }, 800);
12214
- return 0;
12267
+ try {
12268
+ retryTimer = schedule(() => {
12269
+ retryTimer = null;
12270
+ hydrate();
12271
+ }, 800);
12272
+ return 0;
12273
+ } catch (scheduleError) {
12274
+ report("水合重试调度失败: " + String(scheduleError), "warn");
12275
+ }
12215
12276
  }
12216
12277
  report("水合放弃(重试 " + attempts + " 次仍失败): " + String(error), "warn");
12217
12278
  settle();
12218
12279
  return 0;
12219
12280
  }
12220
12281
  }
12282
+ /** @private @author ddj 2026年10月23号 Release timers and only this instance's storage hooks. */
12283
+ function dispose() {
12284
+ if (disposed) return;
12285
+ disposed = true;
12286
+ if (timer !== null) cancel(timer);
12287
+ if (retryTimer !== null) cancel(retryTimer);
12288
+ timer = null;
12289
+ retryTimer = null;
12290
+ restoreStorage?.();
12291
+ installed = false;
12292
+ }
12221
12293
  return {
12294
+ dispose,
12222
12295
  install() {
12296
+ if (disposed) return false;
12223
12297
  if (installed) return true;
12224
12298
  try {
12225
12299
  const proto = Object.getPrototypeOf(storage);
12226
- const rawSet = proto?.setItem;
12227
- const rawRemove = proto?.removeItem;
12300
+ const rawSet = storage.setItem ?? proto?.setItem;
12301
+ const rawRemove = storage.removeItem ?? proto?.removeItem;
12228
12302
  if (typeof rawSet !== "function" || typeof rawRemove !== "function") return false;
12229
- storage.setItem = function(key, value) {
12303
+ const setDescriptor = Object.getOwnPropertyDescriptor(storage, "setItem");
12304
+ const removeDescriptor = Object.getOwnPropertyDescriptor(storage, "removeItem");
12305
+ /** @private @author ddj 2026年10月23号 @param key Storage key. @param value Stored text. @sideEffects Queues a mirror update. */
12306
+ function wrappedSet(key, value) {
12230
12307
  rawSet.call(storage, key, value);
12231
12308
  queue(String(key), String(value));
12232
- };
12233
- storage.removeItem = function(key) {
12309
+ }
12310
+ /** @private @author ddj 2026年10月23号 @param key Storage key. @sideEffects Queues a mirror deletion. */
12311
+ function wrappedRemove(key) {
12234
12312
  rawRemove.call(storage, key);
12235
12313
  queue(String(key), null);
12314
+ }
12315
+ restoreStorage = () => {
12316
+ if (storage.setItem === wrappedSet) {
12317
+ if (setDescriptor) Object.defineProperty(storage, "setItem", setDescriptor);
12318
+ else delete storage.setItem;
12319
+ }
12320
+ if (storage.removeItem === wrappedRemove) {
12321
+ if (removeDescriptor) Object.defineProperty(storage, "removeItem", removeDescriptor);
12322
+ else delete storage.removeItem;
12323
+ }
12236
12324
  };
12325
+ storage.setItem = wrappedSet;
12326
+ storage.removeItem = wrappedRemove;
12237
12327
  installed = true;
12238
12328
  report("镜像已包装 localStorage 写口");
12239
12329
  return true;
12240
12330
  } catch (error) {
12331
+ restoreStorage?.();
12332
+ installed = false;
12241
12333
  report("界面状态镜像包装失败(退回纯 localStorage): " + String(error), "warn");
12242
12334
  return false;
12243
12335
  }
@@ -12347,6 +12439,9 @@ window.__ModuleLoader__.load({
12347
12439
  fetch: fetchViaRpc,
12348
12440
  sessionIdOf,
12349
12441
  schedule,
12442
+ cancel: (handle) => {
12443
+ if (typeof handle === "function") handle();
12444
+ },
12350
12445
  report: (text, level) => reportUiStateEvent(text, level),
12351
12446
  log: (text) => {
12352
12447
  try {
@@ -12359,17 +12454,29 @@ window.__ModuleLoader__.load({
12359
12454
  return false;
12360
12455
  }
12361
12456
  singleton = mirror;
12362
- try {
12363
- if (typeof window !== "undefined") window.addEventListener("pagehide", () => {
12364
- mirror.flush();
12365
- });
12366
- } catch (error) {}
12457
+ const pageHost = typeof window === "undefined" ? void 0 : window;
12458
+ /** @private @author ddj 2026年10月23号 Persist pending changes when the page is hidden. */
12459
+ function onPageHide() {
12460
+ mirror.flush();
12461
+ }
12462
+ ctx.effect(() => {
12463
+ pageHost?.addEventListener("pagehide", onPageHide);
12464
+ return () => {
12465
+ pageHost?.removeEventListener("pagehide", onPageHide);
12466
+ mirror.dispose();
12467
+ if (singleton !== mirror) return;
12468
+ singleton = null;
12469
+ hydrating = null;
12470
+ uiReady = false;
12471
+ reporter = null;
12472
+ };
12473
+ }, "vscode-mode: ui state lifecycle");
12367
12474
  hydrating = mirror.hydrate().then((filled) => {
12368
- if (filled > 0 || mirror.settled()) uiReady = true;
12475
+ if (singleton === mirror && (filled > 0 || mirror.settled())) uiReady = true;
12369
12476
  return filled;
12370
12477
  });
12371
12478
  hydrating.finally(() => {
12372
- if (mirror.settled()) {
12479
+ if (singleton === mirror && mirror.settled()) {
12373
12480
  uiReady = true;
12374
12481
  hydrating = null;
12375
12482
  }
@@ -16438,7 +16545,10 @@ window.__ModuleLoader__.load({
16438
16545
  "shell",
16439
16546
  "sql",
16440
16547
  "swift",
16548
+ "systemverilog",
16549
+ "tcl",
16441
16550
  "typescript",
16551
+ "verilog",
16442
16552
  "xml",
16443
16553
  "yaml"
16444
16554
  ];
@@ -25174,8 +25284,7 @@ window.__ModuleLoader__.load({
25174
25284
  language: "plaintext",
25175
25285
  theme: themeNameOf(),
25176
25286
  fontFamily: "var(--ds-font-family-code, ui-monospace, monospace)",
25177
- fontSize: 16,
25178
- lineHeight: 21,
25287
+ ...editorFont(),
25179
25288
  minimap: {
25180
25289
  enabled: !side,
25181
25290
  scale: 1
@@ -27030,8 +27139,7 @@ window.__ModuleLoader__.load({
27030
27139
  scrollBeyondLastLine: false,
27031
27140
  glyphMargin: true,
27032
27141
  fontFamily: "var(--ds-font-family-code, ui-monospace, monospace)",
27033
- fontSize: 16,
27034
- lineHeight: 21,
27142
+ ...editorFont(),
27035
27143
  lineDecorationsWidth: 16,
27036
27144
  padding: { top: layoutRef.current === "side" ? 6 : 8 }
27037
27145
  });
@@ -32315,7 +32423,12 @@ window.__ModuleLoader__.load({
32315
32423
  if (typeof window === "undefined") return () => {};
32316
32424
  const marker = window;
32317
32425
  if (typeof marker.__edrvExtPoll === "function") marker.__edrvExtPoll();
32318
- const clientId = crypto.randomUUID();
32426
+ const generatedId = createRequestId();
32427
+ if (!generatedId) {
32428
+ log$1.warn("安全随机源不可用,已停用外部打开心跳");
32429
+ return () => {};
32430
+ }
32431
+ const clientId = generatedId;
32319
32432
  let disposed = false;
32320
32433
  let polling = false;
32321
32434
  let executing = false;