@playwright-repl/browser-extension 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/dist/background.js +162567 -0
  4. package/dist/background.js.map +1 -0
  5. package/dist/content/recorder.js +479 -0
  6. package/dist/content/trace-loader.js +12 -0
  7. package/dist/devtools/console.html +17 -0
  8. package/dist/devtools/console.js +44 -0
  9. package/dist/devtools/console.js.map +1 -0
  10. package/dist/devtools/devtools.html +8 -0
  11. package/dist/devtools/devtools.js +7 -0
  12. package/dist/devtools/devtools.js.map +1 -0
  13. package/dist/icons/dramaturg_icon_128.png +0 -0
  14. package/dist/icons/dramaturg_icon_16.png +0 -0
  15. package/dist/icons/dramaturg_icon_32.png +0 -0
  16. package/dist/icons/dramaturg_icon_48.png +0 -0
  17. package/dist/index.css +1353 -0
  18. package/dist/index.js +12462 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/index2.js +27328 -0
  21. package/dist/index2.js.map +1 -0
  22. package/dist/manifest.json +49 -0
  23. package/dist/modulepreload-polyfill.js +30 -0
  24. package/dist/modulepreload-polyfill.js.map +1 -0
  25. package/dist/offscreen/offscreen.html +6 -0
  26. package/dist/offscreen/offscreen.js +151 -0
  27. package/dist/offscreen/offscreen.js.map +1 -0
  28. package/dist/panel/panel.html +16 -0
  29. package/dist/panel/panel.js +2258 -0
  30. package/dist/panel/panel.js.map +1 -0
  31. package/dist/preferences/preferences.html +14 -0
  32. package/dist/preferences/preferences.js +102 -0
  33. package/dist/preferences/preferences.js.map +1 -0
  34. package/dist/settings.js +13 -0
  35. package/dist/settings.js.map +1 -0
  36. package/dist/sw-debugger-core.js +1139 -0
  37. package/dist/sw-debugger-core.js.map +1 -0
  38. package/package.json +80 -0
@@ -0,0 +1,2258 @@
1
+ import "../modulepreload-polyfill.js";
2
+ import { j as jsxRuntimeExports, r as reactExports, c as clientExports } from "../index.js";
3
+ import { b as buildPickResult, s as swTerminateExecution, a as swDebugResume, r as runJsScript, c as runJsScriptStep, d as runAndDispatch, e as attachToTab, S as StateEffect, g as getPanel, E as EditorSelection, f as showDialog, h as EditorView, P as Prec, i as StateField, j as showPanel, k as codePointAt, l as codePointSize, V as ViewPlugin, D as Decoration, R as RangeSetBuilder, F as Facet, m as combineConfig, n as EditorState, o as fromCodePoint, p as crelt, q as runScopeHandlers, C as CharCategory, t as findClusterBreak, u as StreamLanguage, H as HighlightStyle, v as tags, w as syntaxHighlighting, W as WidgetType, x as gutter, y as autocompletion, z as placeholder, A as javascript, B as javascriptLanguage, G as playwrightCompletions, I as Compartment, J as lineNumbers, K as highlightActiveLineGutter, L as highlightActiveLine, M as history, N as bracketMatching, O as closeBrackets, Q as keymap, T as acceptCompletion, U as closeBracketsKeymap, X as defaultKeymap, Y as historyKeymap, Z as RangeSet, _ as pwCompletion, $ as GutterMarker, a0 as swGetProperties, a1 as fromCdpGetProperties, a2 as ObjectTree, a3 as Console, a4 as swDebugStepOver, a5 as swDebugStepInto, a6 as swDebugStepOut, a7 as inlineSummary, a8 as panelReducer, a9 as initialState, aa as onConsoleEvent } from "../index2.js";
4
+ import { l as loadSettings, s as storeSettings } from "../settings.js";
5
+ import { C as COMMAND_NAMES } from "../sw-debugger-core.js";
6
+ function SunIcon({ size = 16 }) {
7
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
8
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "5" }),
9
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "1", x2: "12", y2: "3" }),
10
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "21", x2: "12", y2: "23" }),
11
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "4.22", y1: "4.22", x2: "5.64", y2: "5.64" }),
12
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18.36", y1: "18.36", x2: "19.78", y2: "19.78" }),
13
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "1", y1: "12", x2: "3", y2: "12" }),
14
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "21", y1: "12", x2: "23", y2: "12" }),
15
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "4.22", y1: "19.78", x2: "5.64", y2: "18.36" }),
16
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "18.36", y1: "5.64", x2: "19.78", y2: "4.22" })
17
+ ] });
18
+ }
19
+ function MoonIcon({ size = 16 }) {
20
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "none", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" }) });
21
+ }
22
+ function FolderOpenIcon({ size = 16 }) {
23
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "none", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" }) });
24
+ }
25
+ function SaveIcon({ size = 16 }) {
26
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
27
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
28
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "17 21 17 13 7 13 7 21" }),
29
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "7 3 7 8 15 8" })
30
+ ] });
31
+ }
32
+ function RecordIcon({ size = 16 }) {
33
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "7" }) });
34
+ }
35
+ function StopIcon({ size = 16 }) {
36
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1" }) });
37
+ }
38
+ function StepForwardIcon({ size = 16 }) {
39
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "none", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
40
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "3", y1: "12", x2: "15", y2: "12" }),
41
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "11 8 15 12 11 16" }),
42
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "19", y1: "7", x2: "19", y2: "17" })
43
+ ] });
44
+ }
45
+ function PlugIcon({ size = 16 }) {
46
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "none", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
47
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "1", y1: "12", x2: "3", y2: "12" }),
48
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3 8a2 2 0 0 1 2-2h2v12H5a2 2 0 0 1-2-2z" }),
49
+ /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "8", width: "3", height: "8", rx: "1" }),
50
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M16 6h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2z" }),
51
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "20", y1: "12", x2: "23", y2: "12" })
52
+ ] });
53
+ }
54
+ function UnplugIcon({ size = 16 }) {
55
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "none", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
56
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "1", y1: "12", x2: "5", y2: "12" }),
57
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M5 8a2 2 0 0 1 2-2h2v12H7a2 2 0 0 1-2-2z" }),
58
+ /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "9", y: "8", width: "6", height: "8", rx: "1" }),
59
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M15 6h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2z" }),
60
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "19", y1: "12", x2: "23", y2: "12" })
61
+ ] });
62
+ }
63
+ function BugIcon({ size = 16 }) {
64
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: size, height: size, stroke: "currentColor", fill: "none", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
65
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M8 2l1.88 1.88" }),
66
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M14.12 3.88L16 2" }),
67
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
68
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
69
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 20v-9" }),
70
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
71
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 13H2" }),
72
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
73
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
74
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M22 13h-4" }),
75
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
76
+ ] });
77
+ }
78
+ function ContinueIcon({ size = 16 }) {
79
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 16 16", width: size, height: size, fill: "currentColor", children: [
80
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3.5 2v12l9-6-9-6z" }),
81
+ /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "12", y: "2", width: "2", height: "12" })
82
+ ] });
83
+ }
84
+ function StepOverIcon({ size = 16 }) {
85
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 16 16", width: size, height: size, fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9.99993 13C9.99993 14.103 9.10293 15 7.99993 15C6.89693 15 5.99993 14.103 5.99993 13C5.99993 11.897 6.89693 11 7.99993 11C9.10293 11 9.99993 11.897 9.99993 13ZM13.2499 2C12.8359 2 12.4999 2.336 12.4999 2.75V4.027C11.3829 2.759 9.75993 2 7.99993 2C5.03293 2 2.47993 4.211 2.06093 7.144C2.00193 7.554 2.28793 7.934 2.69793 7.993C2.73393 7.999 2.76993 8.001 2.80493 8.001C3.17193 8.001 3.49293 7.731 3.54693 7.357C3.86093 5.159 5.77593 3.501 8.00093 3.501C9.52993 3.501 10.9199 4.264 11.7439 5.501H9.75093C9.33693 5.501 9.00093 5.837 9.00093 6.251C9.00093 6.665 9.33693 7.001 9.75093 7.001H13.2509C13.6649 7.001 14.0009 6.665 14.0009 6.251V2.751C14.0009 2.337 13.6649 2.001 13.2509 2.001L13.2499 2Z" }) });
86
+ }
87
+ function StepIntoIcon({ size = 16 }) {
88
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 16 16", width: size, height: size, fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M10 13C10 14.103 9.10304 15 8.00004 15C6.89704 15 6.00004 14.103 6.00004 13C6.00004 11.897 6.89704 11 8.00004 11C9.10304 11 10 11.897 10 13ZM12.03 5.22C11.737 4.927 11.262 4.927 10.969 5.22L8.74904 7.44V1.75C8.74904 1.336 8.41304 1 7.99904 1C7.58504 1 7.24904 1.336 7.24904 1.75V7.439L5.02904 5.219C4.73604 4.926 4.26104 4.926 3.96804 5.219C3.67504 5.512 3.67504 5.987 3.96804 6.28L7.46804 9.78C7.61404 9.926 7.80604 10 7.99804 10C8.19004 10 8.38204 9.927 8.52804 9.78L12.028 6.28C12.321 5.987 12.321 5.512 12.028 5.219L12.03 5.22Z" }) });
89
+ }
90
+ function StepOutIcon({ size = 16 }) {
91
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 16 16", width: size, height: size, fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9.99802 13C9.99802 14.103 9.10102 15 7.99802 15C6.89502 15 5.99802 14.103 5.99802 13C5.99802 11.897 6.89502 11 7.99802 11C9.10102 11 9.99802 11.897 9.99802 13ZM12.03 4.71999L8.53002 1.21999C8.23702 0.926994 7.76202 0.926994 7.46902 1.21999L3.96902 4.71999C3.67602 5.01299 3.67602 5.48799 3.96902 5.78099C4.26202 6.07399 4.73702 6.07399 5.03002 5.78099L7.25002 3.56099V9.24999C7.25002 9.66399 7.58602 9.99999 8.00002 9.99999C8.41402 9.99999 8.75002 9.66399 8.75002 9.24999V3.56099L10.97 5.78099C11.116 5.92699 11.308 6.00099 11.5 6.00099C11.692 6.00099 11.884 5.92799 12.03 5.78099C12.323 5.48799 12.323 5.01299 12.03 4.71999Z" }) });
92
+ }
93
+ function RestartIcon({ size = 16 }) {
94
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 16 16", width: size, height: size, fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12.75 8a4.5 4.5 0 0 1-4.5 4.5c-2.485 0-4.5-2.015-4.5-4.5S5.765 3.5 8.25 3.5V2L11 4.5 8.25 7V5.5a2.5 2.5 0 1 0 2.5 2.5h2z" }) });
95
+ }
96
+ function DebugStopIcon({ size = 16 }) {
97
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 16 16", width: size, height: size, fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12.5 3.5V12.5H3.5V3.5H12.5ZM12.5 2H3.5C2.672 2 2 2.672 2 3.5V12.5C2 13.328 2.672 14 3.5 14H12.5C13.328 14 14 13.328 14 12.5V3.5C14 2.672 13.328 2 12.5 2Z" }) });
98
+ }
99
+ function ScreencastIcon({ size = 16 }) {
100
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: size, lineHeight: 1, filter: "grayscale(1)" }, children: "🎬" });
101
+ }
102
+ function CrosshairIcon({ size = 16 }) {
103
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 16 16", width: size, height: size, fill: "none", stroke: "currentColor", strokeWidth: "1.5", xmlns: "http://www.w3.org/2000/svg", children: [
104
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "8", cy: "8", r: "5" }),
105
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "8", cy: "8", r: "2" }),
106
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "8", y1: "1", x2: "8", y2: "3" }),
107
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "8", y1: "13", x2: "8", y2: "15" }),
108
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "1", y1: "8", x2: "3", y2: "8" }),
109
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "13", y1: "8", x2: "15", y2: "8" })
110
+ ] });
111
+ }
112
+ function Toolbar({ editorContent, editorMode, stepLine, attachedUrl, attachedTabId, isAttaching, isRunning, isStepDebugging, breakPoints, dispatch, editorRef }) {
113
+ const fileInputRef = reactExports.useRef(null);
114
+ const cancelRunRef = reactExports.useRef(false);
115
+ const [isRecording, setIsRecording] = reactExports.useState(false);
116
+ const [isPicking, setIsPicking] = reactExports.useState(false);
117
+ const [isDarkMode, setIsDarkMode] = reactExports.useState(() => localStorage.getItem("theme") === "dark");
118
+ const [availableTabs, setAvailableTabs] = reactExports.useState([]);
119
+ const [canAttach, setCanAttach] = reactExports.useState(true);
120
+ const [selectedTabId, setSelectedTabId] = reactExports.useState(null);
121
+ const [isVideoRecording, setIsVideoRecording] = reactExports.useState(false);
122
+ const [videoElapsed, setVideoElapsed] = reactExports.useState(0);
123
+ const videoStartTimeRef = reactExports.useRef(0);
124
+ const lines = reactExports.useMemo(() => editorContent.split("\n"), [editorContent]);
125
+ function isInternalUrl(url) {
126
+ if (!url) return true;
127
+ return url.startsWith("chrome://") || url.startsWith("chrome-extension://");
128
+ }
129
+ function getTabLabel(tab) {
130
+ try {
131
+ if (tab.url?.startsWith("about:")) return tab.url;
132
+ const url = new URL(tab.url);
133
+ if (url.protocol === "chrome:") return `chrome://${url.hostname}`;
134
+ return url.hostname;
135
+ } catch {
136
+ return tab.url ?? "(unknown)";
137
+ }
138
+ }
139
+ async function loadTabs() {
140
+ if (!chrome.tabs?.query) return;
141
+ const tabs = await chrome.tabs.query({});
142
+ const ownOrigin = `chrome-extension://${chrome.runtime.id}/`;
143
+ const panelUrl = `${ownOrigin}panel/panel.html`;
144
+ setAvailableTabs(tabs.filter((t) => t?.url && !t.url.startsWith(panelUrl) && (!t.url.startsWith("chrome-extension://") || t.url.startsWith(ownOrigin))));
145
+ }
146
+ async function checkActiveTab() {
147
+ if (!chrome.tabs?.query) return;
148
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
149
+ setCanAttach(!isInternalUrl(tab?.url));
150
+ }
151
+ reactExports.useEffect(() => {
152
+ loadTabs();
153
+ checkActiveTab();
154
+ if (!chrome.tabs?.onActivated) return;
155
+ const onActivated = (info) => {
156
+ setSelectedTabId(info.tabId);
157
+ checkActiveTab();
158
+ };
159
+ const onUpdated = (_tabId, changeInfo) => {
160
+ if (changeInfo.url) loadTabs();
161
+ };
162
+ chrome.tabs.onActivated.addListener(onActivated);
163
+ chrome.tabs.onUpdated.addListener(onUpdated);
164
+ return () => {
165
+ chrome.tabs.onActivated.removeListener(onActivated);
166
+ chrome.tabs.onUpdated.removeListener(onUpdated);
167
+ };
168
+ }, []);
169
+ async function doAttach(tabId) {
170
+ dispatch({ type: "ATTACH_START" });
171
+ const res = await attachToTab(tabId);
172
+ if (res.ok && res.url) {
173
+ dispatch({ type: "ATTACH_SUCCESS", url: res.url, tabId });
174
+ setSelectedTabId(null);
175
+ } else {
176
+ dispatch({ type: "ATTACH_FAIL" });
177
+ dispatch({ type: "ADD_LINE", line: { text: `Attach failed: ${res.error ?? "unknown error"}`, type: "error" } });
178
+ }
179
+ }
180
+ async function handleTabChange(tabId) {
181
+ const tab = availableTabs.find((t) => t.id === tabId);
182
+ setSelectedTabId(tabId);
183
+ chrome.tabs.update(tabId, { active: true }).catch((e) => console.debug("[pw-repl] tab activate:", e));
184
+ if (tab && isInternalUrl(tab.url)) {
185
+ dispatch({ type: "DETACH" });
186
+ return;
187
+ }
188
+ await doAttach(tabId);
189
+ }
190
+ async function handleAttach() {
191
+ const targetTabId = selectedTabId ?? attachedTabId;
192
+ if (targetTabId !== null) {
193
+ const tab2 = availableTabs.find((t) => t.id === targetTabId);
194
+ if (tab2 && isInternalUrl(tab2.url)) return;
195
+ await doAttach(targetTabId);
196
+ return;
197
+ }
198
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
199
+ if (!tab?.id || isInternalUrl(tab.url)) return;
200
+ await doAttach(tab.id);
201
+ }
202
+ function handleFileChange(event) {
203
+ const file = event.target.files?.[0];
204
+ if (!file) return;
205
+ const fileReader = new FileReader();
206
+ fileReader.onload = () => {
207
+ dispatch({ type: "EDIT_EDITOR_CONTENT", content: fileReader.result });
208
+ if (file.name.endsWith(".js")) dispatch({ type: "SET_EDITOR_MODE", mode: "js" });
209
+ else if (file.name.endsWith(".pw") || file.name.endsWith(".txt")) dispatch({ type: "SET_EDITOR_MODE", mode: "pw" });
210
+ fileInputRef.current.value = "";
211
+ };
212
+ fileReader.onerror = () => {
213
+ dispatch({ type: "ADD_LINE", line: { text: "Failed to read file", type: "error" } });
214
+ };
215
+ fileReader.readAsText(file);
216
+ }
217
+ function handleFileOpen() {
218
+ fileInputRef.current.click();
219
+ }
220
+ async function handleSave() {
221
+ const isJs = editorMode === "js";
222
+ const ext = isJs ? ".js" : ".pw";
223
+ const defaultName = `commands-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}${ext}`;
224
+ const opts = {
225
+ suggestedName: defaultName,
226
+ types: isJs ? [{ description: "JavaScript files", accept: { "text/javascript": [".js"] } }] : [{ description: "PW command files", accept: { "text/plain": [".pw"] } }]
227
+ };
228
+ try {
229
+ const fileHandle = await window.showSaveFilePicker(opts);
230
+ const writable = await fileHandle.createWritable();
231
+ await writable.write(editorContent);
232
+ await writable.close();
233
+ } catch (e) {
234
+ if (e instanceof Error && e.name !== "AbortError") {
235
+ dispatch({ type: "ADD_LINE", line: { text: "Save failed: " + e.message, type: "error" } });
236
+ }
237
+ }
238
+ }
239
+ async function runCommand(index, command) {
240
+ dispatch({ type: "SET_RUN_LINE", currentRunLine: index });
241
+ const result = await runAndDispatch(command, dispatch);
242
+ dispatch({ type: "SET_LINE_RESULT", index, result: result.isError ? "fail" : "pass" });
243
+ }
244
+ async function handleRun() {
245
+ cancelRunRef.current = false;
246
+ dispatch({ type: "RUN_START" });
247
+ if (editorMode === "js") {
248
+ await runJsScript(editorContent, dispatch);
249
+ } else {
250
+ for (let i = 0; i < lines.length; i++) {
251
+ if (cancelRunRef.current) break;
252
+ const trimmedValue = lines[i].trim();
253
+ if (!lines[i].startsWith("#") && trimmedValue) {
254
+ await runCommand(i, trimmedValue);
255
+ }
256
+ }
257
+ if (!cancelRunRef.current) {
258
+ dispatch({ type: "ADD_LINE", line: { text: "Run complete.", type: "info" } });
259
+ }
260
+ }
261
+ dispatch({ type: "RUN_STOP" });
262
+ }
263
+ function handleStop() {
264
+ cancelRunRef.current = true;
265
+ if (isStepDebugging) {
266
+ swTerminateExecution().catch((e) => console.warn("[debug] terminate failed:", e));
267
+ swDebugResume().catch((e) => console.warn("[debug] resume failed:", e));
268
+ }
269
+ dispatch({ type: "RUN_STOP" });
270
+ }
271
+ function findExecutableIndex(fromIndex) {
272
+ let executableIndex = -1;
273
+ for (let i = fromIndex; i < lines.length; i++) {
274
+ if (!lines[i].startsWith("#") && lines[i].trim()) {
275
+ executableIndex = i;
276
+ break;
277
+ }
278
+ }
279
+ return executableIndex;
280
+ }
281
+ async function handleDebug() {
282
+ dispatch({ type: "RUN_START", stepDebug: true });
283
+ await runJsScriptStep(editorContent, dispatch, breakPoints);
284
+ dispatch({ type: "RUN_STOP" });
285
+ }
286
+ async function handleStep() {
287
+ if (stepLine === -1) {
288
+ const nextStepLine2 = findExecutableIndex(0);
289
+ if (nextStepLine2 !== -1) dispatch({ type: "STEP_INIT", stepLine: nextStepLine2 });
290
+ return;
291
+ }
292
+ await runCommand(stepLine, lines[stepLine].trim());
293
+ const nextStepLine = findExecutableIndex(stepLine + 1);
294
+ dispatch({ type: "STEP_ADVANCE", stepLine: nextStepLine });
295
+ }
296
+ function isEditorEmpty() {
297
+ return editorContent.split("\n").every((line) => {
298
+ const trimmed = line.trim();
299
+ if (!trimmed) return true;
300
+ if (editorMode === "pw") return trimmed.startsWith("#");
301
+ return trimmed.startsWith("//") || trimmed.startsWith("/*");
302
+ });
303
+ }
304
+ reactExports.useEffect(() => {
305
+ if (!chrome.runtime?.onMessage) return;
306
+ const listener = (msg) => {
307
+ if (msg.type === "recorded-action") {
308
+ editorRef.current?.insertAtCursor(editorMode === "pw" ? msg.action.pw : msg.action.js);
309
+ }
310
+ if (msg.type === "recorded-fill-update") {
311
+ editorRef.current?.replaceLastInsert(editorMode === "pw" ? msg.action.pw : msg.action.js);
312
+ }
313
+ };
314
+ chrome.runtime.onMessage.addListener(listener);
315
+ return () => chrome.runtime.onMessage.removeListener(listener);
316
+ }, [editorMode]);
317
+ reactExports.useEffect(() => {
318
+ if (isRunning && isRecording) {
319
+ setIsRecording(false);
320
+ chrome.runtime.sendMessage({ type: "record-stop" }).catch((e) => console.debug("[pw-repl] record-stop:", e));
321
+ }
322
+ }, [isRunning]);
323
+ async function handleRecord() {
324
+ if (!chrome.tabs?.query) return;
325
+ if (isRecording) {
326
+ setIsRecording(false);
327
+ chrome.runtime.sendMessage({ type: "record-stop" }).catch((e) => console.debug("[pw-repl] record-stop:", e));
328
+ return;
329
+ }
330
+ let result;
331
+ try {
332
+ result = await chrome.runtime.sendMessage({ type: "record-start" });
333
+ } catch (e) {
334
+ dispatch({ type: "ADD_LINE", line: { text: `Recording failed: ${String(e)}`, type: "error" } });
335
+ return;
336
+ }
337
+ if (!result?.ok) {
338
+ dispatch({ type: "ADD_LINE", line: { text: `Recording failed: ${result?.error ?? "unknown error"}`, type: "error" } });
339
+ return;
340
+ }
341
+ setIsRecording(true);
342
+ if (result.url && result.url !== "about:blank" && !result.url.startsWith("chrome://") && isEditorEmpty()) {
343
+ const gotoCmd = editorMode === "js" ? `await page.goto(${JSON.stringify(result.url)});` : `goto "${result.url}"`;
344
+ editorRef.current?.insertAtCursor(gotoCmd);
345
+ }
346
+ }
347
+ async function handlePick() {
348
+ if (!chrome.tabs?.query) return;
349
+ if (isPicking) {
350
+ setIsPicking(false);
351
+ await chrome.runtime.sendMessage({ type: "pick-cancel" }).catch((e) => console.debug("[pw-repl] pick-cancel:", e));
352
+ return;
353
+ }
354
+ setIsPicking(true);
355
+ try {
356
+ const result = await chrome.runtime.sendMessage({ type: "pick" });
357
+ if (!result?.ok || !result.info) {
358
+ if (result?.error !== "cancelled")
359
+ dispatch({ type: "ADD_LINE", line: { text: `Pick failed: ${result?.error ?? "unknown error"}`, type: "error" } });
360
+ return;
361
+ }
362
+ const info = result.info;
363
+ const pickResult = buildPickResult(info, info.locator, info.ariaSnapshot);
364
+ if (info.ariaSnapshot)
365
+ pickResult.ariaSnapshot = info.ariaSnapshot;
366
+ dispatch({ type: "ADD_LINE", line: { text: "", type: "info", pickResult } });
367
+ } catch (e) {
368
+ dispatch({ type: "ADD_LINE", line: { text: `Pick failed: ${String(e)}`, type: "error" } });
369
+ } finally {
370
+ setIsPicking(false);
371
+ }
372
+ }
373
+ function handleDetach() {
374
+ dispatch({ type: "DETACH" });
375
+ chrome.runtime.sendMessage({ type: "detach" }).catch((e) => console.debug("[pw-repl] detach:", e));
376
+ }
377
+ function formatElapsed(seconds) {
378
+ const m = Math.floor(seconds / 60);
379
+ const s = seconds % 60;
380
+ return `${m}:${s.toString().padStart(2, "0")}`;
381
+ }
382
+ reactExports.useEffect(() => {
383
+ chrome.runtime.sendMessage({ type: "video-state" }).then((r) => {
384
+ if (r?.recording && r.startTime) {
385
+ setIsVideoRecording(true);
386
+ videoStartTimeRef.current = r.startTime;
387
+ setVideoElapsed(Math.round((Date.now() - r.startTime) / 1e3));
388
+ }
389
+ }).catch(() => {
390
+ });
391
+ }, []);
392
+ reactExports.useEffect(() => {
393
+ if (!isVideoRecording) {
394
+ setVideoElapsed(0);
395
+ return;
396
+ }
397
+ const id = setInterval(() => {
398
+ setVideoElapsed(Math.round((Date.now() - videoStartTimeRef.current) / 1e3));
399
+ }, 1e3);
400
+ return () => clearInterval(id);
401
+ }, [isVideoRecording]);
402
+ async function handleVideoRecord() {
403
+ if (isVideoRecording) {
404
+ const r2 = await chrome.runtime.sendMessage({ type: "video-stop" });
405
+ setIsVideoRecording(false);
406
+ if (r2?.ok && r2.blobUrl) {
407
+ dispatch({ type: "ADD_LINE", line: { text: "Video recorded", type: "info", video: r2.blobUrl, videoDuration: r2.duration, videoSize: r2.size } });
408
+ } else if (r2?.error !== "Not recording") {
409
+ dispatch({ type: "ADD_LINE", line: { text: `Video stop failed: ${r2?.error ?? "unknown"}`, type: "error" } });
410
+ }
411
+ return;
412
+ }
413
+ const r = await chrome.runtime.sendMessage({ type: "video-start" });
414
+ if (r?.ok) {
415
+ setIsVideoRecording(true);
416
+ videoStartTimeRef.current = Date.now();
417
+ } else if (r?.error === "Already recording") {
418
+ setIsVideoRecording(true);
419
+ videoStartTimeRef.current = Date.now();
420
+ } else {
421
+ dispatch({ type: "ADD_LINE", line: { text: `Video failed: ${r?.error ?? "unknown"}`, type: "error" } });
422
+ }
423
+ }
424
+ reactExports.useEffect(() => {
425
+ document.documentElement.classList.toggle("theme-dark", isDarkMode);
426
+ localStorage.setItem("theme", isDarkMode ? "dark" : "light");
427
+ }, [isDarkMode]);
428
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { id: "toolbar", className: "flex flex-wrap gap-1 justify-between items-center py-1 px-2 bg-(--bg-toolbar) border-b border-solid border-(--border-primary) shrink-0", children: [
429
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { id: "toolbar-left", className: "flex flex-wrap gap-1 items-center", children: [
430
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
431
+ "input",
432
+ {
433
+ type: "file",
434
+ accept: ".pw,.js,.txt",
435
+ ref: fileInputRef,
436
+ style: { display: "none" },
437
+ onChange: handleFileChange
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
441
+ "button",
442
+ {
443
+ "data-testid": "pick-btn",
444
+ className: isPicking ? "picking" : "",
445
+ title: isPicking ? "Stop picking" : "Pick element",
446
+ disabled: isRecording,
447
+ onClick: handlePick,
448
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(CrosshairIcon, {})
449
+ }
450
+ ),
451
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
452
+ "button",
453
+ {
454
+ id: "record-btn",
455
+ "data-testid": "record-btn",
456
+ className: isRecording ? "recording" : "",
457
+ title: isRecording ? "Stop recording" : "Start Recording",
458
+ disabled: isPicking || isRunning,
459
+ onClick: handleRecord,
460
+ children: isRecording ? /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(RecordIcon, {})
461
+ }
462
+ ),
463
+ (isRunning || stepLine !== -1) && !isStepDebugging ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { id: "stop-run-btn", "data-testid": "stop-run-btn", title: "Stop", onClick: handleStop, children: /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { id: "run-btn", "data-testid": "run-btn", title: "Run script (Ctrl+Enter)", disabled: !editorContent.trim() || isStepDebugging, onClick: handleRun, children: "▶" }),
464
+ editorMode === "js" && !isRunning && stepLine === -1 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { id: "debug-btn", "data-testid": "debug-btn", title: "Debug script", disabled: !editorContent.trim(), onClick: handleDebug, children: /* @__PURE__ */ jsxRuntimeExports.jsx(BugIcon, {}) }),
465
+ editorMode === "pw" && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { id: "step-btn", "data-testid": "step-btn", title: "Step: run next line", disabled: !editorContent.trim() || isRunning, onClick: handleStep, children: /* @__PURE__ */ jsxRuntimeExports.jsx(StepForwardIcon, {}) }),
466
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-px h-4.5 bg-(--color-toolbar-sep) mx-1" }),
467
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { "data-testid": "mode-toggle", className: "inline-flex rounded border border-(--border-button) overflow-hidden", children: [
468
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
469
+ "button",
470
+ {
471
+ "data-active": editorMode === "pw" ? "" : void 0,
472
+ onClick: () => {
473
+ loadSettings().then((s) => storeSettings({ ...s, languageMode: "pw" })).then(() => dispatch({ type: "SET_EDITOR_MODE", mode: "pw" }));
474
+ },
475
+ className: "px-1.5 py-0.5 text-[11px] border-0 rounded-none",
476
+ children: ".pw"
477
+ }
478
+ ),
479
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
480
+ "button",
481
+ {
482
+ "data-active": editorMode === "js" ? "" : void 0,
483
+ onClick: () => {
484
+ loadSettings().then((s) => storeSettings({ ...s, languageMode: "js" })).then(() => dispatch({ type: "SET_EDITOR_MODE", mode: "js" }));
485
+ },
486
+ className: "px-1.5 py-0.5 text-[11px] border-0 rounded-none",
487
+ children: "JS"
488
+ }
489
+ )
490
+ ] }),
491
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
492
+ "button",
493
+ {
494
+ id: "video-btn",
495
+ "data-testid": "video-btn",
496
+ className: isVideoRecording ? "video-recording" : "",
497
+ title: isVideoRecording ? `Stop video (${formatElapsed(videoElapsed)})` : "Record video",
498
+ onClick: handleVideoRecord,
499
+ children: [
500
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ScreencastIcon, {}),
501
+ isVideoRecording && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1 text-[10px] font-mono", children: formatElapsed(videoElapsed) })
502
+ ]
503
+ }
504
+ ),
505
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { id: "open-btn", title: "Open .pw file", onClick: handleFileOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsx(FolderOpenIcon, {}) }),
506
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { id: "save-btn", title: "Save as .pw file", disabled: !editorContent.trim(), onClick: handleSave, children: /* @__PURE__ */ jsxRuntimeExports.jsx(SaveIcon, {}) }),
507
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsDarkMode((prev) => !prev), title: isDarkMode ? "Switch to light mode" : "Switch to dark mode", children: isDarkMode ? /* @__PURE__ */ jsxRuntimeExports.jsx(SunIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(MoonIcon, {}) })
508
+ ] }),
509
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { id: "toolbar-right", className: "flex items-center gap-2", children: [
510
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
511
+ "select",
512
+ {
513
+ value: selectedTabId ?? attachedTabId ?? "",
514
+ title: "Switch tab",
515
+ onFocus: loadTabs,
516
+ onChange: (e) => {
517
+ const tabId = Number(e.target.value);
518
+ if (tabId) handleTabChange(tabId);
519
+ },
520
+ children: [
521
+ !selectedTabId && !attachedTabId && /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "— select tab —" }),
522
+ availableTabs.map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: tab.id, children: getTabLabel(tab) }, tab.id))
523
+ ]
524
+ }
525
+ ),
526
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
527
+ "button",
528
+ {
529
+ "data-testid": "attach-btn",
530
+ title: isAttaching ? "Connecting..." : attachedUrl ? `Detach from ${attachedUrl}` : "Attach to tab",
531
+ disabled: !attachedUrl && (isAttaching || !canAttach || availableTabs.some((t) => t.id === selectedTabId && isInternalUrl(t.url))),
532
+ onClick: attachedUrl ? handleDetach : handleAttach,
533
+ style: { color: isAttaching ? "var(--color-warning, #facc15)" : attachedUrl ? "var(--color-success)" : "var(--color-error)" },
534
+ children: attachedUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx(UnplugIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PlugIcon, {})
535
+ }
536
+ )
537
+ ] })
538
+ ] });
539
+ }
540
+ const basicNormalize = typeof String.prototype.normalize == "function" ? (x) => x.normalize("NFKD") : (x) => x;
541
+ class SearchCursor {
542
+ /**
543
+ Create a text cursor. The query is the search string, `from` to
544
+ `to` provides the region to search.
545
+
546
+ When `normalize` is given, it will be called, on both the query
547
+ string and the content it is matched against, before comparing.
548
+ You can, for example, create a case-insensitive search by
549
+ passing `s => s.toLowerCase()`.
550
+
551
+ Text is always normalized with
552
+ [`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
553
+ (when supported).
554
+ */
555
+ constructor(text, query, from = 0, to = text.length, normalize, test) {
556
+ this.test = test;
557
+ this.value = { from: 0, to: 0 };
558
+ this.done = false;
559
+ this.matches = [];
560
+ this.buffer = "";
561
+ this.bufferPos = 0;
562
+ this.iter = text.iterRange(from, to);
563
+ this.bufferStart = from;
564
+ this.normalize = normalize ? (x) => normalize(basicNormalize(x)) : basicNormalize;
565
+ this.query = this.normalize(query);
566
+ }
567
+ peek() {
568
+ if (this.bufferPos == this.buffer.length) {
569
+ this.bufferStart += this.buffer.length;
570
+ this.iter.next();
571
+ if (this.iter.done)
572
+ return -1;
573
+ this.bufferPos = 0;
574
+ this.buffer = this.iter.value;
575
+ }
576
+ return codePointAt(this.buffer, this.bufferPos);
577
+ }
578
+ /**
579
+ Look for the next match. Updates the iterator's
580
+ [`value`](https://codemirror.net/6/docs/ref/#search.SearchCursor.value) and
581
+ [`done`](https://codemirror.net/6/docs/ref/#search.SearchCursor.done) properties. Should be called
582
+ at least once before using the cursor.
583
+ */
584
+ next() {
585
+ while (this.matches.length)
586
+ this.matches.pop();
587
+ return this.nextOverlapping();
588
+ }
589
+ /**
590
+ The `next` method will ignore matches that partially overlap a
591
+ previous match. This method behaves like `next`, but includes
592
+ such matches.
593
+ */
594
+ nextOverlapping() {
595
+ for (; ; ) {
596
+ let next = this.peek();
597
+ if (next < 0) {
598
+ this.done = true;
599
+ return this;
600
+ }
601
+ let str = fromCodePoint(next), start = this.bufferStart + this.bufferPos;
602
+ this.bufferPos += codePointSize(next);
603
+ let norm = this.normalize(str);
604
+ if (norm.length)
605
+ for (let i = 0, pos = start; ; i++) {
606
+ let code = norm.charCodeAt(i);
607
+ let match = this.match(code, pos, this.bufferPos + this.bufferStart);
608
+ if (i == norm.length - 1) {
609
+ if (match) {
610
+ this.value = match;
611
+ return this;
612
+ }
613
+ break;
614
+ }
615
+ if (pos == start && i < str.length && str.charCodeAt(i) == code)
616
+ pos++;
617
+ }
618
+ }
619
+ }
620
+ match(code, pos, end) {
621
+ let match = null;
622
+ for (let i = 0; i < this.matches.length; i += 2) {
623
+ let index = this.matches[i], keep = false;
624
+ if (this.query.charCodeAt(index) == code) {
625
+ if (index == this.query.length - 1) {
626
+ match = { from: this.matches[i + 1], to: end };
627
+ } else {
628
+ this.matches[i]++;
629
+ keep = true;
630
+ }
631
+ }
632
+ if (!keep) {
633
+ this.matches.splice(i, 2);
634
+ i -= 2;
635
+ }
636
+ }
637
+ if (this.query.charCodeAt(0) == code) {
638
+ if (this.query.length == 1)
639
+ match = { from: pos, to: end };
640
+ else
641
+ this.matches.push(1, pos);
642
+ }
643
+ if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferStart))
644
+ match = null;
645
+ return match;
646
+ }
647
+ }
648
+ if (typeof Symbol != "undefined")
649
+ SearchCursor.prototype[Symbol.iterator] = function() {
650
+ return this;
651
+ };
652
+ const empty = { from: -1, to: -1, match: /* @__PURE__ */ /.*/.exec("") };
653
+ const baseFlags = "gm" + (/x/.unicode == null ? "" : "u");
654
+ class RegExpCursor {
655
+ /**
656
+ Create a cursor that will search the given range in the given
657
+ document. `query` should be the raw pattern (as you'd pass it to
658
+ `new RegExp`).
659
+ */
660
+ constructor(text, query, options, from = 0, to = text.length) {
661
+ this.text = text;
662
+ this.to = to;
663
+ this.curLine = "";
664
+ this.done = false;
665
+ this.value = empty;
666
+ if (/\\[sWDnr]|\n|\r|\[\^/.test(query))
667
+ return new MultilineRegExpCursor(text, query, options, from, to);
668
+ this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
669
+ this.test = options === null || options === void 0 ? void 0 : options.test;
670
+ this.iter = text.iter();
671
+ let startLine = text.lineAt(from);
672
+ this.curLineStart = startLine.from;
673
+ this.matchPos = toCharEnd(text, from);
674
+ this.getLine(this.curLineStart);
675
+ }
676
+ getLine(skip) {
677
+ this.iter.next(skip);
678
+ if (this.iter.lineBreak) {
679
+ this.curLine = "";
680
+ } else {
681
+ this.curLine = this.iter.value;
682
+ if (this.curLineStart + this.curLine.length > this.to)
683
+ this.curLine = this.curLine.slice(0, this.to - this.curLineStart);
684
+ this.iter.next();
685
+ }
686
+ }
687
+ nextLine() {
688
+ this.curLineStart = this.curLineStart + this.curLine.length + 1;
689
+ if (this.curLineStart > this.to)
690
+ this.curLine = "";
691
+ else
692
+ this.getLine(0);
693
+ }
694
+ /**
695
+ Move to the next match, if there is one.
696
+ */
697
+ next() {
698
+ for (let off = this.matchPos - this.curLineStart; ; ) {
699
+ this.re.lastIndex = off;
700
+ let match = this.matchPos <= this.to && this.re.exec(this.curLine);
701
+ if (match) {
702
+ let from = this.curLineStart + match.index, to = from + match[0].length;
703
+ this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
704
+ if (from == this.curLineStart + this.curLine.length)
705
+ this.nextLine();
706
+ if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {
707
+ this.value = { from, to, match };
708
+ return this;
709
+ }
710
+ off = this.matchPos - this.curLineStart;
711
+ } else if (this.curLineStart + this.curLine.length < this.to) {
712
+ this.nextLine();
713
+ off = 0;
714
+ } else {
715
+ this.done = true;
716
+ return this;
717
+ }
718
+ }
719
+ }
720
+ }
721
+ const flattened = /* @__PURE__ */ new WeakMap();
722
+ class FlattenedDoc {
723
+ constructor(from, text) {
724
+ this.from = from;
725
+ this.text = text;
726
+ }
727
+ get to() {
728
+ return this.from + this.text.length;
729
+ }
730
+ static get(doc, from, to) {
731
+ let cached = flattened.get(doc);
732
+ if (!cached || cached.from >= to || cached.to <= from) {
733
+ let flat = new FlattenedDoc(from, doc.sliceString(from, to));
734
+ flattened.set(doc, flat);
735
+ return flat;
736
+ }
737
+ if (cached.from == from && cached.to == to)
738
+ return cached;
739
+ let { text, from: cachedFrom } = cached;
740
+ if (cachedFrom > from) {
741
+ text = doc.sliceString(from, cachedFrom) + text;
742
+ cachedFrom = from;
743
+ }
744
+ if (cached.to < to)
745
+ text += doc.sliceString(cached.to, to);
746
+ flattened.set(doc, new FlattenedDoc(cachedFrom, text));
747
+ return new FlattenedDoc(from, text.slice(from - cachedFrom, to - cachedFrom));
748
+ }
749
+ }
750
+ class MultilineRegExpCursor {
751
+ constructor(text, query, options, from, to) {
752
+ this.text = text;
753
+ this.to = to;
754
+ this.done = false;
755
+ this.value = empty;
756
+ this.matchPos = toCharEnd(text, from);
757
+ this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
758
+ this.test = options === null || options === void 0 ? void 0 : options.test;
759
+ this.flat = FlattenedDoc.get(text, from, this.chunkEnd(
760
+ from + 5e3
761
+ /* Chunk.Base */
762
+ ));
763
+ }
764
+ chunkEnd(pos) {
765
+ return pos >= this.to ? this.to : this.text.lineAt(pos).to;
766
+ }
767
+ next() {
768
+ for (; ; ) {
769
+ let off = this.re.lastIndex = this.matchPos - this.flat.from;
770
+ let match = this.re.exec(this.flat.text);
771
+ if (match && !match[0] && match.index == off) {
772
+ this.re.lastIndex = off + 1;
773
+ match = this.re.exec(this.flat.text);
774
+ }
775
+ if (match) {
776
+ let from = this.flat.from + match.index, to = from + match[0].length;
777
+ if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) && (!this.test || this.test(from, to, match))) {
778
+ this.value = { from, to, match };
779
+ this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
780
+ return this;
781
+ }
782
+ }
783
+ if (this.flat.to == this.to) {
784
+ this.done = true;
785
+ return this;
786
+ }
787
+ this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
788
+ }
789
+ }
790
+ }
791
+ if (typeof Symbol != "undefined") {
792
+ RegExpCursor.prototype[Symbol.iterator] = MultilineRegExpCursor.prototype[Symbol.iterator] = function() {
793
+ return this;
794
+ };
795
+ }
796
+ function validRegExp(source) {
797
+ try {
798
+ new RegExp(source, baseFlags);
799
+ return true;
800
+ } catch (_a) {
801
+ return false;
802
+ }
803
+ }
804
+ function toCharEnd(text, pos) {
805
+ if (pos >= text.length)
806
+ return pos;
807
+ let line = text.lineAt(pos), next;
808
+ while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 56320 && next < 57344)
809
+ pos++;
810
+ return pos;
811
+ }
812
+ const gotoLine = (view) => {
813
+ let { state } = view;
814
+ let line = String(state.doc.lineAt(view.state.selection.main.head).number);
815
+ let { close, result } = showDialog(view, {
816
+ label: state.phrase("Go to line"),
817
+ input: { type: "text", name: "line", value: line },
818
+ focus: true,
819
+ submitLabel: state.phrase("go")
820
+ });
821
+ result.then((form) => {
822
+ let match = form && /^([+-])?(\d+)?(:\d+)?(%)?$/.exec(form.elements["line"].value);
823
+ if (!match) {
824
+ view.dispatch({ effects: close });
825
+ return;
826
+ }
827
+ let startLine = state.doc.lineAt(state.selection.main.head);
828
+ let [, sign, ln, cl, percent] = match;
829
+ let col = cl ? +cl.slice(1) : 0;
830
+ let line2 = ln ? +ln : startLine.number;
831
+ if (ln && percent) {
832
+ let pc = line2 / 100;
833
+ if (sign)
834
+ pc = pc * (sign == "-" ? -1 : 1) + startLine.number / state.doc.lines;
835
+ line2 = Math.round(state.doc.lines * pc);
836
+ } else if (ln && sign) {
837
+ line2 = line2 * (sign == "-" ? -1 : 1) + startLine.number;
838
+ }
839
+ let docLine = state.doc.line(Math.max(1, Math.min(state.doc.lines, line2)));
840
+ let selection = EditorSelection.cursor(docLine.from + Math.max(0, Math.min(col, docLine.length)));
841
+ view.dispatch({
842
+ effects: [close, EditorView.scrollIntoView(selection.from, { y: "center" })],
843
+ selection
844
+ });
845
+ });
846
+ return true;
847
+ };
848
+ const selectWord = ({ state, dispatch }) => {
849
+ let { selection } = state;
850
+ let newSel = EditorSelection.create(selection.ranges.map((range) => state.wordAt(range.head) || EditorSelection.cursor(range.head)), selection.mainIndex);
851
+ if (newSel.eq(selection))
852
+ return false;
853
+ dispatch(state.update({ selection: newSel }));
854
+ return true;
855
+ };
856
+ function findNextOccurrence(state, query) {
857
+ let { main, ranges } = state.selection;
858
+ let word = state.wordAt(main.head), fullWord = word && word.from == main.from && word.to == main.to;
859
+ for (let cycled = false, cursor = new SearchCursor(state.doc, query, ranges[ranges.length - 1].to); ; ) {
860
+ cursor.next();
861
+ if (cursor.done) {
862
+ if (cycled)
863
+ return null;
864
+ cursor = new SearchCursor(state.doc, query, 0, Math.max(0, ranges[ranges.length - 1].from - 1));
865
+ cycled = true;
866
+ } else {
867
+ if (cycled && ranges.some((r) => r.from == cursor.value.from))
868
+ continue;
869
+ if (fullWord) {
870
+ let word2 = state.wordAt(cursor.value.from);
871
+ if (!word2 || word2.from != cursor.value.from || word2.to != cursor.value.to)
872
+ continue;
873
+ }
874
+ return cursor.value;
875
+ }
876
+ }
877
+ }
878
+ const selectNextOccurrence = ({ state, dispatch }) => {
879
+ let { ranges } = state.selection;
880
+ if (ranges.some((sel) => sel.from === sel.to))
881
+ return selectWord({ state, dispatch });
882
+ let searchedText = state.sliceDoc(ranges[0].from, ranges[0].to);
883
+ if (state.selection.ranges.some((r) => state.sliceDoc(r.from, r.to) != searchedText))
884
+ return false;
885
+ let range = findNextOccurrence(state, searchedText);
886
+ if (!range)
887
+ return false;
888
+ dispatch(state.update({
889
+ selection: state.selection.addRange(EditorSelection.range(range.from, range.to), false),
890
+ effects: EditorView.scrollIntoView(range.to)
891
+ }));
892
+ return true;
893
+ };
894
+ const searchConfigFacet = /* @__PURE__ */ Facet.define({
895
+ combine(configs) {
896
+ return combineConfig(configs, {
897
+ top: false,
898
+ caseSensitive: false,
899
+ literal: false,
900
+ regexp: false,
901
+ wholeWord: false,
902
+ createPanel: (view) => new SearchPanel(view),
903
+ scrollToMatch: (range) => EditorView.scrollIntoView(range)
904
+ });
905
+ }
906
+ });
907
+ function search(config) {
908
+ return searchExtensions;
909
+ }
910
+ class SearchQuery {
911
+ /**
912
+ Create a query object.
913
+ */
914
+ constructor(config) {
915
+ this.search = config.search;
916
+ this.caseSensitive = !!config.caseSensitive;
917
+ this.literal = !!config.literal;
918
+ this.regexp = !!config.regexp;
919
+ this.replace = config.replace || "";
920
+ this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
921
+ this.unquoted = this.unquote(this.search);
922
+ this.wholeWord = !!config.wholeWord;
923
+ this.test = config.test;
924
+ }
925
+ /**
926
+ @internal
927
+ */
928
+ unquote(text) {
929
+ return this.literal ? text : text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? " " : "\\");
930
+ }
931
+ /**
932
+ Compare this query to another query.
933
+ */
934
+ eq(other) {
935
+ return this.search == other.search && this.replace == other.replace && this.caseSensitive == other.caseSensitive && this.regexp == other.regexp && this.wholeWord == other.wholeWord && this.test == other.test;
936
+ }
937
+ /**
938
+ @internal
939
+ */
940
+ create() {
941
+ return this.regexp ? new RegExpQuery(this) : new StringQuery(this);
942
+ }
943
+ /**
944
+ Get a search cursor for this query, searching through the given
945
+ range in the given state.
946
+ */
947
+ getCursor(state, from = 0, to) {
948
+ let st = state.doc ? state : EditorState.create({ doc: state });
949
+ if (to == null)
950
+ to = st.doc.length;
951
+ return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);
952
+ }
953
+ }
954
+ class QueryType {
955
+ constructor(spec) {
956
+ this.spec = spec;
957
+ }
958
+ }
959
+ function wrapStringTest(test, state, inner) {
960
+ return (from, to, buffer, bufferPos) => {
961
+ if (inner && !inner(from, to, buffer, bufferPos))
962
+ return false;
963
+ let match = from >= bufferPos && to <= bufferPos + buffer.length ? buffer.slice(from - bufferPos, to - bufferPos) : state.doc.sliceString(from, to);
964
+ return test(match, state, from, to);
965
+ };
966
+ }
967
+ function stringCursor(spec, state, from, to) {
968
+ let test;
969
+ if (spec.wholeWord)
970
+ test = stringWordTest(state.doc, state.charCategorizer(state.selection.main.head));
971
+ if (spec.test)
972
+ test = wrapStringTest(spec.test, state, test);
973
+ return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? void 0 : (x) => x.toLowerCase(), test);
974
+ }
975
+ function stringWordTest(doc, categorizer) {
976
+ return (from, to, buf, bufPos) => {
977
+ if (bufPos > from || bufPos + buf.length < to) {
978
+ bufPos = Math.max(0, from - 2);
979
+ buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
980
+ }
981
+ return (categorizer(charBefore(buf, from - bufPos)) != CharCategory.Word || categorizer(charAfter(buf, from - bufPos)) != CharCategory.Word) && (categorizer(charAfter(buf, to - bufPos)) != CharCategory.Word || categorizer(charBefore(buf, to - bufPos)) != CharCategory.Word);
982
+ };
983
+ }
984
+ class StringQuery extends QueryType {
985
+ constructor(spec) {
986
+ super(spec);
987
+ }
988
+ nextMatch(state, curFrom, curTo) {
989
+ let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
990
+ if (cursor.done) {
991
+ let end = Math.min(state.doc.length, curFrom + this.spec.unquoted.length);
992
+ cursor = stringCursor(this.spec, state, 0, end).nextOverlapping();
993
+ }
994
+ return cursor.done || cursor.value.from == curFrom && cursor.value.to == curTo ? null : cursor.value;
995
+ }
996
+ // Searching in reverse is, rather than implementing an inverted search
997
+ // cursor, done by scanning chunk after chunk forward.
998
+ prevMatchInRange(state, from, to) {
999
+ for (let pos = to; ; ) {
1000
+ let start = Math.max(from, pos - 1e4 - this.spec.unquoted.length);
1001
+ let cursor = stringCursor(this.spec, state, start, pos), range = null;
1002
+ while (!cursor.nextOverlapping().done)
1003
+ range = cursor.value;
1004
+ if (range)
1005
+ return range;
1006
+ if (start == from)
1007
+ return null;
1008
+ pos -= 1e4;
1009
+ }
1010
+ }
1011
+ prevMatch(state, curFrom, curTo) {
1012
+ let found = this.prevMatchInRange(state, 0, curFrom);
1013
+ if (!found)
1014
+ found = this.prevMatchInRange(state, Math.max(0, curTo - this.spec.unquoted.length), state.doc.length);
1015
+ return found && (found.from != curFrom || found.to != curTo) ? found : null;
1016
+ }
1017
+ getReplacement(_result) {
1018
+ return this.spec.unquote(this.spec.replace);
1019
+ }
1020
+ matchAll(state, limit) {
1021
+ let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];
1022
+ while (!cursor.next().done) {
1023
+ if (ranges.length >= limit)
1024
+ return null;
1025
+ ranges.push(cursor.value);
1026
+ }
1027
+ return ranges;
1028
+ }
1029
+ highlight(state, from, to, add) {
1030
+ let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));
1031
+ while (!cursor.next().done)
1032
+ add(cursor.value.from, cursor.value.to);
1033
+ }
1034
+ }
1035
+ function wrapRegexpTest(test, state, inner) {
1036
+ return (from, to, match) => {
1037
+ return (!inner || inner(from, to, match)) && test(match[0], state, from, to);
1038
+ };
1039
+ }
1040
+ function regexpCursor(spec, state, from, to) {
1041
+ let test;
1042
+ if (spec.wholeWord)
1043
+ test = regexpWordTest(state.charCategorizer(state.selection.main.head));
1044
+ if (spec.test)
1045
+ test = wrapRegexpTest(spec.test, state, test);
1046
+ return new RegExpCursor(state.doc, spec.search, { ignoreCase: !spec.caseSensitive, test }, from, to);
1047
+ }
1048
+ function charBefore(str, index) {
1049
+ return str.slice(findClusterBreak(str, index, false), index);
1050
+ }
1051
+ function charAfter(str, index) {
1052
+ return str.slice(index, findClusterBreak(str, index));
1053
+ }
1054
+ function regexpWordTest(categorizer) {
1055
+ return (_from, _to, match) => !match[0].length || (categorizer(charBefore(match.input, match.index)) != CharCategory.Word || categorizer(charAfter(match.input, match.index)) != CharCategory.Word) && (categorizer(charAfter(match.input, match.index + match[0].length)) != CharCategory.Word || categorizer(charBefore(match.input, match.index + match[0].length)) != CharCategory.Word);
1056
+ }
1057
+ class RegExpQuery extends QueryType {
1058
+ nextMatch(state, curFrom, curTo) {
1059
+ let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();
1060
+ if (cursor.done)
1061
+ cursor = regexpCursor(this.spec, state, 0, curFrom).next();
1062
+ return cursor.done ? null : cursor.value;
1063
+ }
1064
+ prevMatchInRange(state, from, to) {
1065
+ for (let size = 1; ; size++) {
1066
+ let start = Math.max(
1067
+ from,
1068
+ to - size * 1e4
1069
+ /* FindPrev.ChunkSize */
1070
+ );
1071
+ let cursor = regexpCursor(this.spec, state, start, to), range = null;
1072
+ while (!cursor.next().done)
1073
+ range = cursor.value;
1074
+ if (range && (start == from || range.from > start + 10))
1075
+ return range;
1076
+ if (start == from)
1077
+ return null;
1078
+ }
1079
+ }
1080
+ prevMatch(state, curFrom, curTo) {
1081
+ return this.prevMatchInRange(state, 0, curFrom) || this.prevMatchInRange(state, curTo, state.doc.length);
1082
+ }
1083
+ getReplacement(result) {
1084
+ return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
1085
+ if (i == "&")
1086
+ return result.match[0];
1087
+ if (i == "$")
1088
+ return "$";
1089
+ for (let l = i.length; l > 0; l--) {
1090
+ let n = +i.slice(0, l);
1091
+ if (n > 0 && n < result.match.length)
1092
+ return result.match[n] + i.slice(l);
1093
+ }
1094
+ return m;
1095
+ });
1096
+ }
1097
+ matchAll(state, limit) {
1098
+ let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
1099
+ while (!cursor.next().done) {
1100
+ if (ranges.length >= limit)
1101
+ return null;
1102
+ ranges.push(cursor.value);
1103
+ }
1104
+ return ranges;
1105
+ }
1106
+ highlight(state, from, to, add) {
1107
+ let cursor = regexpCursor(this.spec, state, Math.max(
1108
+ 0,
1109
+ from - 250
1110
+ /* RegExp.HighlightMargin */
1111
+ ), Math.min(to + 250, state.doc.length));
1112
+ while (!cursor.next().done)
1113
+ add(cursor.value.from, cursor.value.to);
1114
+ }
1115
+ }
1116
+ const setSearchQuery = /* @__PURE__ */ StateEffect.define();
1117
+ const togglePanel = /* @__PURE__ */ StateEffect.define();
1118
+ const searchState = /* @__PURE__ */ StateField.define({
1119
+ create(state) {
1120
+ return new SearchState(defaultQuery(state).create(), null);
1121
+ },
1122
+ update(value, tr) {
1123
+ for (let effect of tr.effects) {
1124
+ if (effect.is(setSearchQuery))
1125
+ value = new SearchState(effect.value.create(), value.panel);
1126
+ else if (effect.is(togglePanel))
1127
+ value = new SearchState(value.query, effect.value ? createSearchPanel : null);
1128
+ }
1129
+ return value;
1130
+ },
1131
+ provide: (f) => showPanel.from(f, (val) => val.panel)
1132
+ });
1133
+ class SearchState {
1134
+ constructor(query, panel) {
1135
+ this.query = query;
1136
+ this.panel = panel;
1137
+ }
1138
+ }
1139
+ const matchMark = /* @__PURE__ */ Decoration.mark({ class: "cm-searchMatch" }), selectedMatchMark = /* @__PURE__ */ Decoration.mark({ class: "cm-searchMatch cm-searchMatch-selected" });
1140
+ const searchHighlighter = /* @__PURE__ */ ViewPlugin.fromClass(class {
1141
+ constructor(view) {
1142
+ this.view = view;
1143
+ this.decorations = this.highlight(view.state.field(searchState));
1144
+ }
1145
+ update(update) {
1146
+ let state = update.state.field(searchState);
1147
+ if (state != update.startState.field(searchState) || update.docChanged || update.selectionSet || update.viewportChanged)
1148
+ this.decorations = this.highlight(state);
1149
+ }
1150
+ highlight({ query, panel }) {
1151
+ if (!panel || !query.spec.valid)
1152
+ return Decoration.none;
1153
+ let { view } = this;
1154
+ let builder = new RangeSetBuilder();
1155
+ for (let i = 0, ranges = view.visibleRanges, l = ranges.length; i < l; i++) {
1156
+ let { from, to } = ranges[i];
1157
+ while (i < l - 1 && to > ranges[i + 1].from - 2 * 250)
1158
+ to = ranges[++i].to;
1159
+ query.highlight(view.state, from, to, (from2, to2) => {
1160
+ let selected = view.state.selection.ranges.some((r) => r.from == from2 && r.to == to2);
1161
+ builder.add(from2, to2, selected ? selectedMatchMark : matchMark);
1162
+ });
1163
+ }
1164
+ return builder.finish();
1165
+ }
1166
+ }, {
1167
+ decorations: (v) => v.decorations
1168
+ });
1169
+ function searchCommand(f) {
1170
+ return (view) => {
1171
+ let state = view.state.field(searchState, false);
1172
+ return state && state.query.spec.valid ? f(view, state) : openSearchPanel(view);
1173
+ };
1174
+ }
1175
+ const findNext = /* @__PURE__ */ searchCommand((view, { query }) => {
1176
+ let { to } = view.state.selection.main;
1177
+ let next = query.nextMatch(view.state, to, to);
1178
+ if (!next)
1179
+ return false;
1180
+ let selection = EditorSelection.single(next.from, next.to);
1181
+ let config = view.state.facet(searchConfigFacet);
1182
+ view.dispatch({
1183
+ selection,
1184
+ effects: [announceMatch(view, next), config.scrollToMatch(selection.main, view)],
1185
+ userEvent: "select.search"
1186
+ });
1187
+ selectSearchInput(view);
1188
+ return true;
1189
+ });
1190
+ const findPrevious = /* @__PURE__ */ searchCommand((view, { query }) => {
1191
+ let { state } = view, { from } = state.selection.main;
1192
+ let prev = query.prevMatch(state, from, from);
1193
+ if (!prev)
1194
+ return false;
1195
+ let selection = EditorSelection.single(prev.from, prev.to);
1196
+ let config = view.state.facet(searchConfigFacet);
1197
+ view.dispatch({
1198
+ selection,
1199
+ effects: [announceMatch(view, prev), config.scrollToMatch(selection.main, view)],
1200
+ userEvent: "select.search"
1201
+ });
1202
+ selectSearchInput(view);
1203
+ return true;
1204
+ });
1205
+ const selectMatches = /* @__PURE__ */ searchCommand((view, { query }) => {
1206
+ let ranges = query.matchAll(view.state, 1e3);
1207
+ if (!ranges || !ranges.length)
1208
+ return false;
1209
+ view.dispatch({
1210
+ selection: EditorSelection.create(ranges.map((r) => EditorSelection.range(r.from, r.to))),
1211
+ userEvent: "select.search.matches"
1212
+ });
1213
+ return true;
1214
+ });
1215
+ const selectSelectionMatches = ({ state, dispatch }) => {
1216
+ let sel = state.selection;
1217
+ if (sel.ranges.length > 1 || sel.main.empty)
1218
+ return false;
1219
+ let { from, to } = sel.main;
1220
+ let ranges = [], main = 0;
1221
+ for (let cur = new SearchCursor(state.doc, state.sliceDoc(from, to)); !cur.next().done; ) {
1222
+ if (ranges.length > 1e3)
1223
+ return false;
1224
+ if (cur.value.from == from)
1225
+ main = ranges.length;
1226
+ ranges.push(EditorSelection.range(cur.value.from, cur.value.to));
1227
+ }
1228
+ dispatch(state.update({
1229
+ selection: EditorSelection.create(ranges, main),
1230
+ userEvent: "select.search.matches"
1231
+ }));
1232
+ return true;
1233
+ };
1234
+ const replaceNext = /* @__PURE__ */ searchCommand((view, { query }) => {
1235
+ let { state } = view, { from, to } = state.selection.main;
1236
+ if (state.readOnly)
1237
+ return false;
1238
+ let match = query.nextMatch(state, from, from);
1239
+ if (!match)
1240
+ return false;
1241
+ let next = match;
1242
+ let changes = [], selection, replacement;
1243
+ let effects = [];
1244
+ if (next.from == from && next.to == to) {
1245
+ replacement = state.toText(query.getReplacement(next));
1246
+ changes.push({ from: next.from, to: next.to, insert: replacement });
1247
+ next = query.nextMatch(state, next.from, next.to);
1248
+ effects.push(EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));
1249
+ }
1250
+ let changeSet = view.state.changes(changes);
1251
+ if (next) {
1252
+ selection = EditorSelection.single(next.from, next.to).map(changeSet);
1253
+ effects.push(announceMatch(view, next));
1254
+ effects.push(state.facet(searchConfigFacet).scrollToMatch(selection.main, view));
1255
+ }
1256
+ view.dispatch({
1257
+ changes: changeSet,
1258
+ selection,
1259
+ effects,
1260
+ userEvent: "input.replace"
1261
+ });
1262
+ return true;
1263
+ });
1264
+ const replaceAll = /* @__PURE__ */ searchCommand((view, { query }) => {
1265
+ if (view.state.readOnly)
1266
+ return false;
1267
+ let changes = query.matchAll(view.state, 1e9).map((match) => {
1268
+ let { from, to } = match;
1269
+ return { from, to, insert: query.getReplacement(match) };
1270
+ });
1271
+ if (!changes.length)
1272
+ return false;
1273
+ let announceText = view.state.phrase("replaced $ matches", changes.length) + ".";
1274
+ view.dispatch({
1275
+ changes,
1276
+ effects: EditorView.announce.of(announceText),
1277
+ userEvent: "input.replace.all"
1278
+ });
1279
+ return true;
1280
+ });
1281
+ function createSearchPanel(view) {
1282
+ return view.state.facet(searchConfigFacet).createPanel(view);
1283
+ }
1284
+ function defaultQuery(state, fallback) {
1285
+ var _a, _b, _c, _d, _e;
1286
+ let sel = state.selection.main;
1287
+ let selText = sel.empty || sel.to > sel.from + 100 ? "" : state.sliceDoc(sel.from, sel.to);
1288
+ if (fallback && !selText)
1289
+ return fallback;
1290
+ let config = state.facet(searchConfigFacet);
1291
+ return new SearchQuery({
1292
+ search: ((_a = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _a !== void 0 ? _a : config.literal) ? selText : selText.replace(/\n/g, "\\n"),
1293
+ caseSensitive: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _b !== void 0 ? _b : config.caseSensitive,
1294
+ literal: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _c !== void 0 ? _c : config.literal,
1295
+ regexp: (_d = fallback === null || fallback === void 0 ? void 0 : fallback.regexp) !== null && _d !== void 0 ? _d : config.regexp,
1296
+ wholeWord: (_e = fallback === null || fallback === void 0 ? void 0 : fallback.wholeWord) !== null && _e !== void 0 ? _e : config.wholeWord
1297
+ });
1298
+ }
1299
+ function getSearchInput(view) {
1300
+ let panel = getPanel(view, createSearchPanel);
1301
+ return panel && panel.dom.querySelector("[main-field]");
1302
+ }
1303
+ function selectSearchInput(view) {
1304
+ let input = getSearchInput(view);
1305
+ if (input && input == view.root.activeElement)
1306
+ input.select();
1307
+ }
1308
+ const openSearchPanel = (view) => {
1309
+ let state = view.state.field(searchState, false);
1310
+ if (state && state.panel) {
1311
+ let searchInput = getSearchInput(view);
1312
+ if (searchInput && searchInput != view.root.activeElement) {
1313
+ let query = defaultQuery(view.state, state.query.spec);
1314
+ if (query.valid)
1315
+ view.dispatch({ effects: setSearchQuery.of(query) });
1316
+ searchInput.focus();
1317
+ searchInput.select();
1318
+ }
1319
+ } else {
1320
+ view.dispatch({ effects: [
1321
+ togglePanel.of(true),
1322
+ state ? setSearchQuery.of(defaultQuery(view.state, state.query.spec)) : StateEffect.appendConfig.of(searchExtensions)
1323
+ ] });
1324
+ }
1325
+ return true;
1326
+ };
1327
+ const closeSearchPanel = (view) => {
1328
+ let state = view.state.field(searchState, false);
1329
+ if (!state || !state.panel)
1330
+ return false;
1331
+ let panel = getPanel(view, createSearchPanel);
1332
+ if (panel && panel.dom.contains(view.root.activeElement))
1333
+ view.focus();
1334
+ view.dispatch({ effects: togglePanel.of(false) });
1335
+ return true;
1336
+ };
1337
+ const searchKeymap = [
1338
+ { key: "Mod-f", run: openSearchPanel, scope: "editor search-panel" },
1339
+ { key: "F3", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },
1340
+ { key: "Mod-g", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },
1341
+ { key: "Escape", run: closeSearchPanel, scope: "editor search-panel" },
1342
+ { key: "Mod-Shift-l", run: selectSelectionMatches },
1343
+ { key: "Mod-Alt-g", run: gotoLine },
1344
+ { key: "Mod-d", run: selectNextOccurrence, preventDefault: true }
1345
+ ];
1346
+ class SearchPanel {
1347
+ constructor(view) {
1348
+ this.view = view;
1349
+ let query = this.query = view.state.field(searchState).query.spec;
1350
+ this.commit = this.commit.bind(this);
1351
+ this.searchField = crelt("input", {
1352
+ value: query.search,
1353
+ placeholder: phrase(view, "Find"),
1354
+ "aria-label": phrase(view, "Find"),
1355
+ class: "cm-textfield",
1356
+ name: "search",
1357
+ form: "",
1358
+ "main-field": "true",
1359
+ onchange: this.commit,
1360
+ onkeyup: this.commit
1361
+ });
1362
+ this.replaceField = crelt("input", {
1363
+ value: query.replace,
1364
+ placeholder: phrase(view, "Replace"),
1365
+ "aria-label": phrase(view, "Replace"),
1366
+ class: "cm-textfield",
1367
+ name: "replace",
1368
+ form: "",
1369
+ onchange: this.commit,
1370
+ onkeyup: this.commit
1371
+ });
1372
+ this.caseField = crelt("input", {
1373
+ type: "checkbox",
1374
+ name: "case",
1375
+ form: "",
1376
+ checked: query.caseSensitive,
1377
+ onchange: this.commit
1378
+ });
1379
+ this.reField = crelt("input", {
1380
+ type: "checkbox",
1381
+ name: "re",
1382
+ form: "",
1383
+ checked: query.regexp,
1384
+ onchange: this.commit
1385
+ });
1386
+ this.wordField = crelt("input", {
1387
+ type: "checkbox",
1388
+ name: "word",
1389
+ form: "",
1390
+ checked: query.wholeWord,
1391
+ onchange: this.commit
1392
+ });
1393
+ function button(name, onclick, content) {
1394
+ return crelt("button", { class: "cm-button", name, onclick, type: "button" }, content);
1395
+ }
1396
+ this.dom = crelt("div", { onkeydown: (e) => this.keydown(e), class: "cm-search" }, [
1397
+ this.searchField,
1398
+ button("next", () => findNext(view), [phrase(view, "next")]),
1399
+ button("prev", () => findPrevious(view), [phrase(view, "previous")]),
1400
+ button("select", () => selectMatches(view), [phrase(view, "all")]),
1401
+ crelt("label", null, [this.caseField, phrase(view, "match case")]),
1402
+ crelt("label", null, [this.reField, phrase(view, "regexp")]),
1403
+ crelt("label", null, [this.wordField, phrase(view, "by word")]),
1404
+ ...view.state.readOnly ? [] : [
1405
+ crelt("br"),
1406
+ this.replaceField,
1407
+ button("replace", () => replaceNext(view), [phrase(view, "replace")]),
1408
+ button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])
1409
+ ],
1410
+ crelt("button", {
1411
+ name: "close",
1412
+ onclick: () => closeSearchPanel(view),
1413
+ "aria-label": phrase(view, "close"),
1414
+ type: "button"
1415
+ }, ["×"])
1416
+ ]);
1417
+ }
1418
+ commit() {
1419
+ let query = new SearchQuery({
1420
+ search: this.searchField.value,
1421
+ caseSensitive: this.caseField.checked,
1422
+ regexp: this.reField.checked,
1423
+ wholeWord: this.wordField.checked,
1424
+ replace: this.replaceField.value
1425
+ });
1426
+ if (!query.eq(this.query)) {
1427
+ this.query = query;
1428
+ this.view.dispatch({ effects: setSearchQuery.of(query) });
1429
+ }
1430
+ }
1431
+ keydown(e) {
1432
+ if (runScopeHandlers(this.view, e, "search-panel")) {
1433
+ e.preventDefault();
1434
+ } else if (e.keyCode == 13 && e.target == this.searchField) {
1435
+ e.preventDefault();
1436
+ (e.shiftKey ? findPrevious : findNext)(this.view);
1437
+ } else if (e.keyCode == 13 && e.target == this.replaceField) {
1438
+ e.preventDefault();
1439
+ replaceNext(this.view);
1440
+ }
1441
+ }
1442
+ update(update) {
1443
+ for (let tr of update.transactions)
1444
+ for (let effect of tr.effects) {
1445
+ if (effect.is(setSearchQuery) && !effect.value.eq(this.query))
1446
+ this.setQuery(effect.value);
1447
+ }
1448
+ }
1449
+ setQuery(query) {
1450
+ this.query = query;
1451
+ this.searchField.value = query.search;
1452
+ this.replaceField.value = query.replace;
1453
+ this.caseField.checked = query.caseSensitive;
1454
+ this.reField.checked = query.regexp;
1455
+ this.wordField.checked = query.wholeWord;
1456
+ }
1457
+ mount() {
1458
+ this.searchField.select();
1459
+ }
1460
+ get pos() {
1461
+ return 80;
1462
+ }
1463
+ get top() {
1464
+ return this.view.state.facet(searchConfigFacet).top;
1465
+ }
1466
+ }
1467
+ function phrase(view, phrase2) {
1468
+ return view.state.phrase(phrase2);
1469
+ }
1470
+ const AnnounceMargin = 30;
1471
+ const Break = /[\s\.,:;?!]/;
1472
+ function announceMatch(view, { from, to }) {
1473
+ let line = view.state.doc.lineAt(from), lineEnd = view.state.doc.lineAt(to).to;
1474
+ let start = Math.max(line.from, from - AnnounceMargin), end = Math.min(lineEnd, to + AnnounceMargin);
1475
+ let text = view.state.sliceDoc(start, end);
1476
+ if (start != line.from) {
1477
+ for (let i = 0; i < AnnounceMargin; i++)
1478
+ if (!Break.test(text[i + 1]) && Break.test(text[i])) {
1479
+ text = text.slice(i);
1480
+ break;
1481
+ }
1482
+ }
1483
+ if (end != lineEnd) {
1484
+ for (let i = text.length - 1; i > text.length - AnnounceMargin; i--)
1485
+ if (!Break.test(text[i - 1]) && Break.test(text[i])) {
1486
+ text = text.slice(0, i);
1487
+ break;
1488
+ }
1489
+ }
1490
+ return EditorView.announce.of(`${view.state.phrase("current match")}. ${text} ${view.state.phrase("on line")} ${line.number}.`);
1491
+ }
1492
+ const baseTheme = /* @__PURE__ */ EditorView.baseTheme({
1493
+ ".cm-panel.cm-search": {
1494
+ padding: "2px 6px 4px",
1495
+ position: "relative",
1496
+ "& [name=close]": {
1497
+ position: "absolute",
1498
+ top: "0",
1499
+ right: "4px",
1500
+ backgroundColor: "inherit",
1501
+ border: "none",
1502
+ font: "inherit",
1503
+ padding: 0,
1504
+ margin: 0
1505
+ },
1506
+ "& input, & button, & label": {
1507
+ margin: ".2em .6em .2em 0"
1508
+ },
1509
+ "& input[type=checkbox]": {
1510
+ marginRight: ".2em"
1511
+ },
1512
+ "& label": {
1513
+ fontSize: "80%",
1514
+ whiteSpace: "pre"
1515
+ }
1516
+ },
1517
+ "&light .cm-searchMatch": { backgroundColor: "#ffff0054" },
1518
+ "&dark .cm-searchMatch": { backgroundColor: "#00ffff8a" },
1519
+ "&light .cm-searchMatch-selected": { backgroundColor: "#ff6a0054" },
1520
+ "&dark .cm-searchMatch-selected": { backgroundColor: "#ff00ff8a" }
1521
+ });
1522
+ const searchExtensions = [
1523
+ searchState,
1524
+ /* @__PURE__ */ Prec.low(searchHighlighter),
1525
+ baseTheme
1526
+ ];
1527
+ const COMMANDS = new Set(COMMAND_NAMES);
1528
+ function token(stream, state) {
1529
+ if (stream.sol()) state.commandSeen = false;
1530
+ if (stream.eatSpace()) return null;
1531
+ if (!state.commandSeen && stream.peek() === "#") {
1532
+ stream.skipToEnd();
1533
+ return "comment";
1534
+ }
1535
+ if (!state.commandSeen) {
1536
+ const word = stream.match(/^[\w-]+/);
1537
+ if (word) {
1538
+ state.commandSeen = true;
1539
+ if (COMMANDS.has(word[0])) return "keyword";
1540
+ }
1541
+ return null;
1542
+ }
1543
+ const ch = stream.peek();
1544
+ if (ch === '"' || ch === "'") {
1545
+ const quote = stream.next();
1546
+ while (!stream.eol()) {
1547
+ const c = stream.next();
1548
+ if (c === "\\") stream.next();
1549
+ else if (c === quote) break;
1550
+ }
1551
+ return "string";
1552
+ }
1553
+ if (stream.match(/^--[\w-]+/)) return "attributeName";
1554
+ if (stream.match(/^https?:\/\/\S+/)) return "url";
1555
+ stream.next();
1556
+ return null;
1557
+ }
1558
+ const pwLanguage = StreamLanguage.define({
1559
+ startState: () => ({ commandSeen: false }),
1560
+ token
1561
+ });
1562
+ const pwHighlightStyle = HighlightStyle.define([
1563
+ { tag: tags.keyword, color: "var(--color-command)" },
1564
+ { tag: tags.comment, color: "var(--color-comment)", fontStyle: "italic" },
1565
+ { tag: tags.string, color: "var(--color-string)" },
1566
+ { tag: tags.attributeName, color: "var(--color-flag)" },
1567
+ { tag: tags.url, color: "var(--color-url)", textDecoration: "underline" }
1568
+ ]);
1569
+ const pwSyntax = [
1570
+ pwLanguage,
1571
+ syntaxHighlighting(pwHighlightStyle)
1572
+ ];
1573
+ const pwTheme = EditorView.theme({
1574
+ "&": {
1575
+ backgroundColor: "var(--bg-editor)",
1576
+ color: "var(--text-default)",
1577
+ height: "100%",
1578
+ fontSize: "13px",
1579
+ fontFamily: '"Cascadia Code", "Fira Code", "Consolas", "Courier New", monospace'
1580
+ },
1581
+ ".cm-content": {
1582
+ // the editable area
1583
+ caretColor: "var(--color-caret)",
1584
+ lineHeight: "18px",
1585
+ padding: "8px 0"
1586
+ },
1587
+ ".cm-cursor": {
1588
+ // blinking cursor
1589
+ borderLeftColor: "var(--color-caret)"
1590
+ },
1591
+ "&:not(.cm-focused) .cm-cursorLayer": {
1592
+ // keep cursor visible when unfocused (e.g. during recording)
1593
+ visibility: "visible",
1594
+ animation: "none"
1595
+ },
1596
+ ".cm-gutters": {
1597
+ // line number column
1598
+ backgroundColor: "var(--bg-editor)",
1599
+ color: "var(--text-line-numbers)",
1600
+ borderRight: "1px solid var(--border-primary)"
1601
+ },
1602
+ ".cm-activeLineGutter": {
1603
+ // gutter on active line
1604
+ backgroundColor: "var(--bg-line-highlight)"
1605
+ },
1606
+ ".cm-activeLine": {
1607
+ // active line background
1608
+ backgroundColor: "var(--bg-line-highlight)"
1609
+ },
1610
+ "&.cm-focused": {
1611
+ // remove default focus outline
1612
+ outline: "none"
1613
+ },
1614
+ ".cm-scroller": {
1615
+ // scrollable container
1616
+ overflow: "auto"
1617
+ },
1618
+ ".cm-run-line": {
1619
+ background: "var(--bg-run-line)"
1620
+ },
1621
+ ".cm-tooltip-autocomplete": {
1622
+ backgroundColor: "var(--bg-toolbar)",
1623
+ border: "1px solid var(--border-primary)",
1624
+ boxShadow: "0 2px 8px rgba(0,0,0,0.2)"
1625
+ },
1626
+ ".cm-tooltip-autocomplete ul li[aria-selected]": {
1627
+ backgroundColor: "var(--bg-button)"
1628
+ },
1629
+ ".cm-breakpoint-gutter": { width: "16px" },
1630
+ ".cm-breakpoint-gutter .cm-gutterElement": { cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" },
1631
+ ".cm-inline-values": {
1632
+ color: "var(--color-inline-value)",
1633
+ fontStyle: "italic",
1634
+ opacity: "0.7",
1635
+ pointerEvents: "none"
1636
+ }
1637
+ });
1638
+ const setRunLineEffect = StateEffect.define();
1639
+ const setLineResultsEffect = StateEffect.define();
1640
+ const toggleBreakpointEffect = StateEffect.define();
1641
+ const setInlineValuesEffect = StateEffect.define();
1642
+ const runLineField = StateField.define({
1643
+ create: () => -1,
1644
+ update(value, tr) {
1645
+ for (const e of tr.effects) {
1646
+ if (e.is(setRunLineEffect)) return e.value;
1647
+ }
1648
+ return value;
1649
+ }
1650
+ });
1651
+ const lineResultsField = StateField.define({
1652
+ create: () => [],
1653
+ update(value, tr) {
1654
+ for (const e of tr.effects) {
1655
+ if (e.is(setLineResultsEffect)) return e.value;
1656
+ }
1657
+ return value;
1658
+ }
1659
+ });
1660
+ const breakpointField = StateField.define({
1661
+ create: () => /* @__PURE__ */ new Set(),
1662
+ update(value, tr) {
1663
+ let set = value;
1664
+ if (tr.docChanged) {
1665
+ const newSet = /* @__PURE__ */ new Set();
1666
+ for (const bp of set) {
1667
+ if (bp >= tr.startState.doc.lines) continue;
1668
+ const line = tr.startState.doc.line(bp + 1);
1669
+ const mapped = tr.changes.mapPos(line.from, 1);
1670
+ if (mapped <= tr.newDoc.length) {
1671
+ newSet.add(tr.newDoc.lineAt(mapped).number - 1);
1672
+ }
1673
+ }
1674
+ set = newSet;
1675
+ }
1676
+ for (const e of tr.effects) {
1677
+ if (e.is(toggleBreakpointEffect)) {
1678
+ const next = new Set(set);
1679
+ if (next.has(e.value)) next.delete(e.value);
1680
+ else next.add(e.value);
1681
+ set = next;
1682
+ }
1683
+ }
1684
+ return set;
1685
+ }
1686
+ });
1687
+ const inlineValuesField = StateField.define({
1688
+ create: () => /* @__PURE__ */ new Map(),
1689
+ update(value, tr) {
1690
+ for (const e of tr.effects) {
1691
+ if (e.is(setInlineValuesEffect)) return e.value;
1692
+ }
1693
+ return value;
1694
+ }
1695
+ });
1696
+ const runLineHighlight = EditorView.decorations.compute(
1697
+ [runLineField],
1698
+ (state) => {
1699
+ const lineNum = state.field(runLineField);
1700
+ if (lineNum < 0 || lineNum >= state.doc.lines) return Decoration.none;
1701
+ const line = state.doc.line(lineNum + 1);
1702
+ return Decoration.set([
1703
+ Decoration.line({ class: "cm-run-line" }).range(line.from)
1704
+ ]);
1705
+ }
1706
+ );
1707
+ const inlineValuesDecoration = EditorView.decorations.compute(
1708
+ [inlineValuesField],
1709
+ (state) => {
1710
+ const values = state.field(inlineValuesField);
1711
+ if (values.size === 0) return Decoration.none;
1712
+ const decorations = [];
1713
+ for (const [lineNum, text] of values) {
1714
+ if (lineNum < 0 || lineNum >= state.doc.lines) continue;
1715
+ const line = state.doc.line(lineNum + 1);
1716
+ decorations.push(
1717
+ Decoration.widget({
1718
+ widget: new InlineValueWidget(text),
1719
+ side: 1
1720
+ }).range(line.to)
1721
+ );
1722
+ }
1723
+ return Decoration.set(decorations, true);
1724
+ }
1725
+ );
1726
+ class ResultMarker extends GutterMarker {
1727
+ constructor(result) {
1728
+ super();
1729
+ this.result = result;
1730
+ }
1731
+ toDOM() {
1732
+ const span = document.createElement("span");
1733
+ span.textContent = this.result === "pass" ? "✓" : "✗";
1734
+ span.style.color = this.result === "pass" ? "var(--color-line-pass)" : "var(--color-line-fail)";
1735
+ return span;
1736
+ }
1737
+ }
1738
+ class BreakpointMarker extends GutterMarker {
1739
+ toDOM() {
1740
+ const dot = document.createElement("span");
1741
+ dot.dataset.testid = "breakpoint-marker";
1742
+ dot.style.cssText = "width:10px;height:10px;border-radius:50%;background:var(--color-breakpoint);display:inline-block;";
1743
+ return dot;
1744
+ }
1745
+ }
1746
+ class InlineValueWidget extends WidgetType {
1747
+ constructor(text) {
1748
+ super();
1749
+ this.text = text;
1750
+ }
1751
+ toDOM() {
1752
+ const span = document.createElement("span");
1753
+ span.className = "cm-inline-values";
1754
+ span.textContent = " " + this.text;
1755
+ return span;
1756
+ }
1757
+ eq(other) {
1758
+ return this.text === other.text;
1759
+ }
1760
+ }
1761
+ const resultGutter = gutter({
1762
+ class: "cm-result-gutter",
1763
+ markers(view) {
1764
+ const results = view.state.field(lineResultsField);
1765
+ const markers = [];
1766
+ for (let i = 0; i < results.length && i < view.state.doc.lines; i++) {
1767
+ if (results[i]) {
1768
+ const line = view.state.doc.line(i + 1);
1769
+ markers.push(new ResultMarker(results[i]).range(line.from));
1770
+ }
1771
+ }
1772
+ return RangeSet.of(markers);
1773
+ }
1774
+ });
1775
+ const breakpointGutter = gutter({
1776
+ class: "cm-breakpoint-gutter",
1777
+ markers(view) {
1778
+ const bps = view.state.field(breakpointField);
1779
+ const markers = [];
1780
+ for (const lineNum of bps) {
1781
+ if (lineNum < view.state.doc.lines) {
1782
+ const line = view.state.doc.line(lineNum + 1);
1783
+ markers.push(new BreakpointMarker().range(line.from));
1784
+ }
1785
+ }
1786
+ return RangeSet.of(markers, true);
1787
+ },
1788
+ domEventHandlers: {
1789
+ mousedown(view, line) {
1790
+ const lineNum = view.state.doc.lineAt(line.from).number - 1;
1791
+ view.dispatch({ effects: toggleBreakpointEffect.of(lineNum) });
1792
+ return true;
1793
+ }
1794
+ }
1795
+ });
1796
+ function dispatchRunState(view, runLine, lineResults, inlineValues = /* @__PURE__ */ new Map()) {
1797
+ view.dispatch({
1798
+ effects: [
1799
+ setRunLineEffect.of(runLine),
1800
+ setLineResultsEffect.of(lineResults),
1801
+ setInlineValuesEffect.of(inlineValues)
1802
+ ]
1803
+ });
1804
+ }
1805
+ const languageCompartment = new Compartment();
1806
+ const jsHighlightStyle = HighlightStyle.define([
1807
+ { tag: tags.keyword, color: "var(--color-command)" },
1808
+ { tag: tags.string, color: "var(--color-string)" },
1809
+ { tag: tags.number, color: "var(--color-url)" },
1810
+ { tag: tags.bool, color: "var(--color-url)" },
1811
+ { tag: tags.null, color: "var(--text-dim)", fontStyle: "italic" },
1812
+ { tag: tags.comment, color: "var(--color-comment)", fontStyle: "italic" },
1813
+ { tag: tags.propertyName, color: "var(--color-flag)" },
1814
+ { tag: tags.definition(tags.variableName), color: "var(--color-active-line)" }
1815
+ ]);
1816
+ const pwModeExtension = [
1817
+ ...pwSyntax,
1818
+ autocompletion({ override: [pwCompletion], icons: false }),
1819
+ placeholder("# Type or open a .pw script...")
1820
+ ];
1821
+ const jsModeExtension = [
1822
+ javascript(),
1823
+ javascriptLanguage.data.of({ autocomplete: playwrightCompletions }),
1824
+ syntaxHighlighting(jsHighlightStyle),
1825
+ autocompletion({ icons: false }),
1826
+ placeholder("// Type JavaScript...")
1827
+ ];
1828
+ const baseExtensions = [
1829
+ languageCompartment.of(pwModeExtension),
1830
+ // dynamic language compartment
1831
+ breakpointField,
1832
+ // ← breakpoint state (must register before gutter)
1833
+ breakpointGutter,
1834
+ // ← clickable breakpoint dots (leftmost gutter)
1835
+ lineNumbers(),
1836
+ // built-in line numbers
1837
+ highlightActiveLineGutter(),
1838
+ // highlights gutter on cursor line
1839
+ highlightActiveLine(),
1840
+ // highlights content on cursor line
1841
+ history(),
1842
+ // undo/redo stack
1843
+ bracketMatching(),
1844
+ // highlight matching brackets
1845
+ closeBrackets(),
1846
+ // auto-insert closing ), ], }, ", '
1847
+ search(),
1848
+ // Ctrl+F search panel
1849
+ keymap.of([
1850
+ { key: "Tab", run: acceptCompletion },
1851
+ // accept completion with Tab
1852
+ ...closeBracketsKeymap,
1853
+ // Backspace deletes both brackets
1854
+ ...defaultKeymap,
1855
+ // basic editing keys
1856
+ ...historyKeymap,
1857
+ // Ctrl+Z, Ctrl+Y
1858
+ ...searchKeymap
1859
+ // Ctrl+F, Ctrl+H
1860
+ ]),
1861
+ EditorState.tabSize.of(2),
1862
+ // tab = 2 spaces
1863
+ pwTheme,
1864
+ runLineField,
1865
+ // ← register the StateField so CM6 tracks it
1866
+ lineResultsField,
1867
+ // ← register the StateField so CM6 tracks it
1868
+ runLineHighlight,
1869
+ // ← decoration that reads runLineField
1870
+ resultGutter,
1871
+ // ← gutter that reads lineResultsField
1872
+ inlineValuesField,
1873
+ inlineValuesDecoration
1874
+ ];
1875
+ function CodeMirrorEditorPane({ editorContent, editorMode, currentRunLine, lineResults, dispatch, ref, containerRef, inlineValues }) {
1876
+ const cmContainerRef = reactExports.useRef(null);
1877
+ const viewRef = reactExports.useRef(null);
1878
+ const externalUpdateRef = reactExports.useRef(false);
1879
+ const lastInsertRangeRef = reactExports.useRef(null);
1880
+ reactExports.useImperativeHandle(ref, () => ({
1881
+ insertAtCursor(text) {
1882
+ const view = viewRef.current;
1883
+ if (!view) return;
1884
+ const { from } = view.state.selection.main;
1885
+ const before = view.state.doc.sliceString(Math.max(0, from - 1), from);
1886
+ const insert = (before && before !== "\n" ? "\n" : "") + text;
1887
+ const insertFrom = from;
1888
+ view.dispatch({
1889
+ changes: { from, to: from, insert },
1890
+ selection: { anchor: from + insert.length },
1891
+ scrollIntoView: true
1892
+ });
1893
+ lastInsertRangeRef.current = { from: insertFrom, to: insertFrom + insert.length };
1894
+ view.focus();
1895
+ },
1896
+ replaceLastInsert(text) {
1897
+ const view = viewRef.current;
1898
+ const range = lastInsertRangeRef.current;
1899
+ if (!view || !range) return;
1900
+ const before = view.state.doc.sliceString(Math.max(0, range.from - 1), range.from);
1901
+ const insert = (before && before !== "\n" ? "\n" : "") + text;
1902
+ view.dispatch({
1903
+ changes: { from: range.from, to: range.to, insert },
1904
+ selection: { anchor: range.from + insert.length },
1905
+ scrollIntoView: true
1906
+ });
1907
+ lastInsertRangeRef.current = { from: range.from, to: range.from + insert.length };
1908
+ view.focus();
1909
+ }
1910
+ }));
1911
+ reactExports.useEffect(() => {
1912
+ const view = new EditorView({
1913
+ doc: editorContent,
1914
+ extensions: [
1915
+ ...baseExtensions,
1916
+ EditorView.updateListener.of((update) => {
1917
+ if (update.docChanged && !externalUpdateRef.current) {
1918
+ dispatch({ type: "EDIT_EDITOR_CONTENT", content: update.state.doc.toString() });
1919
+ }
1920
+ const oldBps = update.startState.field(breakpointField);
1921
+ const newBps = update.state.field(breakpointField);
1922
+ if (oldBps !== newBps) {
1923
+ dispatch({ type: "SET_BREAKPOINTS", breakPoints: newBps });
1924
+ }
1925
+ })
1926
+ ],
1927
+ parent: cmContainerRef.current
1928
+ });
1929
+ viewRef.current = view;
1930
+ return () => view.destroy();
1931
+ }, []);
1932
+ reactExports.useEffect(() => {
1933
+ const view = viewRef.current;
1934
+ if (!view) return;
1935
+ if (view.state.doc.toString() === editorContent) return;
1936
+ externalUpdateRef.current = true;
1937
+ view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: editorContent } });
1938
+ view.dispatch({ selection: { anchor: view.state.doc.length }, scrollIntoView: true });
1939
+ externalUpdateRef.current = false;
1940
+ view.focus();
1941
+ }, [editorContent]);
1942
+ reactExports.useEffect(() => {
1943
+ const view = viewRef.current;
1944
+ if (!view) return;
1945
+ dispatchRunState(view, currentRunLine, lineResults, inlineValues ?? /* @__PURE__ */ new Map());
1946
+ }, [currentRunLine, lineResults, inlineValues]);
1947
+ reactExports.useEffect(() => {
1948
+ const view = viewRef.current;
1949
+ if (!view) return;
1950
+ view.dispatch({ effects: languageCompartment.reconfigure(editorMode === "js" ? jsModeExtension : pwModeExtension) });
1951
+ }, [editorMode]);
1952
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { id: "editor-pane", ref: containerRef, "data-testid": "editor", className: "flex flex-1 min-h-[80px] overflow-hidden bg-(--bg-editor)", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: cmContainerRef, className: "flex-1" }) });
1953
+ }
1954
+ function Splitter({ editorPaneRef }) {
1955
+ const [isDragging, setIsDragging] = reactExports.useState(false);
1956
+ const dragStartY = reactExports.useRef(0);
1957
+ const dragStartHeight = reactExports.useRef(0);
1958
+ function handleMouseMove(event) {
1959
+ const delta = event.clientY - dragStartY.current;
1960
+ const newHeight = dragStartHeight.current + delta;
1961
+ const minHeight = 80;
1962
+ const clamped = Math.max(minHeight, Math.min(newHeight, window.innerHeight - minHeight));
1963
+ editorPaneRef.current.style.flex = `0 0 ${clamped}px`;
1964
+ }
1965
+ function handleMouseUp() {
1966
+ setIsDragging(false);
1967
+ dragStartHeight.current = 0;
1968
+ dragStartY.current = 0;
1969
+ document.body.style.cursor = "";
1970
+ document.body.style.userSelect = "";
1971
+ }
1972
+ function handleMouseDown(event) {
1973
+ document.body.style.cursor = "row-resize";
1974
+ document.body.style.userSelect = "none";
1975
+ dragStartY.current = event.clientY;
1976
+ const editorPane = editorPaneRef.current;
1977
+ dragStartHeight.current = editorPane.offsetHeight;
1978
+ setIsDragging(true);
1979
+ }
1980
+ reactExports.useEffect(() => {
1981
+ if (isDragging) {
1982
+ document.addEventListener("mousemove", handleMouseMove);
1983
+ document.addEventListener("mouseup", handleMouseUp);
1984
+ } else {
1985
+ document.removeEventListener("mousemove", handleMouseMove);
1986
+ document.removeEventListener("mouseup", handleMouseUp);
1987
+ }
1988
+ return () => {
1989
+ document.removeEventListener("mousemove", handleMouseMove);
1990
+ document.removeEventListener("mouseup", handleMouseUp);
1991
+ };
1992
+ }, [isDragging]);
1993
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
1994
+ "div",
1995
+ {
1996
+ id: "splitter",
1997
+ className: "h-[6px] bg-(--bg-splitter) cursor-row-resize shrink-0 flex items-center justify-center border-y border-(--border-primary)",
1998
+ onMouseDown: handleMouseDown,
1999
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2000
+ "div",
2001
+ {
2002
+ id: "splitter-handle",
2003
+ className: "w-10 h-[2px] bg-(--color-splitter-handle) rounded-[1px]"
2004
+ }
2005
+ )
2006
+ }
2007
+ );
2008
+ }
2009
+ function ScopeSection({ scope, defaultOpen, onLocalProps }) {
2010
+ const [open, setOpen] = reactExports.useState(defaultOpen);
2011
+ const [props, setProps] = reactExports.useState(null);
2012
+ const [loading, setLoading] = reactExports.useState(false);
2013
+ const prevObjectId = reactExports.useRef(scope.objectId);
2014
+ reactExports.useEffect(() => {
2015
+ if (open && !props) fetchProps();
2016
+ }, [open]);
2017
+ reactExports.useEffect(() => {
2018
+ if (scope.objectId != prevObjectId.current) {
2019
+ prevObjectId.current = scope.objectId;
2020
+ setProps(null);
2021
+ if (open) fetchProps();
2022
+ }
2023
+ }, [scope.objectId]);
2024
+ function fetchProps() {
2025
+ setLoading(true);
2026
+ swGetProperties(scope.objectId).then((raw) => {
2027
+ const parsed = fromCdpGetProperties(raw);
2028
+ setProps(parsed);
2029
+ setLoading(false);
2030
+ onLocalProps?.(parsed);
2031
+ });
2032
+ }
2033
+ const title = scope.type === "local" ? "Local" : scope.type === "closure" ? scope.name ? `Closure (${scope.name})` : "Closure" : scope.type === "script" ? "Script" : scope.type === "block" ? "Block" : scope.type;
2034
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2035
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ot-toggle", onClick: () => setOpen((o) => !o), children: [
2036
+ open ? "▼" : "▶",
2037
+ " ",
2038
+ title
2039
+ ] }),
2040
+ open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ot-children", children: loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ot-empty", children: "Loading ..." }) : props && Object.entries(props).map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ot-row", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ObjectTree, { data: v, label: k, getProperties: swGetProperties }) }, k)) })
2041
+ ] });
2042
+ }
2043
+ function VariablePane({ scopeData, onLocalProps }) {
2044
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-auto flex-1 p-2", children: scopeData.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ot-empty", children: "No local variables" }) : scopeData.map((scope) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2045
+ ScopeSection,
2046
+ {
2047
+ scope,
2048
+ defaultOpen: scope.type === "local",
2049
+ onLocalProps: scope.type === "local" || scope.type === "block" ? onLocalProps : void 0
2050
+ },
2051
+ scope.objectId
2052
+ )) });
2053
+ }
2054
+ function BottomPane({ isStepDebugging, bottomTab, outputLines, dispatch, scopeData, onLocalProps }) {
2055
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-20 overflow-hidden", "data-testid": "bottom-pane", children: [
2056
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 px-2 py-0.5 border-b border-(--border-primary) bg-(--bg-toolbar) shrink-0", children: [
2057
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2058
+ "button",
2059
+ {
2060
+ "data-active": bottomTab === "console" ? "" : void 0,
2061
+ onClick: () => dispatch({ type: "SET_BOTTOM_TAB", tab: "console" }),
2062
+ "data-testid": "tab-console",
2063
+ children: "Console"
2064
+ }
2065
+ ),
2066
+ isStepDebugging && /* @__PURE__ */ jsxRuntimeExports.jsx(
2067
+ "button",
2068
+ {
2069
+ "data-active": bottomTab === "variables" ? "" : void 0,
2070
+ onClick: () => dispatch({ type: "SET_BOTTOM_TAB", tab: "variables" }),
2071
+ "data-testid": "tab-variables",
2072
+ children: "Variables"
2073
+ }
2074
+ )
2075
+ ] }),
2076
+ bottomTab === "console" && /* @__PURE__ */ jsxRuntimeExports.jsx(Console, { outputLines, dispatch }),
2077
+ bottomTab === "variables" && isStepDebugging && /* @__PURE__ */ jsxRuntimeExports.jsx(VariablePane, { scopeData, onLocalProps })
2078
+ ] });
2079
+ }
2080
+ function DebugBar({ dispatch }) {
2081
+ function handleContinue() {
2082
+ swDebugResume().catch((e) => console.warn("[debug] resume failed:", e));
2083
+ }
2084
+ function handleStepOver() {
2085
+ swDebugStepOver().catch((e) => console.warn("[debug] step-over failed:", e));
2086
+ }
2087
+ function handleStepInto() {
2088
+ swDebugStepInto().catch((e) => console.warn("[debug] step-into failed:", e));
2089
+ }
2090
+ function handleStepOut() {
2091
+ swDebugStepOut().catch((e) => console.warn("[debug] step-out failed:", e));
2092
+ }
2093
+ function handleRestart() {
2094
+ swTerminateExecution().catch((e) => console.warn("[debug] terminate failed:", e));
2095
+ swDebugResume().catch((e) => console.warn("[debug] resume failed:", e));
2096
+ dispatch({ type: "RUN_STOP" });
2097
+ }
2098
+ function handleStop() {
2099
+ swTerminateExecution().catch((e) => console.warn("[debug] terminate failed:", e));
2100
+ swDebugResume().catch((e) => console.warn("[debug] resume failed:", e));
2101
+ dispatch({ type: "RUN_STOP" });
2102
+ }
2103
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { id: "debug-bar", "data-testid": "debug-bar", children: [
2104
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { "data-testid": "debug-continue", title: "Continue (F5)", onClick: handleContinue, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ContinueIcon, { size: 14 }) }),
2105
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { "data-testid": "debug-step-over", title: "Step Over (F10)", onClick: handleStepOver, children: /* @__PURE__ */ jsxRuntimeExports.jsx(StepOverIcon, { size: 14 }) }),
2106
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { "data-testid": "debug-step-into", title: "Step Into (F11)", onClick: handleStepInto, children: /* @__PURE__ */ jsxRuntimeExports.jsx(StepIntoIcon, { size: 14 }) }),
2107
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { "data-testid": "debug-step-out", title: "Step Out (Shift+F11)", onClick: handleStepOut, children: /* @__PURE__ */ jsxRuntimeExports.jsx(StepOutIcon, { size: 14 }) }),
2108
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "debug-bar-sep" }),
2109
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { "data-testid": "debug-restart", title: "Restart (Ctrl+Shift+F5)", onClick: handleRestart, children: /* @__PURE__ */ jsxRuntimeExports.jsx(RestartIcon, { size: 14 }) }),
2110
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { "data-testid": "debug-stop", title: "Stop (Shift+F5)", onClick: handleStop, children: /* @__PURE__ */ jsxRuntimeExports.jsx(DebugStopIcon, { size: 14 }) })
2111
+ ] });
2112
+ }
2113
+ const MAX_INLINE_LENGTH = 80;
2114
+ function formatInlineValues(pausedLine, props) {
2115
+ const values = /* @__PURE__ */ new Map();
2116
+ if (pausedLine < 0 || !props) return values;
2117
+ const parts = [];
2118
+ for (const [name, val] of Object.entries(props)) {
2119
+ if (name.startsWith("[[")) continue;
2120
+ const summary = inlineSummary(val);
2121
+ if (!summary) continue;
2122
+ parts.push(`${name} = ${summary}`);
2123
+ }
2124
+ if (parts.length > 0) {
2125
+ let text = parts.join(", ");
2126
+ if (text.length > MAX_INLINE_LENGTH) text = text.slice(0, MAX_INLINE_LENGTH) + "…";
2127
+ values.set(pausedLine, text);
2128
+ }
2129
+ return values;
2130
+ }
2131
+ function App() {
2132
+ const [state, dispatch] = reactExports.useReducer(panelReducer, initialState);
2133
+ const editorPaneRef = reactExports.useRef(null);
2134
+ const editorRef = reactExports.useRef(null);
2135
+ const attachedTabRef = reactExports.useRef(null);
2136
+ const [localProps, setLocalProps] = reactExports.useState(null);
2137
+ const inlineValues = reactExports.useMemo(
2138
+ () => formatInlineValues(state.currentRunLine, localProps),
2139
+ [state.currentRunLine, localProps]
2140
+ );
2141
+ reactExports.useEffect(() => {
2142
+ onConsoleEvent((level, args) => {
2143
+ for (const arg of args) {
2144
+ const type = level === "error" ? "error" : level === "warn" ? "info" : "success";
2145
+ dispatch({ type: "ADD_LINE", line: { text: "", type, value: arg } });
2146
+ }
2147
+ });
2148
+ return () => onConsoleEvent(null);
2149
+ }, [dispatch]);
2150
+ reactExports.useEffect(() => {
2151
+ loadSettings().then((s) => {
2152
+ dispatch({ type: "SET_EDITOR_MODE", mode: s.languageMode });
2153
+ });
2154
+ }, []);
2155
+ async function doAttach(tabId) {
2156
+ dispatch({ type: "ATTACH_START" });
2157
+ const res = await attachToTab(tabId);
2158
+ if (res.ok && res.url) {
2159
+ attachedTabRef.current = tabId;
2160
+ dispatch({ type: "ATTACH_SUCCESS", url: res.url, tabId });
2161
+ } else {
2162
+ attachedTabRef.current = null;
2163
+ dispatch({ type: "ATTACH_FAIL" });
2164
+ }
2165
+ }
2166
+ reactExports.useEffect(() => {
2167
+ if (!chrome.tabs?.query) return;
2168
+ const params = new URLSearchParams(window.location.search);
2169
+ const tabIdParam = params.get("tabId");
2170
+ if (tabIdParam) {
2171
+ doAttach(Number(tabIdParam));
2172
+ return;
2173
+ }
2174
+ chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
2175
+ const tab = tabs[0];
2176
+ const url = tab?.url ?? "";
2177
+ const ownOrigin = `chrome-extension://${chrome.runtime.id}/`;
2178
+ if (tab?.id && !url.startsWith("chrome://") && (!url.startsWith("chrome-extension://") || url.startsWith(ownOrigin))) {
2179
+ doAttach(tab.id);
2180
+ }
2181
+ });
2182
+ const onActivated = async (info) => {
2183
+ const tab = await chrome.tabs.get(info.tabId).catch(() => null);
2184
+ const url = tab?.url ?? "";
2185
+ if (url.startsWith("chrome-extension://") && !url.startsWith(`chrome-extension://${chrome.runtime.id}/`)) return;
2186
+ if (url.startsWith("chrome://")) {
2187
+ attachedTabRef.current = null;
2188
+ dispatch({ type: "DETACH" });
2189
+ return;
2190
+ }
2191
+ doAttach(info.tabId);
2192
+ };
2193
+ chrome.tabs.onActivated.addListener(onActivated);
2194
+ const onUpdated = (tabId, changeInfo) => {
2195
+ if (attachedTabRef.current === tabId) {
2196
+ if (changeInfo.url) dispatch({ type: "UPDATE_URL", url: changeInfo.url });
2197
+ return;
2198
+ }
2199
+ if (changeInfo.url && !changeInfo.url.startsWith("chrome://") && (!changeInfo.url.startsWith("chrome-extension://") || changeInfo.url.startsWith(`chrome-extension://${chrome.runtime.id}/`))) {
2200
+ chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
2201
+ if (tabs[0]?.id === tabId) doAttach(tabId);
2202
+ });
2203
+ }
2204
+ };
2205
+ chrome.tabs.onUpdated.addListener(onUpdated);
2206
+ return () => {
2207
+ chrome.tabs.onActivated.removeListener(onActivated);
2208
+ chrome.tabs.onUpdated.removeListener(onUpdated);
2209
+ };
2210
+ }, []);
2211
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2212
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2213
+ Toolbar,
2214
+ {
2215
+ editorContent: state.editorContent,
2216
+ editorMode: state.editorMode,
2217
+ stepLine: state.stepLine,
2218
+ isRunning: state.isRunning,
2219
+ isStepDebugging: state.isStepDebugging,
2220
+ attachedUrl: state.attachedUrl,
2221
+ attachedTabId: state.attachedTabId,
2222
+ isAttaching: state.isAttaching,
2223
+ dispatch,
2224
+ editorRef,
2225
+ breakPoints: state.breakPoints
2226
+ }
2227
+ ),
2228
+ state.isStepDebugging && /* @__PURE__ */ jsxRuntimeExports.jsx(DebugBar, { dispatch }),
2229
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: editorPaneRef, style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 80, overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2230
+ CodeMirrorEditorPane,
2231
+ {
2232
+ ref: editorRef,
2233
+ editorContent: state.editorContent,
2234
+ editorMode: state.editorMode,
2235
+ currentRunLine: state.currentRunLine,
2236
+ lineResults: state.lineResults,
2237
+ inlineValues,
2238
+ dispatch
2239
+ }
2240
+ ) }),
2241
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Splitter, { editorPaneRef }),
2242
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2243
+ BottomPane,
2244
+ {
2245
+ outputLines: state.outputLines,
2246
+ dispatch,
2247
+ bottomTab: state.bottomTab,
2248
+ isStepDebugging: state.isStepDebugging,
2249
+ scopeData: state.scopeData,
2250
+ onLocalProps: setLocalProps
2251
+ }
2252
+ )
2253
+ ] });
2254
+ }
2255
+ clientExports.createRoot(document.getElementById("root")).render(
2256
+ /* @__PURE__ */ jsxRuntimeExports.jsx(App, {})
2257
+ );
2258
+ //# sourceMappingURL=panel.js.map