@pierre/diffs 1.3.0-beta.1 → 1.3.0-beta.3

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 (82) hide show
  1. package/dist/components/CodeView.d.ts +4 -0
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +38 -0
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts.map +1 -1
  6. package/dist/components/File.js +9 -9
  7. package/dist/components/File.js.map +1 -1
  8. package/dist/components/FileDiff.d.ts.map +1 -1
  9. package/dist/components/FileDiff.js +3 -2
  10. package/dist/components/FileDiff.js.map +1 -1
  11. package/dist/components/VirtualizedFile.js +6 -1
  12. package/dist/components/VirtualizedFile.js.map +1 -1
  13. package/dist/components/VirtualizedFileDiff.js +22 -42
  14. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  15. package/dist/components/Virtualizer.js +5 -3
  16. package/dist/components/Virtualizer.js.map +1 -1
  17. package/dist/editor/editor.d.ts +7 -1
  18. package/dist/editor/editor.d.ts.map +1 -1
  19. package/dist/editor/editor.js +550 -405
  20. package/dist/editor/editor.js.map +1 -1
  21. package/dist/editor/editor2.js +6 -0
  22. package/dist/editor/editor2.js.map +1 -0
  23. package/dist/editor/pieceTable.d.ts +1 -1
  24. package/dist/editor/pieceTable.d.ts.map +1 -1
  25. package/dist/editor/pieceTable.js +2 -22
  26. package/dist/editor/pieceTable.js.map +1 -1
  27. package/dist/editor/quickEdit.js +2 -4
  28. package/dist/editor/quickEdit.js.map +1 -1
  29. package/dist/editor/searchPanel.d.ts +6 -7
  30. package/dist/editor/searchPanel.d.ts.map +1 -1
  31. package/dist/editor/searchPanel.js +102 -137
  32. package/dist/editor/searchPanel.js.map +1 -1
  33. package/dist/editor/selection.js +8 -2
  34. package/dist/editor/selection.js.map +1 -1
  35. package/dist/editor/sprite.d.ts +7 -0
  36. package/dist/editor/sprite.d.ts.map +1 -0
  37. package/dist/editor/sprite.js +38 -0
  38. package/dist/editor/sprite.js.map +1 -0
  39. package/dist/editor/textDocument.d.ts +1 -1
  40. package/dist/editor/textDocument.d.ts.map +1 -1
  41. package/dist/editor/textDocument.js +2 -2
  42. package/dist/editor/textDocument.js.map +1 -1
  43. package/dist/editor/textMeasure.js +3 -3
  44. package/dist/editor/textMeasure.js.map +1 -1
  45. package/dist/editor/tokenzier.d.ts +6 -2
  46. package/dist/editor/tokenzier.d.ts.map +1 -1
  47. package/dist/editor/tokenzier.js +127 -85
  48. package/dist/editor/tokenzier.js.map +1 -1
  49. package/dist/index.d.ts +2 -2
  50. package/dist/react/index.d.ts +2 -2
  51. package/dist/react/jsx.d.ts +1 -0
  52. package/dist/react/jsx.d.ts.map +1 -1
  53. package/dist/react/types.js +1 -0
  54. package/dist/renderers/DiffHunksRenderer.js +5 -9
  55. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  56. package/dist/ssr/index.d.ts +2 -2
  57. package/dist/style.js +1 -1
  58. package/dist/style.js.map +1 -1
  59. package/dist/types.d.ts +13 -12
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/utils/computeEstimatedDiffHeights.js +9 -20
  62. package/dist/utils/computeEstimatedDiffHeights.js.map +1 -1
  63. package/dist/utils/iterateOverDiff.js +147 -182
  64. package/dist/utils/iterateOverDiff.js.map +1 -1
  65. package/dist/utils/virtualDiffLayout.d.ts +23 -2
  66. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  67. package/dist/utils/virtualDiffLayout.js +41 -1
  68. package/dist/utils/virtualDiffLayout.js.map +1 -1
  69. package/dist/worker/WorkerPoolManager.js +1 -1
  70. package/dist/worker/WorkerPoolManager.js.map +1 -1
  71. package/dist/worker/{wasm-D4DU5jgR.js → wasm-BaDzIkIn.js} +2 -2
  72. package/dist/worker/wasm-BaDzIkIn.js.map +1 -0
  73. package/dist/worker/worker-portable.js +294 -292
  74. package/dist/worker/worker-portable.js.map +1 -1
  75. package/dist/worker/worker.js +179 -181
  76. package/dist/worker/worker.js.map +1 -1
  77. package/package.json +22 -21
  78. package/dist/editor/css.d.ts +0 -6
  79. package/dist/editor/css.d.ts.map +0 -1
  80. package/dist/editor/css.js +0 -218
  81. package/dist/editor/css.js.map +0 -1
  82. package/dist/worker/wasm-D4DU5jgR.js.map +0 -1
@@ -1,116 +1,111 @@
1
1
  import { isPrimaryModifier } from "./platform.js";
2
+ import { getEditorIconSvg } from "./sprite.js";
2
3
  import { h } from "./utils.js";
3
4
 
4
5
  //#region src/editor/searchPanel.ts
5
6
  var SearchPanelWidget = class {
6
- #textDocument;
7
7
  #container;
8
8
  #inputElement;
9
- #matchesElement;
10
- #searchParams = {
11
- text: "",
12
- replaceText: "",
13
- caseSensitive: false,
14
- wholeWord: false,
15
- regex: false
16
- };
17
- #allMatches = [];
18
9
  constructor(options) {
19
- const { textDocument, containerElement, defaultQuery, initialMatch, postSearch, getCurrentSearchRange, onClose } = options;
20
- const close = () => {
21
- this.cleanup();
22
- onClose();
10
+ const { textDocument, containerElement, defaultQuery, initialMatch, scrollToMatch, onUpdate, onClose } = options;
11
+ const searchParams = {
12
+ text: defaultQuery,
13
+ replaceText: "",
14
+ caseSensitive: false,
15
+ wholeWord: false,
16
+ regex: false
23
17
  };
24
- const updateSearchParam = (key, value) => {
25
- this.#searchParams[key] = value;
26
- updateAllMatches();
27
- this.updateMatches();
18
+ const matches = {
19
+ all: [],
20
+ current: void 0
28
21
  };
29
- const updateAllMatches = () => {
30
- this.#allMatches = this.#searchParams.text !== "" ? this.#textDocument.search("findAll", this.#searchParams) : [];
22
+ const matchResultElement = h("div", { dataset: "matches" });
23
+ const updateMatches = () => {
24
+ matches.all = searchParams.text !== "" ? textDocument.search(searchParams) : [];
31
25
  this.#container.querySelectorAll("[data-disabled]").forEach((element) => {
32
- element.dataset.disabled = String(this.#allMatches.length === 0);
26
+ element.dataset.disabled = String(matches.all.length === 0);
33
27
  });
28
+ if (searchParams.text === "") {
29
+ matchResultElement.textContent = "";
30
+ delete matchResultElement.dataset.noMatches;
31
+ return;
32
+ }
33
+ if (matches.all.length === 0) {
34
+ matchResultElement.textContent = "No results";
35
+ matchResultElement.dataset.noMatches = "";
36
+ } else {
37
+ delete matchResultElement.dataset.noMatches;
38
+ updateCurrentMatch(onUpdate(matches.all));
39
+ return;
40
+ }
41
+ matches.current = void 0;
42
+ onUpdate([]);
34
43
  };
35
- const search = (kind, retainFocus) => {
36
- const matches = this.#textDocument.search(kind, this.#searchParams, getCurrentSearchRange());
37
- if (matches.length === 0) return;
38
- const firstMatch = matches[0];
39
- this.updateMatches(firstMatch);
40
- postSearch(kind, firstMatch, retainFocus);
44
+ const updateCurrentMatch = (currentMatch) => {
45
+ if (currentMatch === void 0) matchResultElement.textContent = `${matches.all.length} results`;
46
+ else {
47
+ const [start, end] = currentMatch;
48
+ matchResultElement.textContent = `${matches.all.findIndex((m) => m[0] === start && m[1] === end) + 1} of ${matches.all.length}`;
49
+ }
50
+ matches.current = currentMatch;
51
+ };
52
+ const updateSearchParam = (key, value) => {
53
+ searchParams[key] = value;
54
+ updateMatches();
41
55
  };
42
- const settingsSwitch = h("div", {
43
- dataset: { icon: "settings" },
44
- title: "Settings",
45
- innerHTML: `<svg width="16" height="16" viewBox="0 0 20 20">
46
- <line x1="3" y1="6" x2="10" y2="6" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line>
47
- <circle cx="12.5" cy="6" r="2.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></circle>
48
- <line x1="15" y1="6" x2="17" y2="6" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line>
49
- <line x1="17" y1="14" x2="10" y2="14" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line>
50
- <circle cx="7.5" cy="14" r="2.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></circle>
51
- <line x1="5" y1="14" x2="3" y2="14" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line>
52
- </svg>
53
- `,
54
- onclick: () => {
55
- settingsSwitch.replaceWith(settingsPanel);
56
+ const findNextMatch = (findPrevious = false, retainFocus = false) => {
57
+ const allMatches = matches.all;
58
+ let nextMatch = allMatches[0];
59
+ if (allMatches.length > 0) if (findPrevious) {
60
+ const searchOffset = matches.current?.[0] ?? 0;
61
+ nextMatch = allMatches.at(-1);
62
+ for (const m of allMatches) if (m[1] <= searchOffset) nextMatch = m;
63
+ else break;
64
+ } else {
65
+ const searchOffset = matches.current?.[1] ?? 0;
66
+ for (const m of allMatches) if (m[0] >= searchOffset) {
67
+ nextMatch = m;
68
+ break;
69
+ }
56
70
  }
57
- });
58
- const settingsPanel = h("div", {
59
- dataset: "settings",
60
- children: [
61
- h("label", {
62
- dataset: "checkbox",
63
- children: [h("input", {
64
- type: "checkbox",
65
- checked: this.#searchParams.caseSensitive,
66
- onchange: (e) => {
67
- updateSearchParam("caseSensitive", e.target.checked);
68
- }
69
- }), "Match Case"]
70
- }),
71
- h("label", {
72
- dataset: "checkbox",
73
- children: [h("input", {
74
- type: "checkbox",
75
- checked: this.#searchParams.wholeWord,
76
- onchange: (e) => {
77
- updateSearchParam("wholeWord", e.target.checked);
78
- }
79
- }), "Whole Word"]
80
- }),
81
- h("label", {
82
- dataset: "checkbox",
83
- children: [h("input", {
84
- type: "checkbox",
85
- checked: this.#searchParams.regex,
86
- onchange: (e) => {
87
- updateSearchParam("regex", e.target.checked);
88
- }
89
- }), "Regexp"]
90
- })
91
- ],
92
- onmouseleave: () => {
93
- closeSettingsPanelTimeout = setTimeout(() => {
94
- settingsPanel.replaceWith(settingsSwitch);
95
- }, 500);
96
- },
97
- onmouseenter: () => {
98
- clearTimeout(closeSettingsPanelTimeout);
99
- closeSettingsPanelTimeout = void 0;
71
+ if (nextMatch !== void 0) {
72
+ updateCurrentMatch(nextMatch);
73
+ scrollToMatch(nextMatch, retainFocus);
100
74
  }
101
- });
102
- let closeSettingsPanelTimeout;
103
- this.#textDocument = textDocument;
104
- this.#searchParams.text = defaultQuery;
75
+ matches.current = nextMatch;
76
+ };
77
+ const close = () => {
78
+ this.cleanup();
79
+ onClose();
80
+ };
81
+ const makeToggle = (icon, title, key) => {
82
+ const button = h("div", {
83
+ dataset: {
84
+ icon,
85
+ active: String(searchParams[key])
86
+ },
87
+ title,
88
+ innerHTML: getEditorIconSvg(icon),
89
+ onclick: () => {
90
+ const next = !searchParams[key];
91
+ button.dataset.active = String(next);
92
+ updateSearchParam(key, next);
93
+ }
94
+ });
95
+ return button;
96
+ };
97
+ const caseSensitiveToggle = makeToggle("case", "Match Case", "caseSensitive");
98
+ const wholeWordToggle = makeToggle("whole-word", "Whole Word", "wholeWord");
99
+ const regexToggle = makeToggle("regex", "Regexp", "regex");
105
100
  this.#inputElement = h("input", {
106
101
  type: "text",
107
102
  placeholder: "Search",
108
103
  dataset: "search",
109
104
  value: defaultQuery,
110
105
  oninput: (e) => {
111
- this.#searchParams.text = e.target.value;
112
- updateAllMatches();
113
- this.updateMatches();
106
+ searchParams.text = e.target.value;
107
+ matches.current = void 0;
108
+ updateMatches();
114
109
  },
115
110
  onkeydown: (e) => {
116
111
  if (e.key === "Escape") {
@@ -118,11 +113,10 @@ var SearchPanelWidget = class {
118
113
  close();
119
114
  } else if (e.key === "Enter") {
120
115
  e.preventDefault();
121
- search("findNext", true);
116
+ findNextMatch(false, true);
122
117
  } else if (e.key === "f" && isPrimaryModifier(e)) e.preventDefault();
123
118
  }
124
119
  });
125
- this.#matchesElement = h("div", { dataset: "matches" });
126
120
  this.#container = h("div", {
127
121
  dataset: "searchPanel",
128
122
  children: [h("div", {
@@ -130,26 +124,23 @@ var SearchPanelWidget = class {
130
124
  children: [
131
125
  h("div", {
132
126
  dataset: { icon: "search" },
133
- innerHTML: `<svg width="16" height="16" viewBox="0 0 20 20">
134
- <line x1="16.5" y1="16.5" x2="12.0355" y2="12.0355" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line>
135
- <circle cx="8.5" cy="8.5" r="5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></circle>
136
- </svg>
137
- `
127
+ innerHTML: getEditorIconSvg("search")
138
128
  }),
139
129
  this.#inputElement,
140
- this.#matchesElement,
130
+ matchResultElement,
131
+ caseSensitiveToggle,
132
+ wholeWordToggle,
133
+ regexToggle,
134
+ h("div", { dataset: "divider" }),
141
135
  h("div", {
142
136
  dataset: {
143
137
  icon: "arrow-up",
144
138
  disabled: "true"
145
139
  },
146
140
  title: "Previous",
147
- innerHTML: `<svg width="14" height="14" viewBox="0 0 20 20">
148
- <polyline points="12.5 3.5 6 10 12.5 16.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline>
149
- </svg>
150
- `,
141
+ innerHTML: getEditorIconSvg("arrow-up"),
151
142
  onclick: () => {
152
- search("findPrevious");
143
+ findNextMatch(true);
153
144
  }
154
145
  }),
155
146
  h("div", {
@@ -158,56 +149,30 @@ var SearchPanelWidget = class {
158
149
  disabled: "true"
159
150
  },
160
151
  title: "Next",
161
- innerHTML: `<svg width="14" height="14" viewBox="0 0 20 20">
162
- <polyline points="7.5 16.5 14 10 7.5 3.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline>
163
- </svg>
164
- `,
152
+ innerHTML: getEditorIconSvg("arrow-down"),
165
153
  onclick: () => {
166
- search("findNext");
154
+ findNextMatch();
167
155
  }
168
156
  }),
169
- h("div", { dataset: "spacer" }),
170
- settingsSwitch,
171
157
  h("div", {
172
158
  dataset: { icon: "close" },
173
159
  title: "Close",
174
- innerHTML: `<svg width="16" height="16" viewBox="0 0 20 20">
175
- <line x1="5" y1="5" x2="15" y2="15" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line>
176
- <line x1="5" y1="15" x2="15" y2="5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line>
177
- </svg>
178
- `,
160
+ innerHTML: getEditorIconSvg("close"),
179
161
  onclick: close
180
162
  })
181
163
  ]
182
164
  })]
183
165
  });
166
+ matches.current = initialMatch;
184
167
  containerElement.before(this.#container);
185
168
  requestAnimationFrame(() => {
186
- if (initialMatch !== void 0) {
187
- updateAllMatches();
188
- this.updateMatches(initialMatch);
189
- }
169
+ if (initialMatch !== void 0) updateMatches();
170
+ else onUpdate([]);
190
171
  this.#inputElement.select();
191
172
  });
192
173
  }
193
- updateMatches(currentMatch = this.#allMatches[0]) {
194
- const allMatches = this.#allMatches;
195
- if (this.#searchParams.text === "") {
196
- this.#matchesElement.textContent = "";
197
- delete this.#matchesElement.dataset.noMatches;
198
- return;
199
- }
200
- if (allMatches.length === 0) {
201
- this.#matchesElement.textContent = "No results";
202
- this.#matchesElement.dataset.noMatches = "";
203
- } else {
204
- delete this.#matchesElement.dataset.noMatches;
205
- const index = allMatches.findIndex((m) => m[0] === currentMatch[0] && m[1] === currentMatch[1]);
206
- this.#matchesElement.textContent = index !== -1 ? `${index + 1} of ${allMatches.length}` : "No results";
207
- }
208
- }
209
174
  focus() {
210
- this.#inputElement.select();
175
+ this.#inputElement.focus();
211
176
  }
212
177
  cleanup() {
213
178
  this.#container.remove();
@@ -1 +1 @@
1
- {"version":3,"file":"searchPanel.js","names":["#searchParams","#allMatches","#textDocument","#container","closeSettingsPanelTimeout: ReturnType<typeof setTimeout> | undefined","#inputElement","#matchesElement"],"sources":["../../src/editor/searchPanel.ts"],"sourcesContent":["import { isPrimaryModifier } from './platform';\nimport type { Range, TextDocument } from './textDocument';\nimport { h } from './utils';\n\nexport type SearchKind =\n | 'findNext'\n | 'findPrevious'\n | 'findAll'\n | 'replace'\n | 'replaceAll';\n\nexport interface SearchParams {\n text: string;\n replaceText: string;\n caseSensitive: boolean;\n wholeWord: boolean;\n regex: boolean;\n}\n\nexport interface SearchPanelOptions {\n textDocument: TextDocument<unknown>;\n containerElement: HTMLElement;\n defaultQuery: string;\n initialMatch?: [number, number];\n postSearch: (\n kind: SearchKind,\n match: [number, number],\n retainFocus?: boolean\n ) => void;\n getCurrentSearchRange: () => Range | undefined;\n onClose: () => void;\n}\n\nexport class SearchPanelWidget {\n #textDocument: TextDocument<unknown>;\n #container: HTMLDivElement;\n #inputElement: HTMLInputElement;\n #matchesElement: HTMLDivElement;\n #searchParams: SearchParams = {\n text: '',\n replaceText: '',\n caseSensitive: false,\n wholeWord: false,\n regex: false,\n };\n #allMatches: [number, number][] = [];\n\n constructor(options: SearchPanelOptions) {\n const {\n textDocument,\n containerElement,\n defaultQuery,\n initialMatch,\n postSearch,\n getCurrentSearchRange,\n onClose,\n } = options;\n\n const close = () => {\n this.cleanup();\n onClose();\n };\n\n const updateSearchParam = <K extends keyof SearchParams>(\n key: K,\n value: SearchParams[K]\n ) => {\n this.#searchParams[key] = value;\n updateAllMatches();\n this.updateMatches();\n };\n\n const updateAllMatches = () => {\n this.#allMatches =\n this.#searchParams.text !== ''\n ? this.#textDocument.search('findAll', this.#searchParams)\n : [];\n this.#container\n .querySelectorAll<HTMLElement>('[data-disabled]')\n .forEach((element) => {\n element.dataset.disabled = String(this.#allMatches.length === 0);\n });\n };\n\n const search = (kind: SearchKind, retainFocus?: boolean) => {\n const matches = this.#textDocument.search(\n kind,\n this.#searchParams,\n getCurrentSearchRange()\n );\n if (matches.length === 0) {\n return;\n }\n const firstMatch = matches[0];\n this.updateMatches(firstMatch);\n postSearch(kind, firstMatch, retainFocus);\n };\n\n const settingsSwitch = h('div', {\n dataset: { icon: 'settings' },\n title: 'Settings',\n innerHTML: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <line x1=\"3\" y1=\"6\" x2=\"10\" y2=\"6\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></line>\n <circle cx=\"12.5\" cy=\"6\" r=\"2.5\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></circle>\n <line x1=\"15\" y1=\"6\" x2=\"17\" y2=\"6\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></line>\n <line x1=\"17\" y1=\"14\" x2=\"10\" y2=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></line>\n <circle cx=\"7.5\" cy=\"14\" r=\"2.5\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></circle>\n <line x1=\"5\" y1=\"14\" x2=\"3\" y2=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></line>\n </svg>\n `,\n onclick: () => {\n settingsSwitch.replaceWith(settingsPanel);\n },\n });\n const settingsPanel = h('div', {\n dataset: 'settings',\n children: [\n h('label', {\n dataset: 'checkbox',\n children: [\n h('input', {\n type: 'checkbox',\n checked: this.#searchParams.caseSensitive,\n onchange: (e: Event) => {\n updateSearchParam(\n 'caseSensitive',\n (e.target as HTMLInputElement).checked\n );\n },\n }),\n 'Match Case',\n ],\n }),\n h('label', {\n dataset: 'checkbox',\n children: [\n h('input', {\n type: 'checkbox',\n checked: this.#searchParams.wholeWord,\n onchange: (e: Event) => {\n updateSearchParam(\n 'wholeWord',\n (e.target as HTMLInputElement).checked\n );\n },\n }),\n 'Whole Word',\n ],\n }),\n h('label', {\n dataset: 'checkbox',\n children: [\n h('input', {\n type: 'checkbox',\n checked: this.#searchParams.regex,\n onchange: (e: Event) => {\n updateSearchParam(\n 'regex',\n (e.target as HTMLInputElement).checked\n );\n },\n }),\n 'Regexp',\n ],\n }),\n ],\n onmouseleave: () => {\n closeSettingsPanelTimeout = setTimeout(() => {\n settingsPanel.replaceWith(settingsSwitch);\n }, 500);\n },\n onmouseenter: () => {\n clearTimeout(closeSettingsPanelTimeout);\n closeSettingsPanelTimeout = undefined;\n },\n });\n\n let closeSettingsPanelTimeout: ReturnType<typeof setTimeout> | undefined;\n\n this.#textDocument = textDocument;\n this.#searchParams.text = defaultQuery;\n\n this.#inputElement = h('input', {\n type: 'text',\n placeholder: 'Search',\n dataset: 'search',\n value: defaultQuery,\n oninput: (e: Event) => {\n this.#searchParams.text = (e.target as HTMLInputElement).value;\n updateAllMatches();\n this.updateMatches();\n },\n onkeydown: (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n close();\n } else if (e.key === 'Enter') {\n e.preventDefault();\n search('findNext', true);\n } else if (e.key === 'f' && isPrimaryModifier(e)) {\n // prevent the default browser search panel open behavior\n e.preventDefault();\n }\n },\n });\n this.#matchesElement = h('div', { dataset: 'matches' });\n this.#container = h('div', {\n dataset: 'searchPanel',\n children: [\n h('div', {\n dataset: 'searchPanelRow',\n children: [\n h('div', {\n dataset: { icon: 'search' },\n innerHTML: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <line x1=\"16.5\" y1=\"16.5\" x2=\"12.0355\" y2=\"12.0355\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></line>\n <circle cx=\"8.5\" cy=\"8.5\" r=\"5\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></circle>\n </svg>\n `,\n }),\n this.#inputElement,\n this.#matchesElement,\n h('div', {\n dataset: { icon: 'arrow-up', disabled: 'true' },\n title: 'Previous',\n innerHTML: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <polyline points=\"12.5 3.5 6 10 12.5 16.5\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></polyline>\n </svg>\n `,\n onclick: () => {\n search('findPrevious');\n },\n }),\n h('div', {\n dataset: { icon: 'arrow-down', disabled: 'true' },\n title: 'Next',\n innerHTML: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 20 20\">\n <polyline points=\"7.5 16.5 14 10 7.5 3.5\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></polyline>\n </svg>\n `,\n onclick: () => {\n search('findNext');\n },\n }),\n h('div', { dataset: 'spacer' }),\n settingsSwitch,\n h('div', {\n dataset: { icon: 'close' },\n title: 'Close',\n innerHTML: `<svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\">\n <line x1=\"5\" y1=\"5\" x2=\"15\" y2=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></line>\n <line x1=\"5\" y1=\"15\" x2=\"15\" y2=\"5\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"></line>\n </svg>\n `,\n onclick: close,\n }),\n ],\n }),\n ],\n });\n containerElement.before(this.#container);\n\n requestAnimationFrame(() => {\n if (initialMatch !== undefined) {\n updateAllMatches();\n this.updateMatches(initialMatch);\n }\n this.#inputElement.select();\n });\n }\n\n updateMatches(currentMatch: [number, number] = this.#allMatches[0]): void {\n const allMatches = this.#allMatches;\n const searchText = this.#searchParams.text;\n\n if (searchText === '') {\n this.#matchesElement.textContent = '';\n delete this.#matchesElement.dataset.noMatches;\n return;\n }\n\n if (allMatches.length === 0) {\n this.#matchesElement.textContent = 'No results';\n this.#matchesElement.dataset.noMatches = '';\n } else {\n delete this.#matchesElement.dataset.noMatches;\n const index = allMatches.findIndex(\n (m) => m[0] === currentMatch[0] && m[1] === currentMatch[1]\n );\n this.#matchesElement.textContent =\n index !== -1 ? `${index + 1} of ${allMatches.length}` : 'No results';\n }\n }\n\n focus(): void {\n this.#inputElement.select();\n }\n\n cleanup(): void {\n this.#container.remove();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"searchPanel.js","names":["searchParams: SearchParams","#container","nextMatch: MatchRange | undefined","#inputElement"],"sources":["../../src/editor/searchPanel.ts"],"sourcesContent":["import { isPrimaryModifier } from './platform';\nimport { getEditorIconSvg, type SVGSpriteNames } from './sprite';\nimport type { TextDocument } from './textDocument';\nimport { h } from './utils';\n\nexport type MatchRange = [startOffset: number, endOffset: number];\n\nexport interface SearchParams {\n text: string;\n replaceText: string;\n caseSensitive: boolean;\n wholeWord: boolean;\n regex: boolean;\n}\n\nexport interface SearchPanelOptions {\n textDocument: TextDocument<unknown>;\n containerElement: HTMLElement;\n defaultQuery: string;\n initialMatch?: MatchRange;\n scrollToMatch: (nextMatch: MatchRange, retainFocus: boolean) => void;\n onUpdate: (matches: MatchRange[]) => MatchRange | undefined;\n onClose: () => void;\n}\n\nexport class SearchPanelWidget {\n #container: HTMLDivElement;\n #inputElement: HTMLInputElement;\n\n constructor(options: SearchPanelOptions) {\n const {\n textDocument,\n containerElement,\n defaultQuery,\n initialMatch,\n scrollToMatch,\n onUpdate,\n onClose,\n } = options;\n\n const searchParams: SearchParams = {\n text: defaultQuery,\n replaceText: '',\n caseSensitive: false,\n wholeWord: false,\n regex: false,\n };\n\n const matches = {\n all: [] as MatchRange[],\n current: undefined as MatchRange | undefined,\n };\n\n const matchResultElement = h('div', { dataset: 'matches' });\n const updateMatches = () => {\n matches.all =\n searchParams.text !== '' ? textDocument.search(searchParams) : [];\n this.#container\n .querySelectorAll<HTMLElement>('[data-disabled]')\n .forEach((element) => {\n element.dataset.disabled = String(matches.all.length === 0);\n });\n\n if (searchParams.text === '') {\n matchResultElement.textContent = '';\n delete matchResultElement.dataset.noMatches;\n return;\n }\n\n if (matches.all.length === 0) {\n matchResultElement.textContent = 'No results';\n matchResultElement.dataset.noMatches = '';\n } else {\n delete matchResultElement.dataset.noMatches;\n updateCurrentMatch(onUpdate(matches.all));\n return;\n }\n\n matches.current = undefined;\n onUpdate([]);\n };\n\n const updateCurrentMatch = (currentMatch: MatchRange | undefined) => {\n if (currentMatch === undefined) {\n matchResultElement.textContent = `${matches.all.length} results`;\n } else {\n const [start, end] = currentMatch;\n const index = matches.all.findIndex(\n (m) => m[0] === start && m[1] === end\n );\n matchResultElement.textContent = `${index + 1} of ${matches.all.length}`;\n }\n matches.current = currentMatch;\n };\n\n const updateSearchParam = <K extends keyof SearchParams>(\n key: K,\n value: SearchParams[K]\n ) => {\n searchParams[key] = value;\n updateMatches();\n };\n\n const findNextMatch = (\n findPrevious: boolean = false,\n retainFocus: boolean = false\n ) => {\n const allMatches = matches.all;\n let nextMatch: MatchRange | undefined = allMatches[0];\n if (allMatches.length > 0) {\n if (findPrevious) {\n const searchOffset = matches.current?.[0] ?? 0;\n nextMatch = allMatches.at(-1);\n for (const m of allMatches) {\n if (m[1] <= searchOffset) {\n nextMatch = m;\n } else {\n break;\n }\n }\n } else {\n const searchOffset = matches.current?.[1] ?? 0;\n for (const m of allMatches) {\n if (m[0] >= searchOffset) {\n nextMatch = m;\n break;\n }\n }\n }\n }\n if (nextMatch !== undefined) {\n updateCurrentMatch(nextMatch);\n scrollToMatch(nextMatch, retainFocus);\n }\n matches.current = nextMatch;\n };\n\n const close = () => {\n this.cleanup();\n onClose();\n };\n\n // Builds an always-visible icon button that toggles one boolean search\n // option (case/whole-word/regex). The button reflects its on/off state via\n // the `data-active` attribute so the stylesheet can highlight it.\n const makeToggle = (\n icon: SVGSpriteNames,\n title: string,\n key: 'caseSensitive' | 'wholeWord' | 'regex'\n ) => {\n const button = h('div', {\n dataset: { icon, active: String(searchParams[key]) },\n title,\n innerHTML: getEditorIconSvg(icon),\n onclick: () => {\n const next = !searchParams[key];\n button.dataset.active = String(next);\n updateSearchParam(key, next);\n },\n });\n return button;\n };\n\n const caseSensitiveToggle = makeToggle(\n 'case',\n 'Match Case',\n 'caseSensitive'\n );\n const wholeWordToggle = makeToggle('whole-word', 'Whole Word', 'wholeWord');\n const regexToggle = makeToggle('regex', 'Regexp', 'regex');\n\n this.#inputElement = h('input', {\n type: 'text',\n placeholder: 'Search',\n dataset: 'search',\n value: defaultQuery,\n oninput: (e: Event) => {\n searchParams.text = (e.target as HTMLInputElement).value;\n matches.current = undefined;\n updateMatches();\n },\n onkeydown: (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n close();\n } else if (e.key === 'Enter') {\n e.preventDefault();\n findNextMatch(false, true);\n } else if (e.key === 'f' && isPrimaryModifier(e)) {\n // prevent the default browser search panel open behavior\n e.preventDefault();\n }\n },\n });\n\n this.#container = h('div', {\n dataset: 'searchPanel',\n children: [\n h('div', {\n dataset: 'searchPanelRow',\n children: [\n h('div', {\n dataset: { icon: 'search' },\n innerHTML: getEditorIconSvg('search'),\n }),\n this.#inputElement,\n matchResultElement,\n caseSensitiveToggle,\n wholeWordToggle,\n regexToggle,\n h('div', { dataset: 'divider' }),\n h('div', {\n dataset: { icon: 'arrow-up', disabled: 'true' },\n title: 'Previous',\n innerHTML: getEditorIconSvg('arrow-up'),\n onclick: () => {\n findNextMatch(true);\n },\n }),\n h('div', {\n dataset: { icon: 'arrow-down', disabled: 'true' },\n title: 'Next',\n innerHTML: getEditorIconSvg('arrow-down'),\n onclick: () => {\n findNextMatch();\n },\n }),\n h('div', {\n dataset: { icon: 'close' },\n title: 'Close',\n innerHTML: getEditorIconSvg('close'),\n onclick: close,\n }),\n ],\n }),\n ],\n });\n\n matches.current = initialMatch;\n containerElement.before(this.#container);\n\n requestAnimationFrame(() => {\n if (initialMatch !== undefined) {\n updateMatches();\n } else {\n onUpdate([]);\n }\n this.#inputElement.select();\n });\n }\n\n focus(): void {\n this.#inputElement.focus();\n }\n\n cleanup(): void {\n this.#container.remove();\n }\n}\n"],"mappings":";;;;;AAyBA,IAAa,oBAAb,MAA+B;CAC7B;CACA;CAEA,YAAY,SAA6B;EACvC,MAAM,EACJ,cACA,kBACA,cACA,cACA,eACA,UACA,YACE;EAEJ,MAAMA,eAA6B;GACjC,MAAM;GACN,aAAa;GACb,eAAe;GACf,WAAW;GACX,OAAO;GACR;EAED,MAAM,UAAU;GACd,KAAK,EAAE;GACP,SAAS;GACV;EAED,MAAM,qBAAqB,EAAE,OAAO,EAAE,SAAS,WAAW,CAAC;EAC3D,MAAM,sBAAsB;AAC1B,WAAQ,MACN,aAAa,SAAS,KAAK,aAAa,OAAO,aAAa,GAAG,EAAE;AACnE,SAAKC,UACF,iBAA8B,kBAAkB,CAChD,SAAS,YAAY;AACpB,YAAQ,QAAQ,WAAW,OAAO,QAAQ,IAAI,WAAW,EAAE;KAC3D;AAEJ,OAAI,aAAa,SAAS,IAAI;AAC5B,uBAAmB,cAAc;AACjC,WAAO,mBAAmB,QAAQ;AAClC;;AAGF,OAAI,QAAQ,IAAI,WAAW,GAAG;AAC5B,uBAAmB,cAAc;AACjC,uBAAmB,QAAQ,YAAY;UAClC;AACL,WAAO,mBAAmB,QAAQ;AAClC,uBAAmB,SAAS,QAAQ,IAAI,CAAC;AACzC;;AAGF,WAAQ,UAAU;AAClB,YAAS,EAAE,CAAC;;EAGd,MAAM,sBAAsB,iBAAyC;AACnE,OAAI,iBAAiB,OACnB,oBAAmB,cAAc,GAAG,QAAQ,IAAI,OAAO;QAClD;IACL,MAAM,CAAC,OAAO,OAAO;AAIrB,uBAAmB,cAAc,GAHnB,QAAQ,IAAI,WACvB,MAAM,EAAE,OAAO,SAAS,EAAE,OAAO,IACnC,GAC2C,EAAE,MAAM,QAAQ,IAAI;;AAElE,WAAQ,UAAU;;EAGpB,MAAM,qBACJ,KACA,UACG;AACH,gBAAa,OAAO;AACpB,kBAAe;;EAGjB,MAAM,iBACJ,eAAwB,OACxB,cAAuB,UACpB;GACH,MAAM,aAAa,QAAQ;GAC3B,IAAIC,YAAoC,WAAW;AACnD,OAAI,WAAW,SAAS,EACtB,KAAI,cAAc;IAChB,MAAM,eAAe,QAAQ,UAAU,MAAM;AAC7C,gBAAY,WAAW,GAAG,GAAG;AAC7B,SAAK,MAAM,KAAK,WACd,KAAI,EAAE,MAAM,aACV,aAAY;QAEZ;UAGC;IACL,MAAM,eAAe,QAAQ,UAAU,MAAM;AAC7C,SAAK,MAAM,KAAK,WACd,KAAI,EAAE,MAAM,cAAc;AACxB,iBAAY;AACZ;;;AAKR,OAAI,cAAc,QAAW;AAC3B,uBAAmB,UAAU;AAC7B,kBAAc,WAAW,YAAY;;AAEvC,WAAQ,UAAU;;EAGpB,MAAM,cAAc;AAClB,QAAK,SAAS;AACd,YAAS;;EAMX,MAAM,cACJ,MACA,OACA,QACG;GACH,MAAM,SAAS,EAAE,OAAO;IACtB,SAAS;KAAE;KAAM,QAAQ,OAAO,aAAa,KAAK;KAAE;IACpD;IACA,WAAW,iBAAiB,KAAK;IACjC,eAAe;KACb,MAAM,OAAO,CAAC,aAAa;AAC3B,YAAO,QAAQ,SAAS,OAAO,KAAK;AACpC,uBAAkB,KAAK,KAAK;;IAE/B,CAAC;AACF,UAAO;;EAGT,MAAM,sBAAsB,WAC1B,QACA,cACA,gBACD;EACD,MAAM,kBAAkB,WAAW,cAAc,cAAc,YAAY;EAC3E,MAAM,cAAc,WAAW,SAAS,UAAU,QAAQ;AAE1D,QAAKC,eAAgB,EAAE,SAAS;GAC9B,MAAM;GACN,aAAa;GACb,SAAS;GACT,OAAO;GACP,UAAU,MAAa;AACrB,iBAAa,OAAQ,EAAE,OAA4B;AACnD,YAAQ,UAAU;AAClB,mBAAe;;GAEjB,YAAY,MAAqB;AAC/B,QAAI,EAAE,QAAQ,UAAU;AACtB,OAAE,gBAAgB;AAClB,YAAO;eACE,EAAE,QAAQ,SAAS;AAC5B,OAAE,gBAAgB;AAClB,mBAAc,OAAO,KAAK;eACjB,EAAE,QAAQ,OAAO,kBAAkB,EAAE,CAE9C,GAAE,gBAAgB;;GAGvB,CAAC;AAEF,QAAKF,YAAa,EAAE,OAAO;GACzB,SAAS;GACT,UAAU,CACR,EAAE,OAAO;IACP,SAAS;IACT,UAAU;KACR,EAAE,OAAO;MACP,SAAS,EAAE,MAAM,UAAU;MAC3B,WAAW,iBAAiB,SAAS;MACtC,CAAC;KACF,MAAKE;KACL;KACA;KACA;KACA;KACA,EAAE,OAAO,EAAE,SAAS,WAAW,CAAC;KAChC,EAAE,OAAO;MACP,SAAS;OAAE,MAAM;OAAY,UAAU;OAAQ;MAC/C,OAAO;MACP,WAAW,iBAAiB,WAAW;MACvC,eAAe;AACb,qBAAc,KAAK;;MAEtB,CAAC;KACF,EAAE,OAAO;MACP,SAAS;OAAE,MAAM;OAAc,UAAU;OAAQ;MACjD,OAAO;MACP,WAAW,iBAAiB,aAAa;MACzC,eAAe;AACb,sBAAe;;MAElB,CAAC;KACF,EAAE,OAAO;MACP,SAAS,EAAE,MAAM,SAAS;MAC1B,OAAO;MACP,WAAW,iBAAiB,QAAQ;MACpC,SAAS;MACV,CAAC;KACH;IACF,CAAC,CACH;GACF,CAAC;AAEF,UAAQ,UAAU;AAClB,mBAAiB,OAAO,MAAKF,UAAW;AAExC,8BAA4B;AAC1B,OAAI,iBAAiB,OACnB,gBAAe;OAEf,UAAS,EAAE,CAAC;AAEd,SAAKE,aAAc,QAAQ;IAC3B;;CAGJ,QAAc;AACZ,QAAKA,aAAc,OAAO;;CAG5B,UAAgB;AACd,QAAKF,UAAW,QAAQ"}
@@ -883,11 +883,17 @@ function getLineChildEnd(child, textOffsetInChild) {
883
883
  }
884
884
  function getLineIndex(el) {
885
885
  const { line } = el.dataset;
886
- if (line !== void 0) return parseInt(line) - 1;
886
+ if (line !== void 0) {
887
+ const lineNumber = parseInt(line, 10);
888
+ if (!Number.isNaN(lineNumber)) return lineNumber - 1;
889
+ }
887
890
  }
888
891
  function getCharacterIndex(el) {
889
892
  const { char } = el.dataset;
890
- return char !== void 0 ? parseInt(char) : void 0;
893
+ if (char !== void 0) {
894
+ const charIndex = parseInt(char, 10);
895
+ if (!Number.isNaN(charIndex)) return charIndex;
896
+ }
891
897
  }
892
898
  function getTextOffset(text, offset) {
893
899
  const value = text ?? "";