dsh-vscode-workbench 0.1.1 → 0.1.2

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.en.md CHANGED
@@ -34,13 +34,6 @@ The workbench mounts through the official `sidebar.footer.action` and `shell.ove
34
34
 
35
35
  ## Installation
36
36
 
37
- Install the aggregate package for the complete workbench with terminal integration:
38
-
39
- ```bash
40
- dsh plugin --profile web add dsh-vscode-plugin
41
- ```
42
-
43
- Install only the workbench and its extension API:
44
37
 
45
38
  ```bash
46
39
  dsh plugin --profile web add dsh-vscode-workbench
@@ -58,9 +51,6 @@ dsh plugin --profile web add dsh-vscode-workbench
58
51
 
59
52
  Terminal shortcuts are registered by `dsh-vscode-plugin`.
60
53
 
61
- ## Extension API
62
-
63
- The browser exposes a `vscodeWorkbench` service for registering bottom panels and commands and controlling workbench visibility. `dsh-vscode-plugin` uses this API to mount `@xterm/xterm`.
64
54
 
65
55
  ## Security
66
56
 
package/README.md CHANGED
@@ -33,14 +33,6 @@
33
33
 
34
34
  ## 安装
35
35
 
36
- 推荐安装包含终端适配器的聚合包:
37
-
38
- ```bash
39
- dsh plugin --profile web add dsh-vscode-plugin
40
- ```
41
-
42
- 仅使用工作台和扩展 API:
43
-
44
36
  ```bash
45
37
  dsh plugin --profile web add dsh-vscode-workbench
46
38
  ```
@@ -57,9 +49,6 @@ dsh plugin --profile web add dsh-vscode-workbench
57
49
 
58
50
  终端快捷键由 `dsh-vscode-plugin` 注册。
59
51
 
60
- ## 扩展
61
-
62
- 浏览器端提供 `vscodeWorkbench` 服务,可注册底部面板和命令,并控制工作台与面板显隐。`dsh-vscode-plugin` 使用该接口挂载 `@xterm/xterm`。
63
52
 
64
53
  ## 安全
65
54
 
package/lib/client.js CHANGED
@@ -4191,6 +4191,17 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4191
4191
  //#endregion
4192
4192
  //#region src/client/MonacoEditor.tsx
4193
4193
  let loading;
4194
+ /** Monaco chrome used by both editors so only the narrow native scrollbar remains. */
4195
+ const WORKBENCH_EDITOR_CHROME = {
4196
+ minimap: { enabled: false },
4197
+ renderOverviewRuler: false,
4198
+ overviewRulerLanes: 0,
4199
+ hideCursorInOverviewRuler: true,
4200
+ scrollbar: {
4201
+ verticalScrollbarSize: 10,
4202
+ horizontalScrollbarSize: 10
4203
+ }
4204
+ };
4194
4205
  function loadMonaco() {
4195
4206
  loading ??= new Promise((resolve, reject) => {
4196
4207
  const start = () => {
@@ -4233,9 +4244,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4233
4244
  renderSideBySide: props.mode === "split",
4234
4245
  theme: props.theme === "dark" ? "vs-dark" : "vs",
4235
4246
  fontSize: 14,
4236
- minimap: { enabled: false },
4247
+ ...WORKBENCH_EDITOR_CHROME,
4237
4248
  originalEditable: false,
4238
- renderOverviewRuler: true,
4239
4249
  ignoreTrimWhitespace: false
4240
4250
  });
4241
4251
  instance.setModel({
@@ -4300,7 +4310,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4300
4310
  automaticLayout: true,
4301
4311
  theme: props.theme === "dark" ? "vs-dark" : "vs",
4302
4312
  fontSize: 14,
4303
- minimap: { enabled: true },
4313
+ ...WORKBENCH_EDITOR_CHROME,
4304
4314
  wordWrap: "off"
4305
4315
  });
4306
4316
  editor.current = instance;
@@ -4427,6 +4437,37 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4427
4437
  });
4428
4438
  }
4429
4439
  //#endregion
4440
+ //#region src/client/workspaceState.ts
4441
+ /** Remove editor state that belongs to the previous workspace. */
4442
+ function resetWorkspaceDocuments() {
4443
+ return {
4444
+ tabs: [],
4445
+ active: void 0,
4446
+ error: void 0
4447
+ };
4448
+ }
4449
+ /** Identifies responses that still belong to the active workspace request. */
4450
+ var WorkspaceRequestScope = class {
4451
+ epoch = 0;
4452
+ /** Begin loading another workspace and invalidate earlier work. */
4453
+ begin() {
4454
+ this.epoch += 1;
4455
+ return this.epoch;
4456
+ }
4457
+ /** Return the active workspace request identifier. */
4458
+ current() {
4459
+ return this.epoch;
4460
+ }
4461
+ /** Invalidate an active request while preserving any newer request. */
4462
+ invalidate(epoch) {
4463
+ if (this.epoch === epoch) this.epoch += 1;
4464
+ }
4465
+ /** Return whether a response may update the current workspace. */
4466
+ isCurrent(epoch) {
4467
+ return epoch === this.epoch;
4468
+ }
4469
+ };
4470
+ //#endregion
4430
4471
  //#region src/client/Workbench.tsx
4431
4472
  /** Return the basename shown in a tab while preserving its full internal path. */
4432
4473
  function tabLabel(tab) {
@@ -5234,6 +5275,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5234
5275
  const [chatWidth, setChatWidth] = (0, react.useState)(() => Math.max(400, Math.min(720, Math.round(Number(localStorage.getItem("dvw:dsh-chat-width")) || 420))));
5235
5276
  const [chatVisible, setChatVisible] = (0, react.useState)(() => localStorage.getItem("dvw:dsh-chat-visible") !== "closed");
5236
5277
  const [outlineVisible, setOutlineVisible] = (0, react.useState)(() => localStorage.getItem("dvw:outline") !== "closed");
5278
+ const tabsRef = (0, react.useRef)(tabs);
5279
+ tabsRef.current = tabs;
5280
+ const workspaceRequests = (0, react.useRef)(new WorkspaceRequestScope());
5237
5281
  const dshDark = useDshDarkTheme();
5238
5282
  const [theme, setTheme] = (0, react.useState)(() => {
5239
5283
  const saved = localStorage.getItem("dvw:theme");
@@ -5256,9 +5300,69 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5256
5300
  window.addEventListener("keydown", onKey, true);
5257
5301
  return () => window.removeEventListener("keydown", onKey, true);
5258
5302
  }, [snapshot.visible]);
5303
+ const openForWorkspace = async (targetSessionId, epoch, path, line, column) => {
5304
+ if (!workspaceRequests.current.isCurrent(epoch)) return;
5305
+ if (tabsRef.current.find((tab) => tab.path === path)) {
5306
+ setActive(path);
5307
+ setTabs((rows) => rows.map((tab) => tab.path === path ? {
5308
+ ...tab,
5309
+ line,
5310
+ column
5311
+ } : tab));
5312
+ return;
5313
+ }
5314
+ if (/\.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)$/i.test(path)) {
5315
+ const imageUrl = `/dsh-vscode/file?sessionId=${encodeURIComponent(targetSessionId)}&path=${encodeURIComponent(path)}`;
5316
+ if (!workspaceRequests.current.isCurrent(epoch)) return;
5317
+ setTabs((rows) => workspaceRequests.current.isCurrent(epoch) ? [...rows.filter((tab) => tab.path !== path), {
5318
+ path,
5319
+ content: "",
5320
+ draft: "",
5321
+ revision: "",
5322
+ size: 0,
5323
+ binary: false,
5324
+ tooLarge: false,
5325
+ dirty: false,
5326
+ imageUrl
5327
+ }] : rows);
5328
+ setActive(path);
5329
+ return;
5330
+ }
5331
+ try {
5332
+ const doc = await api("fs.read", {
5333
+ sessionId: targetSessionId,
5334
+ path
5335
+ });
5336
+ if (!workspaceRequests.current.isCurrent(epoch)) return;
5337
+ if (doc.binary || doc.tooLarge) {
5338
+ setError(doc.binary ? "二进制文件不能在文本编辑器中打开" : `文件超过 5 MiB:${path}`);
5339
+ return;
5340
+ }
5341
+ setTabs((rows) => workspaceRequests.current.isCurrent(epoch) ? [...rows.filter((tab) => tab.path !== path), {
5342
+ ...doc,
5343
+ draft: doc.content,
5344
+ dirty: false,
5345
+ line,
5346
+ column,
5347
+ preview: /\.md$/i.test(path)
5348
+ }] : rows);
5349
+ setActive(path);
5350
+ } catch (error) {
5351
+ if (workspaceRequests.current.isCurrent(epoch)) setError(String(error));
5352
+ }
5353
+ };
5259
5354
  (0, react.useEffect)(() => {
5260
5355
  if (!snapshot.visible || sessionId === void 0) return;
5261
- api("workspace", { sessionId }).then((value) => {
5356
+ const epoch = workspaceRequests.current.begin();
5357
+ const cleared = resetWorkspaceDocuments();
5358
+ setWorkspace(void 0);
5359
+ setTabs(cleared.tabs);
5360
+ tabsRef.current = cleared.tabs;
5361
+ setActive(cleared.active);
5362
+ setError(cleared.error);
5363
+ const controller = new AbortController();
5364
+ api("workspace", { sessionId }, controller.signal).then((value) => {
5365
+ if (!workspaceRequests.current.isCurrent(epoch)) return;
5262
5366
  setWorkspace(value);
5263
5367
  const saved = localStorage.getItem(`dvw:${value.cwd}`);
5264
5368
  if (saved) try {
@@ -5266,9 +5370,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5266
5370
  if (parsed.view) service.show(parsed.view);
5267
5371
  if (typeof parsed.panelHeight === "number") setPanelHeight(parsed.panelHeight);
5268
5372
  if (typeof parsed.sideWidth === "number") setSideWidth(Math.max(210, Math.min(640, Math.round(parsed.sideWidth))));
5269
- for (const path of parsed.paths ?? []) open(path);
5373
+ for (const path of parsed.paths ?? []) openForWorkspace(sessionId, epoch, path);
5270
5374
  } catch {}
5271
- }).catch((error) => setError(String(error)));
5375
+ }).catch((error) => {
5376
+ if (!controller.signal.aborted && workspaceRequests.current.isCurrent(epoch)) setError(String(error));
5377
+ });
5378
+ return () => {
5379
+ controller.abort();
5380
+ workspaceRequests.current.invalidate(epoch);
5381
+ };
5272
5382
  }, [snapshot.visible, sessionId]);
5273
5383
  (0, react.useEffect)(() => {
5274
5384
  if (workspace) localStorage.setItem(`dvw:${workspace.cwd}`, JSON.stringify({
@@ -5299,52 +5409,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5299
5409
  }, [snapshot.visible, sessionId]);
5300
5410
  const open = async (path, line, column) => {
5301
5411
  if (sessionId === void 0) return;
5302
- if (tabs.find((t) => t.path === path)) {
5303
- setActive(path);
5304
- setTabs((rows) => rows.map((t) => t.path === path ? {
5305
- ...t,
5306
- line,
5307
- column
5308
- } : t));
5309
- return;
5310
- }
5311
- if (/\.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)$/i.test(path)) {
5312
- const imageUrl = `/dsh-vscode/file?sessionId=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(path)}`;
5313
- setTabs((rows) => [...rows, {
5314
- path,
5315
- content: "",
5316
- draft: "",
5317
- revision: "",
5318
- size: 0,
5319
- binary: false,
5320
- tooLarge: false,
5321
- dirty: false,
5322
- imageUrl
5323
- }]);
5324
- setActive(path);
5325
- return;
5326
- }
5327
- try {
5328
- const doc = await api("fs.read", {
5329
- sessionId,
5330
- path
5331
- });
5332
- if (doc.binary || doc.tooLarge) {
5333
- setError(doc.binary ? "二进制文件不能在文本编辑器中打开" : `文件超过 5 MiB:${path}`);
5334
- return;
5335
- }
5336
- setTabs((rows) => [...rows, {
5337
- ...doc,
5338
- draft: doc.content,
5339
- dirty: false,
5340
- line,
5341
- column,
5342
- preview: /\.md$/i.test(path)
5343
- }]);
5344
- setActive(path);
5345
- } catch (error) {
5346
- setError(String(error));
5347
- }
5412
+ await openForWorkspace(sessionId, workspaceRequests.current.current(), path, line, column);
5348
5413
  };
5349
5414
  (0, react.useEffect)(() => {
5350
5415
  const listener = (event) => void open(event.detail);
@@ -5964,10 +6029,13 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
5964
6029
  }
5965
6030
  //#endregion
5966
6031
  //#region src/client/index.tsx
5967
- const Overlay = ({ service, useSessions }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Workbench, {
5968
- service,
5969
- useSessions
5970
- });
6032
+ const Overlay = ({ service, useSessions }) => {
6033
+ const sessionId = useSessions((state) => state.current);
6034
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Workbench, {
6035
+ service,
6036
+ useSessions
6037
+ }, sessionId ?? "no-session");
6038
+ };
5971
6039
  const Footer = ({ service, wide }) => {
5972
6040
  const marker = (0, react.useRef)(null);
5973
6041
  const [target, setTarget] = (0, react.useState)();