dsh-vscode-workbench 0.1.5 → 0.1.7

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 CHANGED
@@ -115,6 +115,13 @@ window.__ModuleLoader__.load({
115
115
  };
116
116
  //#endregion
117
117
  //#region src/client/api.ts
118
+ var ApiError = class extends Error {
119
+ code;
120
+ constructor(code, message) {
121
+ super(message);
122
+ this.code = code;
123
+ }
124
+ };
118
125
  async function api(method, body, signal) {
119
126
  const response = await fetch(`/dsh-vscode/api/${method}`, {
120
127
  method: "POST",
@@ -123,7 +130,7 @@ window.__ModuleLoader__.load({
123
130
  signal
124
131
  });
125
132
  const answer = await response.json();
126
- if (!response.ok || !answer.ok) throw new Error(answer.error?.message ?? `request failed (${response.status})`);
133
+ if (!response.ok || !answer.ok) throw new ApiError(answer.error?.code ?? "request-failed", answer.error?.message ?? `request failed (${response.status})`);
127
134
  return answer.value;
128
135
  }
129
136
  //#endregion
@@ -4468,6 +4475,99 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4468
4475
  };
4469
4476
  //#endregion
4470
4477
  //#region src/client/Workbench.tsx
4478
+ /** Calculate a panel width from a horizontal pointer movement. */
4479
+ function resizedPanelWidth(startWidth, startX, nextX, direction, min, max) {
4480
+ return Math.max(min, Math.min(max, Math.round(startWidth + (nextX - startX) * direction)));
4481
+ }
4482
+ function PanelResizer({ className, label, width, min, max, defaultWidth, direction, onWidth }) {
4483
+ const [dragging, setDragging] = (0, react.useState)(false);
4484
+ const stopDragging = (0, react.useRef)(() => {});
4485
+ (0, react.useEffect)(() => () => stopDragging.current(), []);
4486
+ const stop = () => stopDragging.current();
4487
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4488
+ className,
4489
+ "data-dragging": dragging || void 0,
4490
+ role: "separator",
4491
+ "aria-label": label,
4492
+ "aria-orientation": "vertical",
4493
+ "aria-valuemin": min,
4494
+ "aria-valuemax": max,
4495
+ "aria-valuenow": Math.round(width),
4496
+ tabIndex: 0,
4497
+ title: `${label},双击恢复默认`,
4498
+ onDoubleClick: () => onWidth(defaultWidth),
4499
+ onKeyDown: (event) => {
4500
+ if (event.key === "Home") {
4501
+ event.preventDefault();
4502
+ onWidth(defaultWidth);
4503
+ return;
4504
+ }
4505
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
4506
+ event.preventDefault();
4507
+ const pointerDelta = event.key === "ArrowRight" ? 10 : -10;
4508
+ onWidth(Math.max(min, Math.min(max, Math.round(width + pointerDelta * direction))));
4509
+ },
4510
+ onPointerDown: (event) => {
4511
+ if (event.button !== 0) return;
4512
+ event.preventDefault();
4513
+ stop();
4514
+ const startX = event.clientX;
4515
+ const startWidth = width;
4516
+ event.currentTarget.setPointerCapture?.(event.pointerId);
4517
+ const previousCursor = document.body.style.cursor;
4518
+ const previousUserSelect = document.body.style.userSelect;
4519
+ document.body.style.cursor = "ew-resize";
4520
+ document.body.style.userSelect = "none";
4521
+ setDragging(true);
4522
+ const move = (next) => onWidth(resizedPanelWidth(startWidth, startX, next.clientX, direction, min, max));
4523
+ const cleanup = (commit = false, nextX = startX) => {
4524
+ window.removeEventListener("pointermove", move);
4525
+ window.removeEventListener("pointerup", up);
4526
+ window.removeEventListener("pointercancel", cancel);
4527
+ window.removeEventListener("blur", cancel);
4528
+ document.body.style.cursor = previousCursor;
4529
+ document.body.style.userSelect = previousUserSelect;
4530
+ setDragging(false);
4531
+ stopDragging.current = () => {};
4532
+ if (!commit) return;
4533
+ onWidth(Math.max(min, Math.min(max, Math.round(startWidth + (nextX - startX) * direction))));
4534
+ };
4535
+ const up = (next) => cleanup(true, next.clientX);
4536
+ const cancel = () => cleanup();
4537
+ stopDragging.current = cancel;
4538
+ window.addEventListener("pointermove", move);
4539
+ window.addEventListener("pointerup", up);
4540
+ window.addEventListener("pointercancel", cancel);
4541
+ window.addEventListener("blur", cancel);
4542
+ }
4543
+ });
4544
+ }
4545
+ function unloadedTab(path) {
4546
+ return {
4547
+ path,
4548
+ content: "",
4549
+ draft: "",
4550
+ revision: "",
4551
+ size: 0,
4552
+ binary: false,
4553
+ tooLarge: false,
4554
+ dirty: false,
4555
+ loaded: false,
4556
+ preview: /\.md$/i.test(path)
4557
+ };
4558
+ }
4559
+ /** Restore tab headers without reading their file contents. */
4560
+ function restoreTabPaths(paths) {
4561
+ return [...new Set(paths)].map(unloadedTab);
4562
+ }
4563
+ /** Return whether file changes require reloading one visible directory. */
4564
+ function directoryAffected(directory, changedPaths) {
4565
+ return changedPaths.some((changed) => {
4566
+ const normalized = changed.replace(/\\/g, "/");
4567
+ const parent = normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : "";
4568
+ return normalized === directory || parent === directory;
4569
+ });
4570
+ }
4471
4571
  /** Return the basename shown in a tab while preserving its full internal path. */
4472
4572
  function tabLabel(tab) {
4473
4573
  const path = tab.title ?? tab.diffSourcePath ?? tab.path.replace(/^diff:/, "");
@@ -4497,7 +4597,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4497
4597
  }, []);
4498
4598
  return dark;
4499
4599
  }
4500
- function Tree({ sessionId, path, depth, expanded, selected, selectedDirectory, statuses, onToggle, onSelectDirectory, onOpen, onChange, onUpload }) {
4600
+ function Tree({ sessionId, path, depth, expanded, selected, selectedDirectory, statuses, onToggle, onSelectDirectory, onOpen, onChange, onUpload, creating, onCreatingChange, onCreatingCancel, onCreatingSubmit }) {
4501
4601
  const [rows, setRows] = (0, react.useState)([]);
4502
4602
  const [error, setError] = (0, react.useState)();
4503
4603
  const [dropTarget, setDropTarget] = (0, react.useState)(false);
@@ -4512,7 +4612,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4512
4612
  }, [sessionId, path]);
4513
4613
  (0, react.useEffect)(load, [load]);
4514
4614
  (0, react.useEffect)(() => {
4515
- const handler = () => load();
4615
+ const handler = (event) => {
4616
+ const paths = event.detail;
4617
+ if (paths === void 0 || directoryAffected(path, paths)) load();
4618
+ };
4516
4619
  window.addEventListener("dvw-files-changed", handler);
4517
4620
  return () => window.removeEventListener("dvw-files-changed", handler);
4518
4621
  }, [load]);
@@ -4579,6 +4682,41 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4579
4682
  className: "dvw-error",
4580
4683
  children: error
4581
4684
  }),
4685
+ creating?.directory === path && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4686
+ className: "dvw-tree-row dvw-tree-create-row",
4687
+ style: { paddingLeft: 6 + depth * 13 },
4688
+ children: [
4689
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dvw-chevron" }),
4690
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
4691
+ name: creating.kind === "directory" ? "folder" : "new-file",
4692
+ className: `dvw-file-icon ${creating.kind === "directory" ? "folder" : ""}`
4693
+ }),
4694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4695
+ className: "dvw-tree-create-input",
4696
+ value: creating.value,
4697
+ autoFocus: true,
4698
+ "aria-label": creating.kind === "directory" ? "新建文件夹名称" : "新建文件名称",
4699
+ "aria-invalid": creating.error !== void 0,
4700
+ placeholder: creating.kind === "directory" ? "文件夹名称" : "文件名称",
4701
+ onChange: (event) => onCreatingChange(event.target.value),
4702
+ onKeyDown: (event) => {
4703
+ if (event.key === "Enter") {
4704
+ event.preventDefault();
4705
+ onCreatingSubmit();
4706
+ }
4707
+ if (event.key === "Escape") {
4708
+ event.preventDefault();
4709
+ onCreatingCancel();
4710
+ }
4711
+ },
4712
+ onBlur: onCreatingCancel
4713
+ })
4714
+ ]
4715
+ }), creating.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4716
+ className: "dvw-tree-create-error",
4717
+ style: { paddingLeft: 42 + depth * 13 },
4718
+ children: creating.error
4719
+ })] }),
4582
4720
  rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4583
4721
  className: "dvw-tree-row",
4584
4722
  "data-dvw-upload-directory": row.kind === "directory" ? row.path : void 0,
@@ -4656,7 +4794,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4656
4794
  onSelectDirectory,
4657
4795
  onOpen,
4658
4796
  onChange,
4659
- onUpload
4797
+ onUpload,
4798
+ creating,
4799
+ onCreatingChange,
4800
+ onCreatingCancel,
4801
+ onCreatingSubmit
4660
4802
  })] }, row.path)),
4661
4803
  context && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4662
4804
  className: "dvw-context-menu",
@@ -4732,6 +4874,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4732
4874
  const [statuses, setStatuses] = (0, react.useState)(/* @__PURE__ */ new Map());
4733
4875
  const [dropTarget, setDropTarget] = (0, react.useState)(false);
4734
4876
  const [selectedDirectory, setSelectedDirectory] = (0, react.useState)("");
4877
+ const [creating, setCreating] = (0, react.useState)();
4735
4878
  const picker = (0, react.useRef)(null);
4736
4879
  (0, react.useEffect)(() => {
4737
4880
  if (props.cwd) try {
@@ -4753,19 +4896,42 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4753
4896
  window.addEventListener("dvw-files-changed", handler);
4754
4897
  return () => window.removeEventListener("dvw-files-changed", handler);
4755
4898
  }, [refreshStatus, refreshStatusDebounced]);
4756
- const create = async (kind) => {
4757
- const path = prompt(kind === "file" ? "新文件路径" : "新目录路径");
4758
- if (!path) return;
4899
+ const startCreating = (kind) => {
4900
+ const directory = selectedDirectory;
4901
+ if (directory) setExpanded((current) => new Set(current).add(directory));
4902
+ setCreating({
4903
+ kind,
4904
+ directory,
4905
+ value: ""
4906
+ });
4907
+ };
4908
+ const submitCreating = async () => {
4909
+ if (creating === void 0) return;
4910
+ const value = creating.value.trim();
4911
+ if (value === "") {
4912
+ setCreating((current) => current && {
4913
+ ...current,
4914
+ error: "请输入名称"
4915
+ });
4916
+ return;
4917
+ }
4918
+ const path = createdEntryPath(creating.directory, value);
4759
4919
  try {
4760
4920
  await api("fs.create", {
4761
4921
  sessionId: props.sessionId,
4762
4922
  path,
4763
- kind
4923
+ kind: creating.kind
4764
4924
  });
4925
+ const created = path;
4926
+ setCreating(void 0);
4927
+ setSelectedDirectory(creating.kind === "directory" ? created : creating.directory);
4765
4928
  setRevision((x) => x + 1);
4766
4929
  window.dispatchEvent(new Event("dvw-files-changed"));
4767
4930
  } catch (error) {
4768
- alert(String(error));
4931
+ setCreating((current) => current && {
4932
+ ...current,
4933
+ error: error instanceof Error ? error.message : String(error)
4934
+ });
4769
4935
  }
4770
4936
  };
4771
4937
  const upload = (0, react.useCallback)(async (directory, files) => {
@@ -4876,13 +5042,13 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4876
5042
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4877
5043
  className: "dvw-icon",
4878
5044
  title: "新建文件",
4879
- onClick: () => void create("file"),
5045
+ onClick: () => startCreating("file"),
4880
5046
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "new-file" })
4881
5047
  }),
4882
5048
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4883
5049
  className: "dvw-icon",
4884
- title: "新建目录",
4885
- onClick: () => void create("directory"),
5050
+ title: "新建文件夹",
5051
+ onClick: () => startCreating("directory"),
4886
5052
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "new-folder" })
4887
5053
  }),
4888
5054
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -4920,7 +5086,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4920
5086
  onSelectDirectory: setSelectedDirectory,
4921
5087
  onOpen: props.onOpen,
4922
5088
  onChange: () => setRevision((x) => x + 1),
4923
- onUpload: (directory, files) => void upload(directory, files)
5089
+ onUpload: (directory, files) => void upload(directory, files),
5090
+ creating,
5091
+ onCreatingChange: (value) => setCreating((current) => current && {
5092
+ ...current,
5093
+ value,
5094
+ error: void 0
5095
+ }),
5096
+ onCreatingCancel: () => setCreating(void 0),
5097
+ onCreatingSubmit: () => void submitCreating()
4924
5098
  })
4925
5099
  }, revision)] });
4926
5100
  }
@@ -4928,6 +5102,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4928
5102
  function uploadDestination(selectedDirectory) {
4929
5103
  return selectedDirectory ?? "";
4930
5104
  }
5105
+ /** Join an inline-created entry name to the currently selected Explorer directory. */
5106
+ function createdEntryPath(directory, name) {
5107
+ return directory ? `${directory}/${name}` : name;
5108
+ }
4931
5109
  /** Replace the final path component while preserving its workspace-relative directory. */
4932
5110
  function renamedPath(path, name) {
4933
5111
  return [...path.split("/").slice(0, -1), name].filter(Boolean).join("/");
@@ -5329,7 +5507,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5329
5507
  setStatus(nextStatus);
5330
5508
  setCommits(nextCommits);
5331
5509
  setError(void 0);
5332
- }).catch((error) => setError(String(error)));
5510
+ }).catch((error) => {
5511
+ setStatus(void 0);
5512
+ setCommits([]);
5513
+ setError(error instanceof ApiError && error.code === "not-repository" ? void 0 : String(error));
5514
+ });
5333
5515
  }, [props.sessionId]);
5334
5516
  const refreshDebounced = useDebouncedCallback(refresh, 500);
5335
5517
  (0, react.useEffect)(refresh, [refresh]);
@@ -5598,6 +5780,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5598
5780
  const tabsRef = (0, react.useRef)(tabs);
5599
5781
  tabsRef.current = tabs;
5600
5782
  const workspaceRequests = (0, react.useRef)(new WorkspaceRequestScope());
5783
+ const loadingTabs = (0, react.useRef)(/* @__PURE__ */ new Set());
5601
5784
  const dshDark = useDshDarkTheme();
5602
5785
  const [theme, setTheme] = (0, react.useState)(() => {
5603
5786
  const saved = localStorage.getItem("dvw:theme");
@@ -5620,61 +5803,24 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5620
5803
  window.addEventListener("keydown", onKey, true);
5621
5804
  return () => window.removeEventListener("keydown", onKey, true);
5622
5805
  }, [snapshot.visible]);
5623
- const openForWorkspace = async (targetSessionId, epoch, path, line, column) => {
5624
- if (!workspaceRequests.current.isCurrent(epoch)) return;
5625
- if (tabsRef.current.find((tab) => tab.path === path)) {
5626
- setActive(path);
5627
- setTabs((rows) => rows.map((tab) => tab.path === path ? {
5628
- ...tab,
5629
- line,
5630
- column
5631
- } : tab));
5632
- return;
5633
- }
5634
- if (/\.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)$/i.test(path)) {
5635
- const imageUrl = `/dsh-vscode/file?sessionId=${encodeURIComponent(targetSessionId)}&path=${encodeURIComponent(path)}`;
5636
- if (!workspaceRequests.current.isCurrent(epoch)) return;
5637
- setTabs((rows) => workspaceRequests.current.isCurrent(epoch) ? [...rows.filter((tab) => tab.path !== path), {
5638
- path,
5639
- content: "",
5640
- draft: "",
5641
- revision: "",
5642
- size: 0,
5643
- binary: false,
5644
- tooLarge: false,
5645
- dirty: false,
5646
- imageUrl
5647
- }] : rows);
5648
- setActive(path);
5649
- return;
5650
- }
5651
- try {
5652
- const doc = await api("fs.read", {
5653
- sessionId: targetSessionId,
5654
- path
5655
- });
5656
- if (!workspaceRequests.current.isCurrent(epoch)) return;
5657
- if (doc.binary || doc.tooLarge) {
5658
- setError(doc.binary ? "二进制文件不能在文本编辑器中打开" : `文件超过 5 MiB:${path}`);
5659
- return;
5660
- }
5661
- setTabs((rows) => workspaceRequests.current.isCurrent(epoch) ? [...rows.filter((tab) => tab.path !== path), {
5662
- ...doc,
5663
- draft: doc.content,
5664
- dirty: false,
5665
- line,
5666
- column,
5667
- preview: /\.md$/i.test(path)
5668
- }] : rows);
5669
- setActive(path);
5670
- } catch (error) {
5671
- if (workspaceRequests.current.isCurrent(epoch)) setError(String(error));
5672
- }
5806
+ const openForWorkspace = (path, line, column) => {
5807
+ if (tabsRef.current.find((tab) => tab.path === path)) setTabs((rows) => rows.map((tab) => tab.path === path ? {
5808
+ ...tab,
5809
+ line,
5810
+ column
5811
+ } : tab));
5812
+ else setTabs((rows) => [...rows, {
5813
+ ...unloadedTab(path),
5814
+ line,
5815
+ column
5816
+ }]);
5817
+ setActive(path);
5673
5818
  };
5674
5819
  (0, react.useEffect)(() => {
5675
5820
  if (!snapshot.visible || sessionId === void 0) return;
5676
5821
  const epoch = workspaceRequests.current.begin();
5677
5822
  const cleared = resetWorkspaceDocuments();
5823
+ loadingTabs.current.clear();
5678
5824
  setWorkspace(void 0);
5679
5825
  setTabs(cleared.tabs);
5680
5826
  tabsRef.current = cleared.tabs;
@@ -5690,13 +5836,16 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5690
5836
  if (parsed.view) service.show(parsed.view);
5691
5837
  if (typeof parsed.panelHeight === "number") setPanelHeight(parsed.panelHeight);
5692
5838
  if (typeof parsed.sideWidth === "number") setSideWidth(Math.max(210, Math.min(640, Math.round(parsed.sideWidth))));
5693
- for (const path of parsed.paths ?? []) openForWorkspace(sessionId, epoch, path);
5839
+ const restored = restoreTabPaths(parsed.paths ?? []);
5840
+ setTabs(restored);
5841
+ setActive(restored.some((tab) => tab.path === parsed.active) ? parsed.active : restored.at(-1)?.path);
5694
5842
  } catch {}
5695
5843
  }).catch((error) => {
5696
5844
  if (!controller.signal.aborted && workspaceRequests.current.isCurrent(epoch)) setError(String(error));
5697
5845
  });
5698
5846
  return () => {
5699
5847
  controller.abort();
5848
+ loadingTabs.current.clear();
5700
5849
  workspaceRequests.current.invalidate(epoch);
5701
5850
  };
5702
5851
  }, [snapshot.visible, sessionId]);
@@ -5704,6 +5853,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5704
5853
  if (workspace) localStorage.setItem(`dvw:${workspace.cwd}`, JSON.stringify({
5705
5854
  view: snapshot.view,
5706
5855
  paths: tabs.filter((t) => !t.path.startsWith("diff:")).map((t) => t.path),
5856
+ active,
5707
5857
  panelHeight,
5708
5858
  sideWidth
5709
5859
  }));
@@ -5711,6 +5861,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5711
5861
  workspace,
5712
5862
  snapshot.view,
5713
5863
  tabs,
5864
+ active,
5714
5865
  panelHeight,
5715
5866
  sideWidth
5716
5867
  ]);
@@ -5718,8 +5869,12 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5718
5869
  if (!snapshot.visible || sessionId === void 0) return;
5719
5870
  const protocol = location.protocol === "https:" ? "wss" : "ws";
5720
5871
  const ws = new WebSocket(`${protocol}://${location.host}/dsh-vscode/ws/files?sessionId=${encodeURIComponent(sessionId)}`);
5721
- ws.onmessage = () => {
5722
- window.dispatchEvent(new Event("dvw-files-changed"));
5872
+ ws.onmessage = (event) => {
5873
+ let paths = [];
5874
+ try {
5875
+ paths = JSON.parse(String(event.data)).paths ?? [];
5876
+ } catch {}
5877
+ window.dispatchEvent(new CustomEvent("dvw-files-changed", { detail: paths }));
5723
5878
  setTabs((current) => current.map((tab) => tab.dirty ? {
5724
5879
  ...tab,
5725
5880
  external: true
@@ -5727,9 +5882,49 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5727
5882
  };
5728
5883
  return () => ws.close();
5729
5884
  }, [snapshot.visible, sessionId]);
5730
- const open = async (path, line, column) => {
5731
- if (sessionId === void 0) return;
5732
- await openForWorkspace(sessionId, workspaceRequests.current.current(), path, line, column);
5885
+ (0, react.useEffect)(() => {
5886
+ if (sessionId === void 0 || active === void 0) return;
5887
+ const tab = tabsRef.current.find((row) => row.path === active);
5888
+ if (tab === void 0 || tab.loaded !== false || loadingTabs.current.has(active)) return;
5889
+ const epoch = workspaceRequests.current.current();
5890
+ loadingTabs.current.add(active);
5891
+ if (/\.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)$/i.test(active)) {
5892
+ const imageUrl = `/dsh-vscode/file?sessionId=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(active)}`;
5893
+ setTabs((rows) => rows.map((row) => row.path === active ? {
5894
+ ...row,
5895
+ loaded: true,
5896
+ imageUrl
5897
+ } : row));
5898
+ loadingTabs.current.delete(active);
5899
+ return;
5900
+ }
5901
+ api("fs.read", {
5902
+ sessionId,
5903
+ path: active
5904
+ }).then((doc) => {
5905
+ if (!workspaceRequests.current.isCurrent(epoch)) return;
5906
+ if (doc.binary || doc.tooLarge) {
5907
+ setTabs((rows) => rows.filter((row) => row.path !== active));
5908
+ setActive((current) => current === active ? void 0 : current);
5909
+ setError(doc.binary ? "二进制文件不能在文本编辑器中打开" : `文件超过 5 MiB:${active}`);
5910
+ return;
5911
+ }
5912
+ setTabs((rows) => rows.map((row) => row.path === active ? {
5913
+ ...row,
5914
+ ...doc,
5915
+ draft: doc.content,
5916
+ loaded: true
5917
+ } : row));
5918
+ }).catch((error) => {
5919
+ if (workspaceRequests.current.isCurrent(epoch)) setError(String(error));
5920
+ }).finally(() => loadingTabs.current.delete(active));
5921
+ }, [
5922
+ active,
5923
+ sessionId,
5924
+ tabs
5925
+ ]);
5926
+ const open = (path, line, column) => {
5927
+ if (sessionId !== void 0) openForWorkspace(path, line, column);
5733
5928
  };
5734
5929
  (0, react.useEffect)(() => {
5735
5930
  const listener = (event) => void open(event.detail);
@@ -5849,21 +6044,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5849
6044
  chatVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
5850
6045
  className: "dvw-chat",
5851
6046
  children: [
5852
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6047
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelResizer, {
5853
6048
  className: "dvw-chat-resizer",
5854
- title: "拖动调整 DSH 对话栏宽度,双击恢复默认",
5855
- onDoubleClick: () => setChatWidth(420),
5856
- onPointerDown: (event) => {
5857
- event.preventDefault();
5858
- const startX = event.clientX, startWidth = chatWidth;
5859
- const move = (next) => setChatWidth(Math.max(400, Math.min(720, Math.round(startWidth + startX - next.clientX))));
5860
- const up = () => {
5861
- window.removeEventListener("pointermove", move);
5862
- window.removeEventListener("pointerup", up);
5863
- };
5864
- window.addEventListener("pointermove", move);
5865
- window.addEventListener("pointerup", up);
5866
- }
6049
+ label: "拖动调整 DSH 对话栏宽度",
6050
+ width: chatWidth,
6051
+ min: 400,
6052
+ max: 720,
6053
+ defaultWidth: 420,
6054
+ direction: -1,
6055
+ onWidth: setChatWidth
5867
6056
  }),
5868
6057
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
5869
6058
  className: "dvw-chat-head",
@@ -6051,21 +6240,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6051
6240
  })
6052
6241
  ]
6053
6242
  }),
6054
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelResizer, {
6055
6244
  className: "dvw-side-resizer",
6056
- title: "拖动调整侧边栏宽度,双击恢复默认",
6057
- onDoubleClick: () => setSideWidth(210),
6058
- onPointerDown: (event) => {
6059
- event.preventDefault();
6060
- const startX = event.clientX, startWidth = sideWidth;
6061
- const move = (next) => setSideWidth(Math.max(210, Math.min(640, Math.round(startWidth + next.clientX - startX))));
6062
- const up = () => {
6063
- window.removeEventListener("pointermove", move);
6064
- window.removeEventListener("pointerup", up);
6065
- };
6066
- window.addEventListener("pointermove", move);
6067
- window.addEventListener("pointerup", up);
6068
- }
6245
+ label: "拖动调整源码管理侧边栏宽度",
6246
+ width: sideWidth,
6247
+ min: 210,
6248
+ max: 640,
6249
+ defaultWidth: 210,
6250
+ direction: 1,
6251
+ onWidth: setSideWidth
6069
6252
  }),
6070
6253
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
6071
6254
  className: "dvw-main",
@@ -6313,7 +6496,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6313
6496
  body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-scheme:dark}
6314
6497
  .dvw-overlay[data-dvw-theme=dark]{--dvw-base:#151517;--dvw-surface-1:#232324;--dvw-surface-2:#2c2c2e;--dvw-surface-3:#353638;--dvw-text:#f9fafb;--dvw-text-2:#cfd3d6;--dvw-text-3:#adb2b8;--dvw-border:#ffffff1f;--dvw-border-soft:#ffffff29;--dvw-hover:#ffffff14;--dvw-active:#ffffff24;--dvw-accent:#679efe;--dvw-error:#f25a5a;--dvw-success:#22c55e;--dvw-warn:#f59e0b}
6315
6498
  .dvw-overlay[data-dvw-theme=light]{--dvw-base:#fff;--dvw-surface-1:#fff;--dvw-surface-2:#fff;--dvw-surface-3:#fff;--dvw-text:#0f1115;--dvw-text-2:#61666b;--dvw-text-3:#81858c;--dvw-border:#0000001a;--dvw-border-soft:#0000001f;--dvw-hover:#2631480f;--dvw-active:#2631481a;--dvw-accent:#4176e6;--dvw-error:#ec1313;--dvw-success:#22c55e;--dvw-warn:#f59e0b;color-scheme:light}
6316
- .dvw-overlay *{box-sizing:border-box}.dvw-activity{grid-row:1/3;background:var(--dvw-base);border-right:1px solid var(--dvw-border-soft);display:flex;flex-direction:column;align-items:center}.dvw-act{width:48px;height:48px;border:0;border-left:2px solid transparent;background:transparent;color:var(--dvw-text-3);font-size:22px;cursor:pointer}.dvw-act:hover,.dvw-act[data-active=true]{color:var(--dvw-text)}.dvw-act[data-active=true]{border-left-color:var(--dvw-accent)}.dvw-act.dsh{margin-top:auto}.dvw-side{grid-column:2;grid-row:1/3;min-width:0;width:100%;background:var(--dvw-surface-1);border-right:1px solid var(--dvw-border);display:flex;flex-direction:column}.dvw-side-resizer{grid-column:2;grid-row:1/3;justify-self:end;width:7px;height:100%;z-index:4;cursor:ew-resize;transform:translateX(3px);touch-action:none}.dvw-side-resizer:hover,.dvw-side-resizer:active{background:var(--dvw-accent)}.dvw-side-head{height:36px;display:flex;align-items:center;padding:0 12px;text-transform:uppercase;font-size:11px;letter-spacing:.4px}.dvw-toolbar{margin-left:auto;display:flex;gap:2px}.dvw-icon{border:0;background:transparent;color:var(--dvw-text-2);border-radius:4px;min-width:24px;height:24px;display:inline-grid;place-items:center;cursor:pointer}.dvw-icon:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-icon:disabled{opacity:.45;cursor:default}.dvw-file-picker{display:none}.dvw-tree,.dvw-results,.dvw-git{overflow:auto;flex:1}.dvw-tree[data-drop-target=true]{outline:1px dashed var(--dvw-accent);outline-offset:-3px}.dvw-tree-row{display:flex;align-items:center;height:24px;padding-right:6px;cursor:pointer;white-space:nowrap}.dvw-tree-row:hover{background:var(--dvw-hover)}.dvw-tree-row[data-selected=true],.dvw-tree-row[data-drop-target=true]{background:var(--dvw-active)}.dvw-chevron{width:16px;text-align:center;color:var(--dvw-text-3)}.dvw-file-icon{width:18px;color:var(--dvw-text-2)}.dvw-file-icon.folder{color:var(--dvw-accent)}.dvw-tree-name{overflow:hidden;text-overflow:ellipsis}.dvw-tree-rename{min-width:0;flex:1;height:20px;border:1px solid var(--dvw-accent);background:var(--dvw-surface-1);color:var(--dvw-text);font:inherit;outline:0}.dvw-tree-status{margin-left:auto;padding-left:6px;font-size:11px;font-weight:600;color:var(--dvw-warn)}.dvw-tree-status[data-status=M]{color:var(--dvw-warn)}.dvw-tree-status[data-status=A]{color:var(--dvw-success)}.dvw-tree-status[data-status=D]{color:var(--dvw-error)}.dvw-context-menu{position:fixed;z-index:40;min-width:148px;padding:4px;border:1px solid var(--dvw-border);border-radius:5px;background:var(--dvw-surface-3);box-shadow:0 6px 20px rgba(0,0,0,.3)}.dvw-context-menu button{width:100%;height:28px;border:0;border-radius:3px;background:transparent;color:var(--dvw-text);display:flex;align-items:center;gap:8px;padding:0 8px;text-align:left;font:inherit;cursor:pointer}.dvw-context-menu button:hover{background:var(--dvw-hover)}.dvw-context-menu .dvw-context-delete{color:var(--dvw-error)}
6499
+ .dvw-overlay *{box-sizing:border-box}.dvw-activity{grid-row:1/3;background:var(--dvw-base);border-right:1px solid var(--dvw-border-soft);display:flex;flex-direction:column;align-items:center}.dvw-act{width:48px;height:48px;border:0;border-left:2px solid transparent;background:transparent;color:var(--dvw-text-3);font-size:22px;cursor:pointer}.dvw-act:hover,.dvw-act[data-active=true]{color:var(--dvw-text)}.dvw-act[data-active=true]{border-left-color:var(--dvw-accent)}.dvw-act.dsh{margin-top:auto}.dvw-side{grid-column:2;grid-row:1/3;min-width:0;width:100%;background:var(--dvw-surface-1);border-right:1px solid var(--dvw-border);display:flex;flex-direction:column}.dvw-side-resizer{grid-column:2;grid-row:1/3;justify-self:end;width:4px;height:100%;z-index:4;cursor:ew-resize;transform:translateX(2px);touch-action:none}.dvw-side-resizer:hover,.dvw-side-resizer:focus-visible,.dvw-side-resizer[data-dragging=true]{background:var(--dvw-accent)}.dvw-side-head{height:36px;display:flex;align-items:center;padding:0 12px;text-transform:uppercase;font-size:11px;letter-spacing:.4px}.dvw-toolbar{margin-left:auto;display:flex;gap:2px}.dvw-icon{border:0;background:transparent;color:var(--dvw-text-2);border-radius:4px;min-width:24px;height:24px;display:inline-grid;place-items:center;cursor:pointer}.dvw-icon:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-icon:disabled{opacity:.45;cursor:default}.dvw-file-picker{display:none}.dvw-tree,.dvw-results,.dvw-git{overflow:auto;flex:1}.dvw-tree[data-drop-target=true]{outline:1px dashed var(--dvw-accent);outline-offset:-3px}.dvw-tree-row{display:flex;align-items:center;height:24px;padding-right:6px;cursor:pointer;white-space:nowrap}.dvw-tree-row:hover{background:var(--dvw-hover)}.dvw-tree-row[data-selected=true],.dvw-tree-row[data-drop-target=true]{background:var(--dvw-active)}.dvw-chevron{width:16px;flex:none;text-align:center;color:var(--dvw-text-3)}.dvw-file-icon{width:18px;flex:none;color:var(--dvw-text-2)}.dvw-file-icon.folder{color:var(--dvw-accent)}.dvw-tree-name{overflow:hidden;text-overflow:ellipsis}.dvw-tree-rename,.dvw-tree-create-input{min-width:0;flex:1;height:20px;border:1px solid var(--dvw-accent);border-radius:0;background:var(--dvw-surface-1);color:var(--dvw-text);padding:0 3px;font:inherit;letter-spacing:0;outline:0}.dvw-tree-create-row{cursor:text}.dvw-tree-create-row:hover{background:transparent}.dvw-tree-create-input::placeholder{color:var(--dvw-text-3)}.dvw-tree-create-input[aria-invalid=true]{border-color:var(--dvw-error)}.dvw-tree-create-error{min-height:20px;padding-top:2px;padding-right:6px;padding-bottom:3px;color:var(--dvw-error);font-size:11px;line-height:15px;white-space:normal;overflow-wrap:anywhere}.dvw-tree-status{margin-left:auto;padding-left:6px;font-size:11px;font-weight:600;color:var(--dvw-warn)}.dvw-tree-status[data-status=M]{color:var(--dvw-warn)}.dvw-tree-status[data-status=A]{color:var(--dvw-success)}.dvw-tree-status[data-status=D]{color:var(--dvw-error)}.dvw-context-menu{position:fixed;z-index:40;min-width:148px;padding:4px;border:1px solid var(--dvw-border);border-radius:5px;background:var(--dvw-surface-3);box-shadow:0 6px 20px rgba(0,0,0,.3)}.dvw-context-menu button{width:100%;height:28px;border:0;border-radius:3px;background:transparent;color:var(--dvw-text);display:flex;align-items:center;gap:8px;padding:0 8px;text-align:left;font:inherit;cursor:pointer}.dvw-context-menu button:hover{background:var(--dvw-hover)}.dvw-context-menu .dvw-context-delete{color:var(--dvw-error)}
6317
6500
  .dvw-main{grid-column:3;grid-row:1;display:flex;min-width:0;min-height:0;flex-direction:column;background:var(--dvw-surface-1)}.dvw-tabs{height:36px;background:var(--dvw-base);display:flex;overflow-x:auto}.dvw-tab{height:36px;min-width:110px;max-width:260px;border:0;border-right:1px solid var(--dvw-border-soft);background:var(--dvw-base);color:var(--dvw-text-2);padding:0 8px 0 12px;display:flex;align-items:center;gap:8px;cursor:pointer}.dvw-tab[data-active=true]{background:var(--dvw-surface-1);color:var(--dvw-text);border-top:1px solid var(--dvw-accent)}.dvw-tab-name{overflow:hidden;text-overflow:ellipsis;flex:1;text-align:left}.dvw-editor{position:relative;flex:1;min-height:0}.dvw-editor-toolbar{position:absolute;z-index:3;top:0;right:0;left:0;height:36px;display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:0 10px;border-bottom:1px solid var(--dvw-border-soft);background:var(--dvw-surface-1)}.dvw-editor-body{position:absolute;inset:0;min-height:0}.dvw-editor-body[data-toolbar=true]{top:36px}.dvw-mode-button{height:27px;padding:0 10px;display:flex;align-items:center;gap:6px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--dvw-text-2);cursor:pointer}.dvw-mode-button:hover{background:var(--dvw-hover)}.dvw-mode-button[data-active=true]{border-color:var(--dvw-border);background:var(--dvw-active);color:var(--dvw-text)}.dvw-empty{height:100%;display:grid;place-items:center;color:var(--dvw-text-3);text-align:center}.dvw-empty strong{font-size:24px;color:var(--dvw-text-2)}.dvw-message{padding:12px;color:var(--dvw-warn)}
6318
6501
  .dvw-bottom{grid-column:3;grid-row:2;height:260px;min-height:120px;border-top:1px solid var(--dvw-border);background:var(--dvw-base);display:flex;flex-direction:column}.dvw-bottom-head{height:35px;display:flex;align-items:center;gap:14px;padding:0 12px}.dvw-bottom-title{border:0;border-bottom:1px solid var(--dvw-text);background:transparent;color:var(--dvw-text);height:35px}.dvw-bottom-body{flex:1;min-height:0}.dvw-status{grid-column:1/4;grid-row:3;background:var(--dvw-accent);color:#fff;display:flex;align-items:center;padding:0 8px;gap:18px;font-size:12px}.dvw-theme-control{position:relative;margin-left:auto}.dvw-theme-trigger{height:22px;min-width:112px;display:flex;align-items:center;gap:7px;padding:0 8px;border:0;border-radius:5px;background:color-mix(in srgb,#000 17%,transparent);color:#fff;cursor:pointer}.dvw-theme-trigger:hover{background:color-mix(in srgb,#000 27%,transparent)}.dvw-theme-orb{width:13px;height:13px;border:2px solid #fff;border-radius:50%;background:linear-gradient(90deg,#17181c 50%,#fff 50%)}.dvw-theme-orb.dark{background:#17181c}.dvw-theme-orb.light{background:#fff}.dvw-theme-menu{position:absolute;right:0;bottom:29px;width:224px;padding:6px;border:1px solid var(--dvw-border);border-radius:10px;background:var(--dvw-surface-3);box-shadow:0 10px 35px rgba(0,0,0,.28);color:var(--dvw-text)}.dvw-theme-menu button{width:100%;min-height:48px;border:0;border-radius:7px;background:transparent;color:inherit;display:grid;grid-template-columns:30px 1fr 20px;align-items:center;text-align:left;padding:6px;cursor:pointer}.dvw-theme-menu button:hover,.dvw-theme-menu button[aria-checked=true]{background:var(--dvw-hover)}.dvw-theme-menu strong,.dvw-theme-menu small{display:block}.dvw-theme-menu small{color:var(--dvw-text-3);margin-top:2px}.dvw-theme-swatch{width:22px;height:22px;border-radius:6px;border:1px solid var(--dvw-border);background:linear-gradient(135deg,#17181c 0 50%,#fff 50%)}.dvw-theme-swatch.dark{background:#17181c}.dvw-theme-swatch.light{background:#fff}
6319
6502
  .dvw-search-form{padding:8px;display:grid;gap:6px}.dvw-input,.dvw-textarea,.dvw-select{width:100%;border:1px solid var(--dvw-border);background:var(--dvw-surface-2);color:var(--dvw-text);padding:5px 7px;outline:none}.dvw-input:focus,.dvw-textarea:focus,.dvw-select:focus{border-color:var(--dvw-accent)}.dvw-options{display:flex;align-items:center;gap:8px;font-size:11px}.dvw-result-file{padding:5px 8px;font-weight:600}.dvw-result{padding:4px 8px 4px 20px;cursor:pointer;display:flex;gap:8px}.dvw-result:hover{background:var(--dvw-hover)}.dvw-line{color:var(--dvw-text-3);min-width:30px;text-align:right}.dvw-preview{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -6332,8 +6515,18 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
6332
6515
  .dvw-chat-head{height:36px;display:flex;align-items:center;justify-content:space-between;padding:0 6px 0 12px;border-bottom:1px solid var(--dvw-border);background:var(--dvw-base);font-weight:600}
6333
6516
  .dvw-chat-head>span{display:flex;align-items:center;gap:7px}.dvw-chat-body{height:calc(100% - 36px);background:var(--dvw-base)}
6334
6517
  .dvw-dsh-mark{display:block}
6335
- .dvw-chat-resizer{position:fixed;top:0;right:calc(var(--dvw-chat-width) - 4px);bottom:0;width:8px;z-index:4;cursor:ew-resize;touch-action:none}
6336
- .dvw-chat-resizer:hover,.dvw-chat-resizer:active{background:var(--dvw-accent)}
6518
+ .dvw-chat-resizer{position:fixed;top:36px;right:calc(var(--dvw-chat-width) - 2px);bottom:0;width:4px;z-index:4;cursor:ew-resize;touch-action:none}
6519
+ .dvw-chat-resizer:hover,.dvw-chat-resizer:focus-visible,.dvw-chat-resizer[data-dragging=true]{background:var(--dvw-accent)}
6520
+ .dvw-side-resizer,.dvw-chat-resizer{width:10px;cursor:ew-resize;touch-action:none;background:transparent}
6521
+ .dvw-side-resizer{position:relative;transform:translateX(5px);z-index:22}
6522
+ .dvw-chat-resizer{position:fixed;top:36px;right:calc(var(--dvw-chat-width) - 5px);z-index:22}
6523
+ .dvw-side-resizer::after,.dvw-chat-resizer::after{content:"";position:absolute;top:0;bottom:0;left:4px;width:2px;background:var(--dvw-border)}
6524
+ .dvw-chat-resizer::after{left:1px;width:6px}
6525
+ .dvw-side-resizer:hover,.dvw-side-resizer:focus-visible,.dvw-side-resizer[data-dragging=true],.dvw-chat-resizer:hover,.dvw-chat-resizer:focus-visible,.dvw-chat-resizer[data-dragging=true]{background:transparent}
6526
+ .dvw-side-resizer:hover::after,.dvw-side-resizer:focus-visible::after,.dvw-side-resizer[data-dragging=true]::after,.dvw-chat-resizer:hover::after,.dvw-chat-resizer:focus-visible::after,.dvw-chat-resizer[data-dragging=true]::after{background:var(--dvw-accent)}
6527
+ .dvw-side{border-right-color:transparent}.dvw-chat,[data-dvw-conversation-panel]{border-left-color:transparent!important}
6528
+ .dvw-side-resizer::after,.dvw-chat-resizer::after{background:transparent}.dvw-side-resizer::after{left:3px;width:3px}
6529
+ .dvw-side-resizer:hover::after,.dvw-side-resizer:focus-visible::after,.dvw-side-resizer[data-dragging=true]::after,.dvw-chat-resizer:hover::after,.dvw-chat-resizer:focus-visible::after,.dvw-chat-resizer[data-dragging=true]::after{background:var(--dvw-accent)}
6337
6530
  .dvw-chat-toggle{position:absolute;z-index:5;top:6px;right:6px;border:1px solid var(--dvw-border);background:var(--dvw-base)}
6338
6531
  .dvw-overlay:not([data-dvw-chat=true]) .dvw-tabs{padding-right:36px}
6339
6532
  [data-dvw-conversation-panel]{position:fixed!important;top:36px!important;right:0!important;bottom:var(--dvw-status-height,24px)!important;left:auto!important;width:var(--dvw-chat-width)!important;height:auto!important;min-width:0!important;z-index:21!important;border-left:1px solid var(--dsw-alias-border-l2)!important;box-shadow:none!important}