dsh-vscode-workbench 0.1.2 → 0.1.4

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
@@ -4487,9 +4487,13 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4487
4487
  }, []);
4488
4488
  return dark;
4489
4489
  }
4490
- function Tree({ sessionId, path, depth, expanded, selected, onToggle, onOpen, onChange }) {
4490
+ function Tree({ sessionId, path, depth, expanded, selected, selectedDirectory, statuses, onToggle, onSelectDirectory, onOpen, onChange, onUpload }) {
4491
4491
  const [rows, setRows] = (0, react.useState)([]);
4492
4492
  const [error, setError] = (0, react.useState)();
4493
+ const [dropTarget, setDropTarget] = (0, react.useState)(false);
4494
+ const [context, setContext] = (0, react.useState)();
4495
+ const [renaming, setRenaming] = (0, react.useState)();
4496
+ const [pendingDelete, setPendingDelete] = (0, react.useState)();
4493
4497
  const load = (0, react.useCallback)(() => {
4494
4498
  api("fs.list", {
4495
4499
  sessionId,
@@ -4502,72 +4506,223 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4502
4506
  window.addEventListener("dvw-files-changed", handler);
4503
4507
  return () => window.removeEventListener("dvw-files-changed", handler);
4504
4508
  }, [load]);
4505
- const menu = async (entry) => {
4506
- const action = prompt(`操作 ${entry.name}: rename / delete / new-file / new-folder`);
4509
+ (0, react.useEffect)(() => {
4510
+ if (context === void 0) return;
4511
+ const close = () => setContext(void 0);
4512
+ const escape = (event) => {
4513
+ if (event.key === "Escape") close();
4514
+ };
4515
+ window.addEventListener("pointerdown", close);
4516
+ window.addEventListener("keydown", escape);
4517
+ return () => {
4518
+ window.removeEventListener("pointerdown", close);
4519
+ window.removeEventListener("keydown", escape);
4520
+ };
4521
+ }, [context]);
4522
+ (0, react.useEffect)(() => {
4523
+ if (pendingDelete === void 0) return;
4524
+ const escape = (event) => {
4525
+ if (event.key === "Escape") setPendingDelete(void 0);
4526
+ };
4527
+ window.addEventListener("keydown", escape);
4528
+ return () => window.removeEventListener("keydown", escape);
4529
+ }, [pendingDelete]);
4530
+ const rename = async (entry) => {
4531
+ const value = renaming?.path === entry.path ? renaming.value.trim() : "";
4532
+ setRenaming(void 0);
4533
+ if (value === "" || value === entry.name) return;
4507
4534
  try {
4508
- if (action === "rename") {
4509
- const name = prompt("新名称", entry.name);
4510
- if (name) await api("fs.rename", {
4511
- sessionId,
4512
- path: entry.path,
4513
- nextPath: [...entry.path.split("/").slice(0, -1), name].filter(Boolean).join("/")
4514
- });
4515
- }
4516
- if (action === "delete" && confirm(`确定删除 ${entry.path}?`)) await api("fs.delete", {
4535
+ await api("fs.rename", {
4536
+ sessionId,
4537
+ path: entry.path,
4538
+ nextPath: renamedPath(entry.path, value)
4539
+ });
4540
+ load();
4541
+ onChange();
4542
+ } catch (error) {
4543
+ alert(error instanceof Error ? error.message : String(error));
4544
+ }
4545
+ };
4546
+ const remove = async () => {
4547
+ if (pendingDelete === void 0) return;
4548
+ const entry = pendingDelete;
4549
+ setPendingDelete(void 0);
4550
+ try {
4551
+ await api("fs.delete", {
4517
4552
  sessionId,
4518
4553
  path: entry.path
4519
4554
  });
4520
- if (action === "new-file" || action === "new-folder") {
4521
- const name = prompt("名称");
4522
- if (name) await api("fs.create", {
4523
- sessionId,
4524
- path: `${entry.kind === "directory" ? entry.path : entry.path.split("/").slice(0, -1).join("/")}/${name}`.replace(/^\//, ""),
4525
- kind: action === "new-folder" ? "directory" : "file"
4526
- });
4527
- }
4528
4555
  load();
4529
4556
  onChange();
4530
4557
  } catch (error) {
4531
4558
  alert(error instanceof Error ? error.message : String(error));
4532
4559
  }
4533
4560
  };
4534
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4535
- className: "dvw-error",
4536
- children: error
4537
- }), rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4538
- className: "dvw-tree-row",
4539
- "data-selected": selected === row.path,
4540
- style: { paddingLeft: 6 + depth * 13 },
4541
- onClick: () => row.kind === "directory" ? onToggle(row.path) : onOpen(row.path),
4542
- onContextMenu: (event) => {
4543
- event.preventDefault();
4544
- menu(row);
4545
- },
4546
- children: [
4547
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4548
- className: "dvw-chevron",
4549
- children: row.kind === "directory" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: expanded.has(row.path) ? "chevron-down" : "chevron-right" })
4550
- }),
4551
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
4552
- name: row.kind === "directory" ? expanded.has(row.path) ? "folder-opened" : "folder" : fileIcon(row.name),
4553
- className: `dvw-file-icon ${row.kind === "directory" ? "folder" : ""}`
4554
- }),
4555
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: row.name })
4556
- ]
4557
- }), row.kind === "directory" && expanded.has(row.path) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tree, {
4558
- sessionId,
4559
- path: row.path,
4560
- depth: depth + 1,
4561
- expanded,
4562
- selected,
4563
- onToggle,
4564
- onOpen,
4565
- onChange
4566
- })] }, row.path))] });
4561
+ const drop = (event, directory) => {
4562
+ event.preventDefault();
4563
+ event.stopPropagation();
4564
+ setDropTarget(false);
4565
+ if (event.dataTransfer.files.length > 0) onUpload(directory, event.dataTransfer.files);
4566
+ };
4567
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4568
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4569
+ className: "dvw-error",
4570
+ children: error
4571
+ }),
4572
+ rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4573
+ className: "dvw-tree-row",
4574
+ "data-dvw-upload-directory": row.kind === "directory" ? row.path : void 0,
4575
+ "data-selected": selected === row.path || selectedDirectory === row.path,
4576
+ "data-drop-target": dropTarget && row.kind === "directory",
4577
+ style: { paddingLeft: 6 + depth * 13 },
4578
+ onClick: () => row.kind === "directory" ? (onSelectDirectory(row.path), onToggle(row.path)) : onOpen(row.path),
4579
+ onContextMenu: (event) => {
4580
+ event.preventDefault();
4581
+ if (row.kind === "directory") onSelectDirectory(row.path);
4582
+ setContext({
4583
+ entry: row,
4584
+ x: event.clientX,
4585
+ y: event.clientY
4586
+ });
4587
+ },
4588
+ onDragEnter: (event) => {
4589
+ if (row.kind === "directory" && event.dataTransfer.types.includes("Files")) {
4590
+ event.preventDefault();
4591
+ event.stopPropagation();
4592
+ setDropTarget(true);
4593
+ }
4594
+ },
4595
+ onDragOver: (event) => {
4596
+ if (row.kind === "directory" && event.dataTransfer.types.includes("Files")) {
4597
+ event.preventDefault();
4598
+ event.stopPropagation();
4599
+ setDropTarget(true);
4600
+ }
4601
+ },
4602
+ onDragLeave: () => setDropTarget(false),
4603
+ onDrop: (event) => row.kind === "directory" && drop(event, row.path),
4604
+ children: [
4605
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4606
+ className: "dvw-chevron",
4607
+ children: row.kind === "directory" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: expanded.has(row.path) ? "chevron-down" : "chevron-right" })
4608
+ }),
4609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
4610
+ name: row.kind === "directory" ? expanded.has(row.path) ? "folder-opened" : "folder" : fileIcon(row.name),
4611
+ className: `dvw-file-icon ${row.kind === "directory" ? "folder" : ""}`
4612
+ }),
4613
+ renaming?.path === row.path ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4614
+ className: "dvw-tree-rename",
4615
+ value: renaming.value,
4616
+ autoFocus: true,
4617
+ onClick: (event) => event.stopPropagation(),
4618
+ onChange: (event) => setRenaming({
4619
+ path: row.path,
4620
+ value: event.target.value
4621
+ }),
4622
+ onKeyDown: (event) => {
4623
+ if (event.key === "Enter") rename(row);
4624
+ if (event.key === "Escape") setRenaming(void 0);
4625
+ },
4626
+ onBlur: () => setRenaming(void 0)
4627
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4628
+ className: "dvw-tree-name",
4629
+ children: row.name
4630
+ }),
4631
+ row.kind === "file" && statuses.has(row.path) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4632
+ className: "dvw-tree-status",
4633
+ "data-status": statuses.get(row.path),
4634
+ children: statuses.get(row.path)
4635
+ })
4636
+ ]
4637
+ }), row.kind === "directory" && expanded.has(row.path) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tree, {
4638
+ sessionId,
4639
+ path: row.path,
4640
+ depth: depth + 1,
4641
+ expanded,
4642
+ selected,
4643
+ selectedDirectory,
4644
+ statuses,
4645
+ onToggle,
4646
+ onSelectDirectory,
4647
+ onOpen,
4648
+ onChange,
4649
+ onUpload
4650
+ })] }, row.path)),
4651
+ context && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4652
+ className: "dvw-context-menu",
4653
+ role: "menu",
4654
+ style: {
4655
+ left: context.x,
4656
+ top: context.y
4657
+ },
4658
+ onPointerDown: (event) => event.stopPropagation(),
4659
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4660
+ role: "menuitem",
4661
+ onClick: () => {
4662
+ setRenaming({
4663
+ path: context.entry.path,
4664
+ value: context.entry.name
4665
+ });
4666
+ setContext(void 0);
4667
+ },
4668
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "edit" }), "重命名"]
4669
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4670
+ className: "dvw-context-delete",
4671
+ role: "menuitem",
4672
+ onClick: () => {
4673
+ setPendingDelete(context.entry);
4674
+ setContext(void 0);
4675
+ },
4676
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "trash" }), "删除"]
4677
+ })]
4678
+ }),
4679
+ pendingDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4680
+ className: "dvw-confirm-backdrop",
4681
+ role: "presentation",
4682
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4683
+ className: "dvw-confirm",
4684
+ role: "alertdialog",
4685
+ "aria-modal": "true",
4686
+ "aria-labelledby": "dvw-delete-title",
4687
+ "aria-describedby": "dvw-delete-message",
4688
+ children: [
4689
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
4690
+ id: "dvw-delete-title",
4691
+ children: ["删除 ", pendingDelete.kind === "directory" ? "文件夹" : "文件"]
4692
+ }),
4693
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4694
+ id: "dvw-delete-message",
4695
+ children: [
4696
+ "是否删除“",
4697
+ pendingDelete.name,
4698
+ "”?"
4699
+ ]
4700
+ }),
4701
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4702
+ className: "dvw-confirm-note",
4703
+ children: "此操作无法撤销。"
4704
+ }),
4705
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4706
+ autoFocus: true,
4707
+ onClick: () => setPendingDelete(void 0),
4708
+ children: "取消"
4709
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4710
+ className: "dvw-danger-button",
4711
+ onClick: () => void remove(),
4712
+ children: "删除"
4713
+ })] })
4714
+ ]
4715
+ })
4716
+ })
4717
+ ] });
4567
4718
  }
4568
4719
  function Explorer(props) {
4569
4720
  const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
4570
4721
  const [revision, setRevision] = (0, react.useState)(0);
4722
+ const [statuses, setStatuses] = (0, react.useState)(/* @__PURE__ */ new Map());
4723
+ const [dropTarget, setDropTarget] = (0, react.useState)(false);
4724
+ const [selectedDirectory, setSelectedDirectory] = (0, react.useState)("");
4725
+ const picker = (0, react.useRef)(null);
4571
4726
  (0, react.useEffect)(() => {
4572
4727
  if (props.cwd) try {
4573
4728
  setExpanded(new Set(JSON.parse(localStorage.getItem(`dvw:tree:${props.cwd}`) ?? "[]")));
@@ -4578,6 +4733,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4578
4733
  (0, react.useEffect)(() => {
4579
4734
  if (props.cwd) localStorage.setItem(`dvw:tree:${props.cwd}`, JSON.stringify([...expanded]));
4580
4735
  }, [props.cwd, expanded]);
4736
+ const refreshStatus = (0, react.useCallback)(() => {
4737
+ api("git.status", { sessionId: props.sessionId }).then((status) => setStatuses(new Map(status.changes.map((change) => [change.path, gitStatusCode(change)])))).catch(() => setStatuses(/* @__PURE__ */ new Map()));
4738
+ }, [props.sessionId]);
4739
+ (0, react.useEffect)(() => {
4740
+ refreshStatus();
4741
+ const handler = () => refreshStatus();
4742
+ window.addEventListener("dvw-files-changed", handler);
4743
+ return () => window.removeEventListener("dvw-files-changed", handler);
4744
+ }, [refreshStatus]);
4581
4745
  const create = async (kind) => {
4582
4746
  const path = prompt(kind === "file" ? "新文件路径" : "新目录路径");
4583
4747
  if (!path) return;
@@ -4593,11 +4757,111 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4593
4757
  alert(String(error));
4594
4758
  }
4595
4759
  };
4760
+ const upload = (0, react.useCallback)(async (directory, files) => {
4761
+ try {
4762
+ await Promise.all([...files].map(async (file) => {
4763
+ const query = new URLSearchParams({
4764
+ sessionId: props.sessionId,
4765
+ directory,
4766
+ name: file.name
4767
+ });
4768
+ const response = await fetch(`/dsh-vscode/upload?${query}`, {
4769
+ method: "POST",
4770
+ headers: { "content-type": file.type || "application/octet-stream" },
4771
+ body: file
4772
+ });
4773
+ const answer = await response.json();
4774
+ if (!response.ok || !answer.ok) throw new Error(answer.error?.message ?? "upload failed");
4775
+ }));
4776
+ setRevision((value) => value + 1);
4777
+ window.dispatchEvent(new Event("dvw-files-changed"));
4778
+ } catch (error) {
4779
+ alert(error instanceof Error ? error.message : String(error));
4780
+ }
4781
+ }, [props.sessionId]);
4782
+ (0, react.useEffect)(() => {
4783
+ let activeTarget;
4784
+ const filesDragged = (event) => Array.from(event.dataTransfer?.types ?? []).includes("Files");
4785
+ const targetDirectory = (event) => event.target instanceof Element ? event.target.closest("[data-dvw-upload-directory]") : null;
4786
+ const clearTarget = () => {
4787
+ activeTarget?.removeAttribute("data-dvw-native-drop-target");
4788
+ activeTarget = void 0;
4789
+ };
4790
+ const intercept = (event) => {
4791
+ if (!filesDragged(event)) return;
4792
+ const target = targetDirectory(event);
4793
+ if (!target) return;
4794
+ event.preventDefault();
4795
+ event.stopImmediatePropagation();
4796
+ if (event.type === "dragleave") {
4797
+ if (!(event.relatedTarget instanceof Node && target.contains(event.relatedTarget))) clearTarget();
4798
+ return;
4799
+ }
4800
+ clearTarget();
4801
+ activeTarget = target;
4802
+ target.dataset.dvwNativeDropTarget = "true";
4803
+ if (event.type === "drop" && event.dataTransfer?.files.length) {
4804
+ const files = event.dataTransfer.files;
4805
+ clearTarget();
4806
+ window.dispatchEvent(new CustomEvent("dvw-upload-files", { detail: {
4807
+ directory: target.dataset.dvwUploadDirectory ?? "",
4808
+ files
4809
+ } }));
4810
+ }
4811
+ };
4812
+ document.addEventListener("dragenter", intercept, true);
4813
+ document.addEventListener("dragover", intercept, true);
4814
+ document.addEventListener("dragleave", intercept, true);
4815
+ document.addEventListener("drop", intercept, true);
4816
+ const uploadFiles = (event) => {
4817
+ const detail = event.detail;
4818
+ if (detail?.files?.length) upload(detail.directory, detail.files);
4819
+ };
4820
+ window.addEventListener("dvw-upload-files", uploadFiles);
4821
+ return () => {
4822
+ clearTarget();
4823
+ document.removeEventListener("dragenter", intercept, true);
4824
+ document.removeEventListener("dragover", intercept, true);
4825
+ document.removeEventListener("dragleave", intercept, true);
4826
+ document.removeEventListener("drop", intercept, true);
4827
+ window.removeEventListener("dvw-upload-files", uploadFiles);
4828
+ };
4829
+ }, [upload]);
4830
+ const drop = (event) => {
4831
+ event.preventDefault();
4832
+ setDropTarget(false);
4833
+ if (event.dataTransfer.files.length > 0) upload("", event.dataTransfer.files);
4834
+ };
4835
+ const destination = uploadDestination(selectedDirectory);
4836
+ const treeDrag = (event) => {
4837
+ if (event.dataTransfer.types.includes("Files")) {
4838
+ event.preventDefault();
4839
+ event.stopPropagation();
4840
+ setDropTarget(true);
4841
+ }
4842
+ };
4596
4843
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4597
4844
  className: "dvw-side-head",
4598
4845
  children: ["资源管理器", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4599
4846
  className: "dvw-toolbar",
4600
4847
  children: [
4848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4849
+ ref: picker,
4850
+ className: "dvw-file-picker",
4851
+ type: "file",
4852
+ accept: "*/*",
4853
+ multiple: true,
4854
+ onChange: (event) => {
4855
+ if (event.currentTarget.files) upload(destination, event.currentTarget.files);
4856
+ event.currentTarget.value = "";
4857
+ }
4858
+ }),
4859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4860
+ className: "dvw-icon",
4861
+ title: `上传文件到 ${destination || "工作区根目录"}`,
4862
+ onClick: () => picker.current?.click(),
4863
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "cloud-upload" })
4864
+ }),
4601
4865
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4602
4866
  className: "dvw-icon",
4603
4867
  title: "新建文件",
@@ -4623,22 +4887,45 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4623
4887
  })]
4624
4888
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4625
4889
  className: "dvw-tree",
4890
+ "data-dvw-upload-directory": "",
4891
+ "data-drop-target": dropTarget,
4892
+ onDragEnter: treeDrag,
4893
+ onDragOver: treeDrag,
4894
+ onDragLeave: () => setDropTarget(false),
4895
+ onDrop: drop,
4626
4896
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tree, {
4627
4897
  sessionId: props.sessionId,
4628
4898
  path: "",
4629
4899
  depth: 0,
4630
4900
  expanded,
4631
4901
  selected: props.selected,
4902
+ selectedDirectory,
4903
+ statuses,
4632
4904
  onToggle: (path) => setExpanded((current) => {
4633
4905
  const next = new Set(current);
4634
4906
  next.has(path) ? next.delete(path) : next.add(path);
4635
4907
  return next;
4636
4908
  }),
4909
+ onSelectDirectory: setSelectedDirectory,
4637
4910
  onOpen: props.onOpen,
4638
- onChange: () => setRevision((x) => x + 1)
4911
+ onChange: () => setRevision((x) => x + 1),
4912
+ onUpload: (directory, files) => void upload(directory, files)
4639
4913
  })
4640
4914
  }, revision)] });
4641
4915
  }
4916
+ /** Return the workspace-relative directory used by the file picker. */
4917
+ function uploadDestination(selectedDirectory) {
4918
+ return selectedDirectory ?? "";
4919
+ }
4920
+ /** Replace the final path component while preserving its workspace-relative directory. */
4921
+ function renamedPath(path, name) {
4922
+ return [...path.split("/").slice(0, -1), name].filter(Boolean).join("/");
4923
+ }
4924
+ function gitStatusCode(change) {
4925
+ if (change.untracked) return "A";
4926
+ const code = change.worktree !== "." ? change.worktree : change.index;
4927
+ return code === "?" ? "A" : code;
4928
+ }
4642
4929
  function MarkdownOutline({ headings, onClose, onHeading }) {
4643
4930
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
4644
4931
  className: "dvw-outline dvw-editor-outline",
@@ -4794,20 +5081,33 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4794
5081
  }
4795
5082
  function graphRows(commits) {
4796
5083
  const visible = new Set(commits.map((commit) => commit.hash));
4797
- let lanes = commits[0] ? [commits[0].hash] : [];
5084
+ let nextColor = 0;
5085
+ let lanes = commits[0] ? [{
5086
+ hash: commits[0].hash,
5087
+ color: nextColor++
5088
+ }] : [];
4798
5089
  return commits.map((commit) => {
4799
- let lane = lanes.indexOf(commit.hash);
5090
+ let lane = lanes.findIndex((track) => track.hash === commit.hash);
4800
5091
  if (lane < 0) {
4801
- lanes = [commit.hash, ...lanes];
5092
+ lanes = [{
5093
+ hash: commit.hash,
5094
+ color: nextColor++
5095
+ }, ...lanes];
4802
5096
  lane = 0;
4803
5097
  }
4804
5098
  const before = [...lanes];
4805
- const after = before.filter((hash) => hash !== commit.hash);
5099
+ const current = before[lane];
5100
+ const after = before.filter((track) => track.hash !== commit.hash);
4806
5101
  const parents = commit.parents.filter((parent) => visible.has(parent));
4807
5102
  parents.forEach((parent, index) => {
4808
- if (!after.includes(parent)) after.splice(Math.min(lane + index, after.length), 0, parent);
5103
+ if (after.some((track) => track.hash === parent)) return;
5104
+ const color = index === 0 ? current.color : nextColor++;
5105
+ after.splice(Math.min(lane + index, after.length), 0, {
5106
+ hash: parent,
5107
+ color
5108
+ });
4809
5109
  });
4810
- const parentLanes = parents.map((parent) => after.indexOf(parent)).filter((index) => index >= 0);
5110
+ const parentLanes = parents.map((parent) => after.findIndex((track) => track.hash === parent)).filter((index) => index >= 0);
4811
5111
  lanes = after;
4812
5112
  return {
4813
5113
  commit,
@@ -4825,15 +5125,23 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4825
5125
  "#d9a441",
4826
5126
  "#9b7bea"
4827
5127
  ];
4828
- function graphColor(lane) {
4829
- return GRAPH_COLORS[lane % GRAPH_COLORS.length];
5128
+ function graphColor(color) {
5129
+ return GRAPH_COLORS[color % GRAPH_COLORS.length];
4830
5130
  }
4831
5131
  function graphWidth(row) {
4832
5132
  return Math.max(row.before.length, row.after.length, row.lane + 1, 1) * 14 + 8;
4833
5133
  }
5134
+ /** Draw a fixed-radius, orthogonal transition between two graph lanes. */
5135
+ function graphTrackPath(from, to, startY, endY, bendY) {
5136
+ if (from === to) return `M ${from} ${startY} V ${endY}`;
5137
+ const direction = Math.sign(to - from);
5138
+ const radius = Math.min(5, Math.abs(to - from) / 2, Math.abs(bendY - startY), Math.abs(endY - bendY));
5139
+ return `M ${from} ${startY} V ${bendY - radius} Q ${from} ${bendY} ${from + direction * radius} ${bendY} H ${to - direction * radius} Q ${to} ${bendY} ${to} ${bendY + radius} V ${endY}`;
5140
+ }
4834
5141
  function GraphTracks({ row }) {
4835
5142
  const width = graphWidth(row);
4836
5143
  const x = row.lane * 14 + 8;
5144
+ const current = row.before[row.lane];
4837
5145
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
4838
5146
  className: "dvw-graph-svg",
4839
5147
  width,
@@ -4841,41 +5149,44 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4841
5149
  viewBox: `0 0 ${width} 38`,
4842
5150
  "aria-hidden": "true",
4843
5151
  children: [
4844
- row.before.map((hash, lane) => {
4845
- if (hash === row.commit.hash) return null;
4846
- const target = row.after.indexOf(hash);
5152
+ row.before.map((track, lane) => {
5153
+ if (track.hash === row.commit.hash) return null;
5154
+ const target = row.after.findIndex((next) => next.hash === track.hash);
4847
5155
  const nextLane = target >= 0 ? target : lane;
4848
- const next = nextLane * 14 + 8;
4849
- const start = lane * 14 + 8;
4850
5156
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4851
- d: `M ${start} 0 C ${start} 14 ${next} 24 ${next} 38`,
5157
+ d: graphTrackPath(lane * 14 + 8, nextLane * 14 + 8, 0, 38, 19),
4852
5158
  fill: "none",
4853
- stroke: graphColor(nextLane),
4854
- strokeWidth: "2"
4855
- }, `continue-${hash}`);
5159
+ stroke: graphColor(track.color),
5160
+ strokeWidth: "2",
5161
+ vectorEffect: "non-scaling-stroke"
5162
+ }, `continue-${track.hash}`);
4856
5163
  }),
4857
5164
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4858
5165
  d: `M ${x} 0 V 19`,
4859
5166
  fill: "none",
4860
- stroke: graphColor(row.lane),
4861
- strokeWidth: "2"
5167
+ stroke: graphColor(current.color),
5168
+ strokeWidth: "2",
5169
+ vectorEffect: "non-scaling-stroke"
4862
5170
  }),
4863
5171
  row.parentLanes.map((parent, index) => {
4864
5172
  const target = parent * 14 + 8;
5173
+ const track = row.after[parent];
4865
5174
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4866
- d: `M ${x} 19 C ${x} 27 ${target} 30 ${target} 38`,
5175
+ d: graphTrackPath(x, target, 19, 38, 29),
4867
5176
  fill: "none",
4868
- stroke: graphColor(parent),
4869
- strokeWidth: "2"
4870
- }, `parent-${parent}-${index}`);
5177
+ stroke: graphColor(track.color),
5178
+ strokeWidth: "2",
5179
+ vectorEffect: "non-scaling-stroke"
5180
+ }, `parent-${track.hash}-${index}`);
4871
5181
  }),
4872
5182
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
4873
5183
  cx: x,
4874
5184
  cy: "19",
4875
5185
  r: "5",
4876
5186
  fill: "var(--dvw-surface-1)",
4877
- stroke: graphColor(row.lane),
4878
- strokeWidth: "2"
5187
+ stroke: graphColor(current.color),
5188
+ strokeWidth: "2",
5189
+ vectorEffect: "non-scaling-stroke"
4879
5190
  })
4880
5191
  ]
4881
5192
  });
@@ -4889,15 +5200,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4889
5200
  viewBox: `0 0 ${width} 100`,
4890
5201
  preserveAspectRatio: "none",
4891
5202
  "aria-hidden": "true",
4892
- children: row.after.map((hash, lane) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
5203
+ children: row.after.map((track, lane) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
4893
5204
  x1: lane * 14 + 8,
4894
5205
  y1: "0",
4895
5206
  x2: lane * 14 + 8,
4896
5207
  y2: "100",
4897
- stroke: graphColor(lane),
5208
+ stroke: graphColor(track.color),
4898
5209
  strokeWidth: "2",
4899
5210
  vectorEffect: "non-scaling-stroke"
4900
- }, hash))
5211
+ }, track.hash))
4901
5212
  });
4902
5213
  }
4903
5214
  function CommitGraph({ commits, expanded, files, onToggle, onOpenFile }) {
@@ -5243,15 +5554,12 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5243
5554
  ] })
5244
5555
  ] });
5245
5556
  }
5246
- function Launcher({ wide, service }) {
5247
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5557
+ function Launcher({ service }) {
5558
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5248
5559
  className: "dvw-launch",
5249
5560
  title: "打开 VS Code 工作台",
5250
5561
  onClick: () => service.show(),
5251
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
5252
- name: "files",
5253
- className: "dvw-launch-icon"
5254
- }), wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "VS Code 工作台" })]
5562
+ children: "VS Code 工作台"
5255
5563
  });
5256
5564
  }
5257
5565
  function Workbench({ service, useSessions }) {
@@ -5465,6 +5773,14 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5465
5773
  } : tab));
5466
5774
  window.setTimeout(() => window.dispatchEvent(new CustomEvent("dvw-markdown-heading", { detail: index })), 50);
5467
5775
  };
5776
+ const suppressAttachmentDrop = (event) => {
5777
+ if (!event.dataTransfer.types.includes("Files")) return;
5778
+ const target = event.target;
5779
+ if (!(target instanceof Element && target.closest(".dvw-tree") !== null)) {
5780
+ event.preventDefault();
5781
+ event.stopPropagation();
5782
+ }
5783
+ };
5468
5784
  if (!snapshot.visible) return null;
5469
5785
  if (sessionId === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5470
5786
  className: "dvw-overlay",
@@ -5498,6 +5814,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5498
5814
  },
5499
5815
  role: "dialog",
5500
5816
  "aria-label": "VS Code 工作台",
5817
+ onDragEnterCapture: suppressAttachmentDrop,
5818
+ onDragOverCapture: suppressAttachmentDrop,
5819
+ onDropCapture: suppressAttachmentDrop,
5501
5820
  children: [
5502
5821
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DshConversationPanel, {
5503
5822
  visible: chatVisible && !settingsOpen,
@@ -5975,20 +6294,20 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5975
6294
  const CSS = String.raw`
5976
6295
  @font-face{font-family:codicon;font-display:block;src:url('/dsh-vscode/codicons/codicon.ttf') format('truetype')}
5977
6296
  .codicon{display:inline-block;font:normal normal normal 16px/1 codicon;text-align:center;text-decoration:none;text-rendering:auto;-webkit-font-smoothing:antialiased;user-select:none}
5978
- .codicon-add:before{content:"\ea60"}.codicon-git-branch:before,.codicon-source-control:before{content:"\ea68"}.codicon-search:before{content:"\ea6d"}.codicon-close:before{content:"\ea76"}.codicon-close-all:before{content:"\eac1"}.codicon-file:before{content:"\ea7b"}.codicon-new-file:before{content:"\ea7f"}.codicon-new-folder:before{content:"\ea80"}.codicon-folder:before{content:"\ea83"}.codicon-trash:before{content:"\ea81"}.codicon-code:before{content:"\eac4"}.codicon-check:before{content:"\eab2"}.codicon-chevron-down:before{content:"\eab4"}.codicon-chevron-right:before{content:"\eab6"}.codicon-color-mode:before{content:"\eac6"}.codicon-diff:before,.codicon-diff-sidebyside:before{content:"\eae1"}.codicon-diff-single:before{content:"\ec22"}.codicon-discard:before{content:"\eae2"}.codicon-file-code:before{content:"\eae9"}.codicon-file-media:before{content:"\eaea"}.codicon-file-pdf:before{content:"\eaeb"}.codicon-file-zip:before{content:"\eaef"}.codicon-files:before{content:"\eaf0"}.codicon-folder-opened:before{content:"\eaf7"}.codicon-git-commit:before{content:"\eafc"}.codicon-graph:before{content:"\eb03"}.codicon-home:before{content:"\eb06"}.codicon-list-tree:before{content:"\eb86"}.codicon-preview:before{content:"\eb2f"}.codicon-refresh:before{content:"\eb37"}.codicon-remove:before{content:"\eb3b"}.codicon-check-all:before{content:"\ebb1"}
6297
+ .codicon-add:before{content:"\ea60"}.codicon-git-branch:before,.codicon-source-control:before{content:"\ea68"}.codicon-search:before{content:"\ea6d"}.codicon-edit:before{content:"\ea73"}.codicon-close:before{content:"\ea76"}.codicon-close-all:before{content:"\eac1"}.codicon-cloud-upload:before{content:"\eac3"}.codicon-file:before{content:"\ea7b"}.codicon-new-file:before{content:"\ea7f"}.codicon-new-folder:before{content:"\ea80"}.codicon-folder:before{content:"\ea83"}.codicon-trash:before{content:"\ea81"}.codicon-code:before{content:"\eac4"}.codicon-check:before{content:"\eab2"}.codicon-chevron-down:before{content:"\eab4"}.codicon-chevron-right:before{content:"\eab6"}.codicon-color-mode:before{content:"\eac6"}.codicon-diff:before,.codicon-diff-sidebyside:before{content:"\eae1"}.codicon-diff-single:before{content:"\ec22"}.codicon-discard:before{content:"\eae2"}.codicon-file-code:before{content:"\eae9"}.codicon-file-media:before{content:"\eaea"}.codicon-file-pdf:before{content:"\eaeb"}.codicon-file-zip:before{content:"\eaef"}.codicon-files:before{content:"\eaf0"}.codicon-folder-opened:before{content:"\eaf7"}.codicon-git-commit:before{content:"\eafc"}.codicon-graph:before{content:"\eb03"}.codicon-home:before{content:"\eb06"}.codicon-list-tree:before{content:"\eb86"}.codicon-preview:before{content:"\eb2f"}.codicon-refresh:before{content:"\eb37"}.codicon-remove:before{content:"\eb3b"}.codicon-check-all:before{content:"\ebb1"}
5979
6298
  .codicon-layout-sidebar-right:before{content:"\ebf4"}.codicon-layout-sidebar-right-off:before{content:"\ec00"}.codicon-settings:before{content:"\eb51"}.codicon-sparkle:before{content:"\ec10"}
5980
- .dvw-launch{display:flex;align-items:center;gap:10px;width:100%;height:36px;padding:0 9px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#aaa);cursor:pointer;font:inherit}.dvw-launch:hover{background:var(--dsw-alias-interactive-bg-hover,#292929);color:var(--dsw-alias-label-primary,#fff)}.dvw-launch-icon{width:22px;font-size:19px}
6299
+ .dvw-launch{display:flex;align-items:center;gap:10px;width:100%;height:36px;padding:0 9px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#aaa);cursor:pointer;font:inherit}.dvw-launch:hover{background:var(--dsw-alias-interactive-bg-hover,#292929);color:var(--dsw-alias-label-primary,#fff)}.dvw-launcher-mount{position:fixed;z-index:20;right:16px;bottom:16px}.dvw-launcher-mount .dvw-launch{width:auto;height:32px;padding:0 12px;justify-content:center;border:1px solid var(--dsw-alias-border-l3,#e5e7eb);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 1px 2px rgba(0,0,0,.06);color:var(--dsw-alias-label-primary,#333)}body[data-ds-dark-theme] .dvw-launcher-mount .dvw-launch{color:var(--dsw-alias-label-primary,#ddd)}@media(max-width:640px){.dvw-launcher-mount{right:8px;bottom:8px}.dvw-launcher-mount .dvw-launch{height:30px;padding:0 10px;font-size:12px}}
5981
6300
  .dvw-overlay{--dvw-side-width:210px;--dvw-base:var(--dsw-alias-bg-base,#181818);--dvw-surface-1:var(--dsw-alias-bg-layer-1,#1e1e1e);--dvw-surface-2:var(--dsw-alias-bg-layer-2,#252526);--dvw-surface-3:var(--dsw-alias-bg-layer-3,#2d2d30);--dvw-text:var(--dsw-alias-label-primary,#ccc);--dvw-text-2:var(--dsw-alias-label-secondary,#aaa);--dvw-text-3:var(--dsw-alias-label-tertiary,#858585);--dvw-border:var(--dsw-alias-border-l2,#333);--dvw-border-soft:var(--dsw-alias-border-l3,#292929);--dvw-hover:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.08));--dvw-active:var(--dsw-alias-interactive-bg-active,rgba(255,255,255,.14));--dvw-accent:var(--dsw-alias-state-business-primary,#3b82f6);--dvw-error:var(--dsw-alias-state-error-primary,#f48771);--dvw-success:var(--dsw-alias-state-success-primary,#73c991);--dvw-warn:var(--dsw-alias-state-warn-primary,#d7ba7d);position:absolute;inset:0;z-index:100;display:grid;grid-template-columns:48px var(--dvw-side-width) minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto 24px;background:var(--dvw-base);color:var(--dvw-text);pointer-events:auto;font:13px/1.4 var(--dsw-font-family,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif);overflow:hidden;color-scheme:light}
5982
6301
  body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-scheme:dark}
5983
6302
  .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}
5984
6303
  .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}
5985
- .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-tree,.dvw-results,.dvw-git{overflow:auto;flex:1}.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]{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)}
6304
+ .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)}
5986
6305
  .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)}
5987
6306
  .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}
5988
6307
  .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}
5989
6308
  .dvw-git-head{padding:7px 10px;border-bottom:1px solid var(--dvw-border);display:flex;align-items:center;gap:8px}.dvw-branch{min-width:0;border:0;background:transparent;color:var(--dvw-text);font:inherit;font-weight:600;display:flex;align-items:center;gap:5px;cursor:pointer}.dvw-commit{display:grid;grid-template-columns:1fr auto;gap:6px;padding:9px}.dvw-commit .dvw-input{grid-column:1/3}.dvw-commit-button{grid-column:1/3;height:31px;border:0;border-radius:6px;background:color-mix(in srgb,var(--dvw-accent) 24%,var(--dvw-surface-2));color:var(--dvw-text);display:flex;align-items:center;justify-content:center;gap:7px;cursor:pointer}.dvw-commit-button:hover{background:color-mix(in srgb,var(--dvw-accent) 34%,var(--dvw-surface-2))}.dvw-commit-button:disabled{opacity:.45;cursor:default}.dvw-group-head{height:30px;display:flex;align-items:center;gap:5px;padding:0 6px 0 9px;border-radius:5px;font-weight:600;position:sticky;top:0;background:var(--dvw-surface-1);z-index:1}.dvw-group-head:hover{background:var(--dvw-hover)}.dvw-group-head .dvw-count{margin-left:auto;min-width:20px;height:20px;border-radius:10px;display:grid;place-items:center;background:var(--dvw-surface-3);color:var(--dvw-text-2);font-size:11px}.dvw-change{display:flex;align-items:center;min-height:28px;padding:3px 5px 3px 19px;gap:5px;cursor:pointer}.dvw-change:hover{background:var(--dvw-hover)}.dvw-change .dvw-icon{opacity:0}.dvw-change:hover .dvw-icon,.dvw-change:focus-within .dvw-icon{opacity:1}.dvw-change-path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.dvw-badge{font-size:11px;color:var(--dvw-warn)}.dvw-graph{border-top:1px solid var(--dvw-border-soft);margin-top:8px;padding-top:3px}.dvw-graph-list{padding-bottom:8px}.dvw-graph-row{width:100%;height:38px;border:0;background:transparent;color:var(--dvw-text);display:flex;align-items:center;padding:0 7px;text-align:left;cursor:pointer;overflow:hidden}.dvw-graph-row:hover{background:var(--dvw-hover)}.dvw-graph-svg{flex:none;align-self:stretch}.dvw-graph-copy{display:flex;min-width:0;flex:1;flex-direction:column;justify-content:center}.dvw-graph-subject{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-graph-meta{font-size:11px;color:var(--dvw-text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-ref{max-width:82px;margin-left:4px;padding:1px 5px;border:1px solid color-mix(in srgb,var(--dvw-accent) 50%,transparent);border-radius:9px;color:var(--dvw-accent);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-error{padding:8px;color:var(--dvw-error);white-space:pre-wrap}.dvw-diff{margin:0;height:100%;overflow:auto;padding:8px 0;background:var(--dvw-surface-1);font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre}.dvw-diff-line{display:block;min-height:18px;padding:0 14px}.dvw-diff-line.add{background:color-mix(in srgb,var(--dvw-success) 15%,transparent);color:var(--dvw-success)}.dvw-diff-line.remove{background:color-mix(in srgb,var(--dvw-error) 13%,transparent);color:var(--dvw-error)}.dvw-diff-line.hunk{background:color-mix(in srgb,var(--dvw-accent) 12%,transparent);color:var(--dvw-accent)}.dvw-diff-line.meta{color:var(--dvw-text-3)}.dvw-diff-empty{padding:24px;color:var(--dvw-text-3)}
5990
6309
  .dvw-tabs{overflow:hidden}.dvw-tab-list{display:flex;min-width:0;flex:1;overflow-x:auto}.dvw-tab-actions{flex:none;width:42px;display:grid;place-items:center;padding:0 6px;background:var(--dvw-base)}.dvw-close-all{border:1px solid var(--dvw-border);background:var(--dvw-base)}.dvw-monaco-diff{position:absolute;inset:0}.dvw-diff-toolbar{justify-content:flex-end}.dvw-group-head{gap:2px;padding-left:2px}.dvw-group-toggle{min-width:0;height:30px;flex:1;border:0;border-radius:5px;background:transparent;color:var(--dvw-text);font:inherit;font-weight:600;display:flex;align-items:center;gap:5px;padding:0 7px;cursor:pointer;text-align:left}.dvw-group-toggle:hover{background:var(--dvw-hover)}.dvw-graph-entry[data-expanded=true]{background:color-mix(in srgb,var(--dvw-accent) 5%,transparent)}.dvw-commit-files{padding:1px 0 5px 26px}.dvw-commit-file{width:100%;height:27px;border:0;background:transparent;color:var(--dvw-text);display:flex;align-items:center;gap:6px;padding:0 7px;text-align:left;cursor:pointer}.dvw-commit-file:hover{background:var(--dvw-hover)}.dvw-commit-file-name{white-space:nowrap}.dvw-commit-file-dir{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dvw-text-3);font-size:11px}
5991
- .dvw-sidebar-launcher-mount{flex:none;padding:4px 8px 6px;border-bottom:1px solid var(--dvw-border-soft)}.dvw-sidebar-launcher-mount .dvw-launch{color:var(--dsw-alias-label-primary,#333)}body[data-ds-dark-theme] .dvw-sidebar-launcher-mount .dvw-launch{color:var(--dsw-alias-label-primary,#ddd)}.dvw-outline{flex:none;max-height:38%;display:flex;flex-direction:column;border-bottom:1px solid var(--dvw-border)}.dvw-outline-head{height:30px;display:flex;align-items:center;justify-content:space-between;padding:0 5px 0 10px;font-weight:600}.dvw-outline-head>span{display:flex;align-items:center;gap:6px}.dvw-outline-list{overflow:auto;padding-bottom:5px}.dvw-outline-item{display:block;width:100%;height:25px;border:0;background:transparent;color:var(--dvw-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left;cursor:pointer}.dvw-outline-item:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-image-preview{position:absolute;inset:0;overflow:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:24px;background:repeating-conic-gradient(var(--dvw-surface-2) 0 25%,var(--dvw-surface-1) 0 50%) 0/20px 20px}.dvw-image-preview img{max-width:100%;max-height:calc(100% - 32px);object-fit:contain;box-shadow:0 4px 24px rgba(0,0,0,.22)}.dvw-image-preview span{max-width:100%;padding:3px 8px;border-radius:5px;background:var(--dvw-surface-1);color:var(--dvw-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6310
+ .dvw-outline{flex:none;max-height:38%;display:flex;flex-direction:column;border-bottom:1px solid var(--dvw-border)}.dvw-outline-head{height:30px;display:flex;align-items:center;justify-content:space-between;padding:0 5px 0 10px;font-weight:600}.dvw-outline-head>span{display:flex;align-items:center;gap:6px}.dvw-outline-list{overflow:auto;padding-bottom:5px}.dvw-outline-item{display:block;width:100%;height:25px;border:0;background:transparent;color:var(--dvw-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left;cursor:pointer}.dvw-outline-item:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-image-preview{position:absolute;inset:0;overflow:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:24px;background:repeating-conic-gradient(var(--dvw-surface-2) 0 25%,var(--dvw-surface-1) 0 50%) 0/20px 20px}.dvw-image-preview img{max-width:100%;max-height:calc(100% - 32px);object-fit:contain;box-shadow:0 4px 24px rgba(0,0,0,.22)}.dvw-image-preview span{max-width:100%;padding:3px 8px;border-radius:5px;background:var(--dvw-surface-1);color:var(--dvw-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5992
6311
  .dvw-markdown{height:100%;overflow:auto;padding:28px clamp(24px,6vw,80px) 60px;color:var(--dvw-text);font-size:15px;line-height:1.65}.dvw-markdown>*:first-child{margin-top:0}.dvw-markdown h1,.dvw-markdown h2{padding-bottom:.3em;border-bottom:1px solid var(--dvw-border)}.dvw-markdown h1{font-size:2em}.dvw-markdown h2{font-size:1.5em}.dvw-markdown h3{font-size:1.25em}.dvw-markdown a{color:var(--dvw-accent)}.dvw-markdown code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;background:var(--dvw-surface-2);border-radius:4px;padding:.15em .35em}.dvw-markdown pre{overflow:auto;padding:14px;border:1px solid var(--dvw-border-soft);border-radius:7px;background:var(--dvw-base)}.dvw-markdown pre code{padding:0;background:transparent}.dvw-markdown blockquote{margin-left:0;padding-left:14px;border-left:3px solid var(--dvw-border);color:var(--dvw-text-2)}.dvw-markdown table{border-collapse:collapse;display:block;overflow:auto}.dvw-markdown th,.dvw-markdown td{padding:6px 12px;border:1px solid var(--dvw-border)}.dvw-markdown tr:nth-child(2n){background:var(--dvw-hover)}.dvw-markdown img{max-width:100%}.dvw-markdown hr{border:0;border-top:1px solid var(--dvw-border)}
5993
6312
  .dvw-sidebar-launcher-mount{border-bottom:1px solid var(--dsw-alias-border-l3,#e5e7eb)}.dvw-outline-toggle{margin-right:auto}.dvw-outline-toggle[data-active=true]{background:var(--dvw-active);color:var(--dvw-text)}.dvw-editor-outline{position:absolute;z-index:2;top:36px;bottom:0;left:0;width:240px;max-height:none;background:var(--dvw-surface-1);border-right:1px solid var(--dvw-border);border-bottom:0}.dvw-editor[data-outline=true] .dvw-editor-body{left:240px}.dvw-diff-preview-switch{margin-right:auto;display:flex;gap:4px}.dvw-commit-files{position:relative;padding-top:1px;padding-right:0;padding-bottom:5px}.dvw-graph-continuation{position:absolute;top:0;bottom:0;left:7px;height:100%;pointer-events:none}.dvw-commit-file-list{position:relative;min-width:0}.dvw-overlay button:focus-visible,.dvw-overlay input:focus-visible,.dvw-overlay select:focus-visible{outline:1px solid var(--dvw-accent);outline-offset:-1px}@media(max-width:800px){.dvw-overlay{grid-template-columns:44px var(--dvw-side-width) minmax(0,1fr)}.dvw-editor-outline{width:210px}.dvw-editor[data-outline=true] .dvw-editor-body{left:210px}}
5994
6313
  .dvw-overlay[data-dvw-chat=true]{grid-template-columns:48px var(--dvw-side-width) minmax(0,1fr) var(--dvw-chat-width)}
@@ -6006,6 +6325,8 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
6006
6325
  .dvw-chat-toggle{position:absolute;z-index:5;top:6px;right:6px;border:1px solid var(--dvw-border);background:var(--dvw-base)}
6007
6326
  .dvw-overlay:not([data-dvw-chat=true]) .dvw-tabs{padding-right:36px}
6008
6327
  [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}
6328
+ .dvw-confirm-backdrop{position:fixed;z-index:50;inset:0;display:grid;place-items:center;padding:24px;background:rgba(0,0,0,.38)}.dvw-confirm{width:min(380px,calc(100vw - 48px));border:1px solid var(--dvw-border);border-radius:7px;background:var(--dvw-surface-1);box-shadow:0 12px 36px rgba(0,0,0,.34);padding:18px}.dvw-confirm h2{margin:0 0 12px;font-size:15px}.dvw-confirm p{margin:0;color:var(--dvw-text)}.dvw-confirm-note{margin-top:8px!important;color:var(--dvw-text-3)!important;font-size:12px}.dvw-confirm footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.dvw-confirm footer button{height:28px;border:1px solid var(--dvw-border);border-radius:4px;background:var(--dvw-surface-2);color:var(--dvw-text);padding:0 12px;font:inherit;cursor:pointer}.dvw-confirm footer button:hover{background:var(--dvw-hover)}.dvw-confirm footer .dvw-danger-button{border-color:color-mix(in srgb,var(--dvw-error) 65%,var(--dvw-border));background:var(--dvw-error);color:#fff}.dvw-confirm footer .dvw-danger-button:hover{background:color-mix(in srgb,var(--dvw-error) 82%,#000)}
6329
+ .dvw-tree[data-dvw-native-drop-target=true]{outline:1px dashed var(--dvw-accent);outline-offset:-3px}.dvw-tree-row[data-dvw-native-drop-target=true]{background:var(--dvw-active)}
6009
6330
  .dvw-settings-backdrop{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px;background:rgba(0,0,0,.34);backdrop-filter:blur(2px)}
6010
6331
  .dvw-settings{width:min(720px,calc(100vw - 48px));max-height:min(720px,calc(100vh - 48px));display:flex;flex-direction:column;border:1px solid var(--dvw-border);border-radius:10px;background:var(--dvw-surface-1);box-shadow:0 16px 50px rgba(0,0,0,.32);overflow:hidden}
6011
6332
  .dvw-settings>header{height:44px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 10px 0 14px;border-bottom:1px solid var(--dvw-border);font-size:14px;font-weight:600}.dvw-settings>header>span{display:flex;align-items:center;gap:8px}
@@ -6028,6 +6349,21 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
6028
6349
  };
6029
6350
  }
6030
6351
  //#endregion
6352
+ //#region src/client/shortcuts.ts
6353
+ /** Handle terminal shortcuts through contributions registered by a terminal adapter. */
6354
+ function handleTerminalShortcut(event, workbench) {
6355
+ if (event.isComposing || !event.ctrlKey || event.altKey || event.code !== "Backquote") return false;
6356
+ if (!workbench.getPanels().some((panel) => panel.id === "terminal")) return false;
6357
+ event.preventDefault();
6358
+ event.stopImmediatePropagation();
6359
+ workbench.show();
6360
+ if (event.shiftKey) {
6361
+ if (workbench.getSnapshot().bottomPanel !== "terminal") workbench.toggleBottomPanel("terminal");
6362
+ queueMicrotask(() => workbench.runCommand("terminal.new"));
6363
+ } else workbench.toggleBottomPanel("terminal");
6364
+ return true;
6365
+ }
6366
+ //#endregion
6031
6367
  //#region src/client/index.tsx
6032
6368
  const Overlay = ({ service, useSessions }) => {
6033
6369
  const sessionId = useSessions((state) => state.current);
@@ -6036,33 +6372,18 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
6036
6372
  useSessions
6037
6373
  }, sessionId ?? "no-session");
6038
6374
  };
6039
- const Footer = ({ service, wide }) => {
6040
- const marker = (0, react.useRef)(null);
6375
+ const Footer = ({ service }) => {
6041
6376
  const [target, setTarget] = (0, react.useState)();
6377
+ const [visible, setVisible] = (0, react.useState)(() => service.getSnapshot().visible);
6042
6378
  (0, react.useLayoutEffect)(() => {
6043
- let container = marker.current?.parentElement;
6044
- while (container?.previousElementSibling === null) container = container.parentElement;
6045
- const region = container?.previousElementSibling;
6046
- if (!(region instanceof HTMLElement)) {
6047
- setTarget(null);
6048
- return;
6049
- }
6050
6379
  const mount = document.createElement("div");
6051
- mount.className = "dvw-sidebar-launcher-mount";
6052
- region.prepend(mount);
6380
+ mount.className = "dvw-launcher-mount";
6381
+ document.body.append(mount);
6053
6382
  setTarget(mount);
6054
6383
  return () => mount.remove();
6055
6384
  }, []);
6056
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6057
- ref: marker,
6058
- hidden: true
6059
- }), target === void 0 ? null : target === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Launcher, {
6060
- service,
6061
- wide
6062
- }) : (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Launcher, {
6063
- service,
6064
- wide
6065
- }), target)] });
6385
+ (0, react.useEffect)(() => service.subscribe(() => setVisible(service.getSnapshot().visible)), [service]);
6386
+ return target === void 0 || visible ? null : (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Launcher, { service }), target);
6066
6387
  };
6067
6388
  const inject = ["slots"];
6068
6389
  function apply(ctx) {
@@ -6086,6 +6407,7 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
6086
6407
  }, Overlay));
6087
6408
  ctx.effect(() => {
6088
6409
  const onKey = (event) => {
6410
+ if (handleTerminalShortcut(event, service)) return;
6089
6411
  if (event.isComposing || !event.ctrlKey || event.altKey) return;
6090
6412
  const view = event.shiftKey && event.code === "KeyE" ? "explorer" : event.shiftKey && event.code === "KeyF" ? "search" : event.shiftKey && event.code === "KeyG" ? "git" : void 0;
6091
6413
  if (view !== void 0) {