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