@adecore/editor-react 0.22.0 → 0.23.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 (196) hide show
  1. package/dist/CallHierarchyPanel.d.ts +6 -0
  2. package/dist/CallHierarchyPanel.js +88 -0
  3. package/dist/CompletionPopup.js +11 -3
  4. package/dist/DiffView.d.ts +22 -0
  5. package/dist/DiffView.js +203 -0
  6. package/dist/EditorContextMenu.js +1 -0
  7. package/dist/EditorView.js +18 -1
  8. package/dist/FindReplace.d.ts +1 -1
  9. package/dist/FindReplace.js +41 -11
  10. package/dist/HoverCard.js +2 -2
  11. package/dist/LanguagePopups.js +4 -1
  12. package/dist/Markdown.d.ts +2 -1
  13. package/dist/Markdown.js +8 -4
  14. package/dist/PeekPanel.d.ts +3 -0
  15. package/dist/PeekPanel.js +2 -3
  16. package/dist/PickPopup.js +15 -2
  17. package/dist/RenameCard.js +10 -5
  18. package/dist/RenderedDocs.d.ts +9 -0
  19. package/dist/RenderedDocs.js +84 -0
  20. package/dist/SplitDiffView.d.ts +9 -0
  21. package/dist/SplitDiffView.js +276 -0
  22. package/dist/SymbolPicker.d.ts +3 -0
  23. package/dist/SymbolPicker.js +4 -3
  24. package/dist/call-hierarchy-model.d.ts +52 -0
  25. package/dist/call-hierarchy-model.js +99 -0
  26. package/dist/call-hierarchy.d.ts +42 -0
  27. package/dist/call-hierarchy.js +399 -0
  28. package/dist/code-actions-model.d.ts +2 -0
  29. package/dist/code-actions-model.js +12 -10
  30. package/dist/code-actions.js +3 -1
  31. package/dist/completion-model.d.ts +14 -4
  32. package/dist/completion-model.js +40 -33
  33. package/dist/completion.d.ts +4 -0
  34. package/dist/completion.js +257 -42
  35. package/dist/diagnostics.js +8 -6
  36. package/dist/diff-expansion.d.ts +9 -0
  37. package/dist/diff-expansion.js +41 -0
  38. package/dist/diff-overlays.d.ts +15 -0
  39. package/dist/diff-overlays.js +28 -0
  40. package/dist/diff-syntax.d.ts +16 -0
  41. package/dist/diff-syntax.js +49 -0
  42. package/dist/diff-wordmarks.d.ts +5 -0
  43. package/dist/diff-wordmarks.js +26 -0
  44. package/dist/doc-comment-model.d.ts +23 -0
  45. package/dist/doc-comment-model.js +291 -0
  46. package/dist/editor-language.d.ts +13 -1
  47. package/dist/editor-language.js +14 -4
  48. package/dist/editor-react.css +79 -0
  49. package/dist/format-on-type.d.ts +14 -0
  50. package/dist/format-on-type.js +96 -0
  51. package/dist/host-types.d.ts +17 -0
  52. package/dist/hover.d.ts +4 -0
  53. package/dist/hover.js +15 -2
  54. package/dist/index.d.ts +19 -5
  55. package/dist/index.js +9 -1
  56. package/dist/inlay-hints.d.ts +47 -2
  57. package/dist/inlay-hints.js +353 -16
  58. package/dist/language-ids.js +4 -2
  59. package/dist/locales/en.json +114 -3
  60. package/dist/locales/nl.json +114 -3
  61. package/dist/models.d.ts +6 -2
  62. package/dist/models.js +5 -1
  63. package/dist/patch-decorations.d.ts +27 -0
  64. package/dist/patch-decorations.js +182 -0
  65. package/dist/patch-layout.d.ts +40 -0
  66. package/dist/patch-layout.js +304 -0
  67. package/dist/peek.js +1 -0
  68. package/dist/pick.d.ts +1 -0
  69. package/dist/pick.js +11 -1
  70. package/dist/popups.d.ts +17 -0
  71. package/dist/popups.js +1 -0
  72. package/dist/project-language.d.ts +8 -0
  73. package/dist/project-language.js +34 -2
  74. package/dist/refresher.d.ts +1 -0
  75. package/dist/refresher.js +5 -0
  76. package/dist/rename-model.d.ts +1 -1
  77. package/dist/rename-model.js +3 -7
  78. package/dist/rename.js +5 -8
  79. package/dist/rendered-docs.d.ts +62 -0
  80. package/dist/rendered-docs.js +368 -0
  81. package/dist/row-host.d.ts +2 -0
  82. package/dist/row-host.js +11 -3
  83. package/dist/semantic-delta.d.ts +2 -0
  84. package/dist/semantic-delta.js +27 -0
  85. package/dist/semantic-model.js +16 -0
  86. package/dist/semantic-tokens.d.ts +26 -1
  87. package/dist/semantic-tokens.js +174 -14
  88. package/dist/shortcut-keys.d.ts +2 -0
  89. package/dist/shortcut-keys.js +6 -2
  90. package/dist/signature.js +1 -1
  91. package/dist/snippet-field-transforms.d.ts +30 -0
  92. package/dist/snippet-field-transforms.js +116 -0
  93. package/dist/snippet-indent.d.ts +2 -0
  94. package/dist/snippet-indent.js +49 -0
  95. package/dist/snippet-range-order.d.ts +3 -0
  96. package/dist/snippet-range-order.js +19 -0
  97. package/dist/snippet-session.d.ts +42 -6
  98. package/dist/snippet-session.js +650 -53
  99. package/dist/snippet-transform-client.d.ts +19 -0
  100. package/dist/snippet-transform-client.js +66 -0
  101. package/dist/snippet-transform-format.d.ts +15 -0
  102. package/dist/snippet-transform-format.js +302 -0
  103. package/dist/snippet-transform-protocol.d.ts +45 -0
  104. package/dist/snippet-transform-protocol.js +8 -0
  105. package/dist/snippet-transform-queue.d.ts +32 -0
  106. package/dist/snippet-transform-queue.js +173 -0
  107. package/dist/snippet-transform-worker.d.ts +10 -0
  108. package/dist/snippet-transform-worker.js +41 -0
  109. package/dist/snippet-variable-names.d.ts +1 -0
  110. package/dist/snippet-variable-names.js +36 -0
  111. package/dist/snippet-variables.d.ts +7 -0
  112. package/dist/snippet-variables.js +66 -0
  113. package/dist/snippet.d.ts +30 -3
  114. package/dist/snippet.js +402 -87
  115. package/dist/split-alignment.d.ts +6 -0
  116. package/dist/split-alignment.js +38 -0
  117. package/dist/split-scroll.d.ts +14 -0
  118. package/dist/split-scroll.js +45 -0
  119. package/dist/symbols.d.ts +2 -1
  120. package/dist/symbols.js +39 -7
  121. package/dist/testing.d.ts +5 -1
  122. package/dist/testing.js +12 -0
  123. package/dist/workspace-edit.d.ts +25 -3
  124. package/dist/workspace-edit.js +282 -143
  125. package/package.json +10 -5
  126. package/src/CallHierarchyPanel.tsx +238 -0
  127. package/src/CompletionPopup.tsx +12 -1
  128. package/src/DiffView.tsx +262 -0
  129. package/src/EditorContextMenu.tsx +1 -0
  130. package/src/EditorView.tsx +21 -2
  131. package/src/FindReplace.tsx +51 -13
  132. package/src/HoverCard.tsx +23 -21
  133. package/src/LanguagePopups.tsx +5 -0
  134. package/src/Markdown.tsx +10 -3
  135. package/src/PeekPanel.tsx +2 -3
  136. package/src/PickPopup.tsx +18 -2
  137. package/src/RenameCard.tsx +18 -0
  138. package/src/RenderedDocs.tsx +155 -0
  139. package/src/SplitDiffView.tsx +337 -0
  140. package/src/SymbolPicker.tsx +4 -3
  141. package/src/call-hierarchy-model.ts +158 -0
  142. package/src/call-hierarchy.ts +441 -0
  143. package/src/code-actions-model.ts +17 -11
  144. package/src/code-actions.ts +3 -1
  145. package/src/completion-model.ts +61 -35
  146. package/src/completion.ts +302 -42
  147. package/src/diagnostics.ts +8 -6
  148. package/src/diff-expansion.ts +49 -0
  149. package/src/diff-overlays.tsx +69 -0
  150. package/src/diff-syntax.ts +67 -0
  151. package/src/diff-wordmarks.ts +31 -0
  152. package/src/doc-comment-model.ts +336 -0
  153. package/src/editor-language.ts +21 -5
  154. package/src/editor-react.css +79 -0
  155. package/src/format-on-type.ts +111 -0
  156. package/src/host-types.ts +19 -0
  157. package/src/hover.ts +18 -3
  158. package/src/index.ts +27 -4
  159. package/src/inlay-hints.ts +394 -18
  160. package/src/language-ids.ts +4 -2
  161. package/src/locales/en.json +114 -3
  162. package/src/locales/nl.json +114 -3
  163. package/src/models.ts +40 -1
  164. package/src/patch-decorations.ts +224 -0
  165. package/src/patch-layout.ts +388 -0
  166. package/src/peek.ts +1 -0
  167. package/src/pick.ts +12 -1
  168. package/src/popups.ts +24 -0
  169. package/src/project-language.ts +43 -2
  170. package/src/refresher.ts +6 -0
  171. package/src/rename-model.ts +4 -8
  172. package/src/rename.ts +5 -8
  173. package/src/rendered-docs.ts +422 -0
  174. package/src/row-host.ts +12 -3
  175. package/src/semantic-delta.ts +29 -0
  176. package/src/semantic-model.ts +17 -0
  177. package/src/semantic-tokens.ts +202 -19
  178. package/src/shortcut-keys.ts +6 -2
  179. package/src/signature.ts +1 -1
  180. package/src/snippet-field-transforms.ts +144 -0
  181. package/src/snippet-indent.ts +53 -0
  182. package/src/snippet-range-order.ts +19 -0
  183. package/src/snippet-session.ts +797 -56
  184. package/src/snippet-transform-client.ts +84 -0
  185. package/src/snippet-transform-format.ts +325 -0
  186. package/src/snippet-transform-protocol.ts +24 -0
  187. package/src/snippet-transform-queue.ts +199 -0
  188. package/src/snippet-transform-worker.ts +58 -0
  189. package/src/snippet-variable-names.ts +36 -0
  190. package/src/snippet-variables.ts +74 -0
  191. package/src/snippet.ts +452 -89
  192. package/src/split-alignment.ts +45 -0
  193. package/src/split-scroll.ts +54 -0
  194. package/src/symbols.ts +43 -7
  195. package/src/testing.ts +25 -0
  196. package/src/workspace-edit.ts +347 -169
@@ -0,0 +1,40 @@
1
+ export type PatchLineRole = 'file-header' | 'hunk-header' | 'context' | 'added' | 'removed' | 'no-newline' | 'binary' | 'other';
2
+ export type PatchFileStatus = 'added' | 'deleted' | 'renamed' | 'copied' | 'modified' | 'binary';
3
+ export interface PatchLayoutLine {
4
+ readonly role: PatchLineRole;
5
+ readonly old: number | null;
6
+ readonly new: number | null;
7
+ }
8
+ export interface PatchHunkLayout {
9
+ readonly line: number;
10
+ readonly oldStart: number;
11
+ readonly oldCount: number;
12
+ readonly newStart: number;
13
+ readonly newCount: number;
14
+ readonly section: string;
15
+ readonly consistent: boolean;
16
+ }
17
+ export interface PatchFileLayout {
18
+ readonly oldPath: string | null;
19
+ readonly newPath: string | null;
20
+ readonly status: PatchFileStatus;
21
+ readonly firstLine: number;
22
+ readonly lastLine: number;
23
+ readonly added: number;
24
+ readonly removed: number;
25
+ readonly hunks: readonly PatchHunkLayout[];
26
+ }
27
+ export interface PatchLayout {
28
+ readonly lines: readonly PatchLayoutLine[];
29
+ readonly files: readonly PatchFileLayout[];
30
+ readonly truncated: boolean;
31
+ }
32
+ export interface PatchLimits {
33
+ readonly maxLines?: number;
34
+ readonly maxCharacters?: number;
35
+ }
36
+ export declare const DEFAULT_PATCH_LIMITS: {
37
+ readonly maxLines: 50000;
38
+ readonly maxCharacters: 4000000;
39
+ };
40
+ export declare function patchLayoutOf(text: string, limits?: PatchLimits): PatchLayout;
@@ -0,0 +1,304 @@
1
+ import { parseHunkHeader } from '@adecore/merge';
2
+ export const DEFAULT_PATCH_LIMITS = { maxLines: 50_000, maxCharacters: 4_000_000 };
3
+ const OTHER = { role: 'other', old: null, new: null };
4
+ const FILE_HEADER = { role: 'file-header', old: null, new: null };
5
+ const HUNK_HEADER = { role: 'hunk-header', old: null, new: null };
6
+ const NO_NEWLINE = { role: 'no-newline', old: null, new: null };
7
+ const BINARY = { role: 'binary', old: null, new: null };
8
+ const GIT_HEADERS = /^(?:index |old mode |new mode |deleted file mode |new file mode |similarity index |dissimilarity index |rename from |rename to |copy from |copy to )/;
9
+ const ESCAPES = { a: 7, b: 8, f: 12, n: 10, r: 13, t: 9, v: 11, '"': 34, '\\': 92 };
10
+ /* Reads a path as git writes it: bare, or in quotes with C escapes and octal bytes. */
11
+ function unquote(raw) {
12
+ if (raw.length < 2 || !raw.startsWith('"') || !raw.endsWith('"')) {
13
+ return raw;
14
+ }
15
+ const bytes = [];
16
+ const encoder = new TextEncoder();
17
+ const body = raw.slice(1, -1);
18
+ for (let i = 0; i < body.length; i++) {
19
+ if (body[i] !== '\\') {
20
+ const point = String.fromCodePoint(body.codePointAt(i));
21
+ bytes.push(...encoder.encode(point));
22
+ i += point.length - 1;
23
+ continue;
24
+ }
25
+ const octal = /^[0-3][0-7]{2}/.exec(body.slice(i + 1, i + 4));
26
+ const escaped = ESCAPES[body[i + 1] ?? ''];
27
+ if (octal !== null) {
28
+ bytes.push(Number.parseInt(octal[0], 8));
29
+ i += 3;
30
+ }
31
+ else if (escaped !== undefined) {
32
+ bytes.push(escaped);
33
+ i++;
34
+ }
35
+ else {
36
+ bytes.push(92);
37
+ }
38
+ }
39
+ return new TextDecoder().decode(new Uint8Array(bytes));
40
+ }
41
+ /* The name on a `---`, `+++`, `rename` or `copy` line: a timestamp after a tab goes, and `/dev/null` is no file. */
42
+ function pathOf(raw) {
43
+ const tab = raw.indexOf('\t');
44
+ const name = unquote(tab === -1 ? raw : raw.slice(0, tab));
45
+ return name === '/dev/null' ? null : name;
46
+ }
47
+ function stripPrefix(path, prefix) {
48
+ return path !== null && path.startsWith(prefix) ? path.slice(2) : path;
49
+ }
50
+ /* The two names of a `diff --git a/x b/y` line; with spaces in an unquoted name, the split that gives both halves the same name wins. */
51
+ function gitPaths(rest) {
52
+ if (rest.startsWith('"')) {
53
+ const end = rest.indexOf('" ', 1);
54
+ if (end !== -1) {
55
+ const oldName = unquote(rest.slice(0, end + 1));
56
+ const newName = unquote(rest.slice(end + 2));
57
+ const prefixed = oldName.startsWith('a/') && newName.startsWith('b/');
58
+ return prefixed ? { oldPath: oldName.slice(2), newPath: newName.slice(2), prefixed } : { oldPath: oldName, newPath: newName, prefixed };
59
+ }
60
+ }
61
+ const middle = rest.indexOf(' b/');
62
+ if (rest.startsWith('a/') && middle !== -1) {
63
+ for (let at = middle; at !== -1; at = rest.indexOf(' b/', at + 1)) {
64
+ if (rest.slice(2, at) === rest.slice(at + 3)) {
65
+ return { oldPath: rest.slice(2, at), newPath: rest.slice(at + 3), prefixed: true };
66
+ }
67
+ }
68
+ return { oldPath: rest.slice(2, middle), newPath: unquote(rest.slice(middle + 3)), prefixed: true };
69
+ }
70
+ const split = rest.indexOf(' ');
71
+ return split === -1
72
+ ? { oldPath: unquote(rest), newPath: null, prefixed: false }
73
+ : { oldPath: unquote(rest.slice(0, split)), newPath: unquote(rest.slice(split + 1)), prefixed: false };
74
+ }
75
+ function emptySection(firstLine, git) {
76
+ return {
77
+ oldPath: null,
78
+ newPath: null,
79
+ git,
80
+ created: false,
81
+ deleted: false,
82
+ renamed: false,
83
+ copied: false,
84
+ binary: false,
85
+ firstLine,
86
+ lastLine: firstLine,
87
+ added: 0,
88
+ removed: 0,
89
+ hunks: [],
90
+ named: false,
91
+ prefixed: false
92
+ };
93
+ }
94
+ function statusOf(section) {
95
+ if (section.binary) {
96
+ return 'binary';
97
+ }
98
+ if (section.created || (!section.git && section.oldPath === null && section.newPath !== null)) {
99
+ return 'added';
100
+ }
101
+ if (section.deleted || (!section.git && section.newPath === null && section.oldPath !== null)) {
102
+ return 'deleted';
103
+ }
104
+ if (section.renamed) {
105
+ return 'renamed';
106
+ }
107
+ return section.copied ? 'copied' : 'modified';
108
+ }
109
+ function linesOf(text, limits) {
110
+ const lines = [];
111
+ let start = 0;
112
+ while (lines.length < limits.maxLines && start <= limits.maxCharacters) {
113
+ const end = text.indexOf('\n', start);
114
+ if (end === -1) {
115
+ lines.push(text.slice(start).replace(/\r$/, ''));
116
+ return { lines, truncated: false };
117
+ }
118
+ lines.push(text.slice(start, end).replace(/\r$/, ''));
119
+ start = end + 1;
120
+ }
121
+ // Stopping right after a final newline leaves nothing unread.
122
+ return { lines, truncated: start < text.length };
123
+ }
124
+ /*
125
+ * What every line of a patch is, with the file numbers of its lines and the files it touches. It reads
126
+ * git patches and plain `diff -u` output, tolerates a text that was edited so its hunks no longer add up
127
+ * (the hunk is marked inconsistent and the reading goes on) and never throws. Past the limits it stops
128
+ * and says so in `truncated`.
129
+ */
130
+ export function patchLayoutOf(text, limits = {}) {
131
+ const { lines: source, truncated } = linesOf(text, { ...DEFAULT_PATCH_LIMITS, ...limits });
132
+ const lines = [];
133
+ const sections = [];
134
+ let section;
135
+ let hunk;
136
+ let binaryData = false;
137
+ const seal = () => {
138
+ if (hunk !== undefined && (hunk.oldLeft > 0 || hunk.newLeft > 0)) {
139
+ hunk.layout.consistent = false;
140
+ }
141
+ hunk = undefined;
142
+ };
143
+ const open = (line, git) => {
144
+ seal();
145
+ binaryData = false;
146
+ section = emptySection(line, git);
147
+ sections.push(section);
148
+ return section;
149
+ };
150
+ // A removed line can read `-- x`, so `---` starts a file only with `+++` under it.
151
+ const startsFile = (index) => source[index].startsWith('--- ') && (source[index + 1]?.startsWith('+++ ') ?? false);
152
+ const push = (line, index) => {
153
+ lines.push(line);
154
+ if (section !== undefined) {
155
+ section.lastLine = index;
156
+ }
157
+ };
158
+ for (let index = 0; index < source.length; index++) {
159
+ const line = source[index];
160
+ const remaining = hunk !== undefined && (hunk.oldLeft > 0 || hunk.newLeft > 0);
161
+ const gitStart = line.startsWith('diff --git ');
162
+ const header = line.startsWith('@@') ? parseHunkHeader(line) : undefined;
163
+ const fileStart = startsFile(index) && (!remaining || source[index + 2]?.startsWith('@@') === true);
164
+ if (remaining && !gitStart && header === undefined && !fileStart) {
165
+ if (isContent(line) || line === '' || line.startsWith('\\')) {
166
+ push(contentLine(line, hunk, section), index);
167
+ }
168
+ else {
169
+ hunk.layout.consistent = false;
170
+ push(OTHER, index);
171
+ }
172
+ continue;
173
+ }
174
+ if (gitStart) {
175
+ const created = open(index, true);
176
+ ({ oldPath: created.oldPath, newPath: created.newPath, prefixed: created.prefixed } = gitPaths(line.slice('diff --git '.length)));
177
+ push(FILE_HEADER, index);
178
+ continue;
179
+ }
180
+ if (fileStart) {
181
+ const reuse = section !== undefined && section.git && !section.named && section.hunks.length === 0;
182
+ const target = reuse ? section : open(index, false);
183
+ seal();
184
+ nameSides(target, line, source[index + 1]);
185
+ push(FILE_HEADER, index);
186
+ push(FILE_HEADER, index + 1);
187
+ index++;
188
+ continue;
189
+ }
190
+ if (header !== undefined) {
191
+ seal();
192
+ const target = section ?? open(index, false);
193
+ const layout = { line: index, ...header, consistent: true };
194
+ target.hunks.push(layout);
195
+ hunk = { layout, oldLeft: header.oldCount, newLeft: header.newCount, oldNumber: header.oldStart, newNumber: header.newStart };
196
+ push(HUNK_HEADER, index);
197
+ continue;
198
+ }
199
+ if (section === undefined) {
200
+ push(OTHER, index);
201
+ }
202
+ else if (hunk !== undefined && line.startsWith('\\')) {
203
+ push(NO_NEWLINE, index);
204
+ }
205
+ else if (hunk !== undefined && isContent(line) && line !== '-- ') {
206
+ // The counts ran out, so a line a person typed below the hunk extends it. The `-- ` of a mail signature does not.
207
+ hunk.layout.consistent = false;
208
+ push(contentLine(line, hunk, section), index);
209
+ }
210
+ else if (hunk === undefined && section.git && GIT_HEADERS.test(line)) {
211
+ readGitHeader(section, line);
212
+ push(FILE_HEADER, index);
213
+ }
214
+ else if (/^Binary files .* differ$/.test(line) || line === 'GIT binary patch') {
215
+ section.binary = true;
216
+ binaryData = line === 'GIT binary patch';
217
+ push(BINARY, index);
218
+ }
219
+ else if (binaryData && line !== '') {
220
+ push(BINARY, index);
221
+ }
222
+ else if (line === '') {
223
+ lines.push(OTHER);
224
+ }
225
+ else {
226
+ push(OTHER, index);
227
+ }
228
+ }
229
+ seal();
230
+ return {
231
+ lines,
232
+ truncated,
233
+ files: sections.map((entry) => ({
234
+ oldPath: entry.oldPath,
235
+ newPath: entry.newPath,
236
+ status: statusOf(entry),
237
+ firstLine: entry.firstLine,
238
+ lastLine: entry.lastLine,
239
+ added: entry.added,
240
+ removed: entry.removed,
241
+ hunks: entry.hunks
242
+ }))
243
+ };
244
+ }
245
+ function isContent(line) {
246
+ return line.startsWith('+') || line.startsWith('-') || line.startsWith(' ');
247
+ }
248
+ /* A line under a hunk header; an empty line is a context line whose space an editor trimmed. */
249
+ function contentLine(line, hunk, section) {
250
+ if (line.startsWith('\\')) {
251
+ return NO_NEWLINE;
252
+ }
253
+ const kind = line.startsWith('+') ? 'added' : line.startsWith('-') ? 'removed' : 'context';
254
+ const takesOld = kind !== 'added';
255
+ const takesNew = kind !== 'removed';
256
+ if ((takesOld && hunk.oldLeft === 0) || (takesNew && hunk.newLeft === 0)) {
257
+ hunk.layout.consistent = false;
258
+ }
259
+ if (kind === 'added') {
260
+ section.added++;
261
+ }
262
+ else if (kind === 'removed') {
263
+ section.removed++;
264
+ }
265
+ const oldNumber = takesOld ? hunk.oldNumber++ : null;
266
+ const newNumber = takesNew ? hunk.newNumber++ : null;
267
+ hunk.oldLeft = takesOld ? Math.max(0, hunk.oldLeft - 1) : hunk.oldLeft;
268
+ hunk.newLeft = takesNew ? Math.max(0, hunk.newLeft - 1) : hunk.newLeft;
269
+ return { role: kind, old: oldNumber, new: newNumber };
270
+ }
271
+ function nameSides(section, oldLine, newLine) {
272
+ const oldName = pathOf(oldLine.slice(4));
273
+ const newName = pathOf(newLine.slice(4));
274
+ const prefixed = section.git ? section.prefixed : (oldName?.startsWith('a/') ?? false) && (newName?.startsWith('b/') ?? false);
275
+ section.oldPath = prefixed ? stripPrefix(oldName, 'a/') : oldName;
276
+ section.newPath = prefixed ? stripPrefix(newName, 'b/') : newName;
277
+ section.created ||= oldName === null;
278
+ section.deleted ||= newName === null;
279
+ section.named = true;
280
+ }
281
+ function readGitHeader(section, line) {
282
+ if (line.startsWith('new file mode ')) {
283
+ section.created = true;
284
+ }
285
+ else if (line.startsWith('deleted file mode ')) {
286
+ section.deleted = true;
287
+ }
288
+ else if (line.startsWith('rename from ')) {
289
+ section.renamed = true;
290
+ section.oldPath = pathOf(line.slice('rename from '.length));
291
+ }
292
+ else if (line.startsWith('rename to ')) {
293
+ section.renamed = true;
294
+ section.newPath = pathOf(line.slice('rename to '.length));
295
+ }
296
+ else if (line.startsWith('copy from ')) {
297
+ section.copied = true;
298
+ section.oldPath = pathOf(line.slice('copy from '.length));
299
+ }
300
+ else if (line.startsWith('copy to ')) {
301
+ section.copied = true;
302
+ section.newPath = pathOf(line.slice('copy to '.length));
303
+ }
304
+ }
package/dist/peek.js CHANGED
@@ -112,6 +112,7 @@ export class PeekFeature {
112
112
  ? files[0].places[0]
113
113
  : (files.flatMap((file) => file.places).find((place) => !(place.location.uri === uri && place.line === position.line)) ?? files[0].places[0]);
114
114
  this.line = position.line;
115
+ this.language.callHierarchy.close();
115
116
  this.language.popups.setState({ peek: { kind, container: null, files, active: first.id, collapsed: [], count: unique.length, preview: null } });
116
117
  this.select(first.id);
117
118
  // The editor draws a row once per id, so a peek opened over another needs an id of its own to get a container.
package/dist/pick.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface PickSpec {
8
8
  readonly groups: readonly PickGroup[];
9
9
  readonly active?: string;
10
10
  accept(id: string): void;
11
+ onTab?(backwards: boolean): void;
11
12
  attempt?(id: string): Promise<string | null>;
12
13
  preview?(id: string, signal: AbortSignal): Promise<PickPreview | null>;
13
14
  }
package/dist/pick.js CHANGED
@@ -125,9 +125,19 @@ export class PickFeature {
125
125
  this.activate(this.ids[this.ids.length - 1]);
126
126
  return true;
127
127
  case 'Enter':
128
- case 'Tab':
129
128
  this.choose(this.active);
130
129
  return true;
130
+ case 'Tab': {
131
+ const spec = this.spec;
132
+ if (spec.onTab !== undefined) {
133
+ this.close();
134
+ spec.onTab(event.shiftKey);
135
+ }
136
+ else {
137
+ this.choose(this.active);
138
+ }
139
+ return true;
140
+ }
131
141
  case 'Escape':
132
142
  this.close();
133
143
  return true;
package/dist/popups.d.ts CHANGED
@@ -4,10 +4,12 @@ import type { Location } from '@adecore/lsp';
4
4
  import type { CodeAuthorship } from './code-authors.ts';
5
5
  import type { Problem } from './diagnostics-model.ts';
6
6
  import type { PeekFile, PeekSnippet } from './peek-model.ts';
7
+ import type { CallDirection, CallRow } from './call-hierarchy-model.ts';
7
8
  import type { ActionEntry } from './code-actions-model.ts';
8
9
  import type { SymbolEntry } from './symbol-picker-model.ts';
9
10
  import type { HoverText } from './hover-content.ts';
10
11
  import type { SignatureViewModel } from './signature-model.ts';
12
+ import type { ResourceOperationView } from './workspace-edit.ts';
11
13
  export interface HoverView {
12
14
  readonly anchor: EditorPosition;
13
15
  readonly position: EditorPosition;
@@ -20,6 +22,7 @@ export interface HoverInfo {
20
22
  readonly definition: Location | null;
21
23
  readonly word: string;
22
24
  readonly references: number | null;
25
+ readonly plain?: boolean;
23
26
  }
24
27
  export interface CompletionRow {
25
28
  readonly label: string;
@@ -90,6 +93,7 @@ export interface RenameView {
90
93
  readonly error: string | null;
91
94
  readonly name: string;
92
95
  readonly files: readonly RenameFileView[];
96
+ readonly operations?: readonly ResourceOperationView[];
93
97
  readonly suggestions: readonly string[];
94
98
  }
95
99
  export interface PeekView {
@@ -103,6 +107,18 @@ export interface PeekView {
103
107
  readonly uri: string;
104
108
  }) | null;
105
109
  }
110
+ export interface CallHierarchyView {
111
+ readonly direction: CallDirection;
112
+ readonly container: HTMLElement | null;
113
+ readonly name: string;
114
+ readonly rows: readonly CallRow[];
115
+ readonly active: string;
116
+ readonly site: number;
117
+ readonly sites: number;
118
+ readonly preview: (PeekSnippet & {
119
+ readonly uri: string;
120
+ }) | null;
121
+ }
106
122
  export interface SymbolsView {
107
123
  readonly file: string;
108
124
  readonly entries: readonly SymbolEntry[];
@@ -125,6 +141,7 @@ export interface PopupState {
125
141
  readonly pick: PickView | null;
126
142
  readonly rename: RenameView | null;
127
143
  readonly peek: PeekView | null;
144
+ readonly callHierarchy?: CallHierarchyView | null;
128
145
  readonly symbols: SymbolsView | null;
129
146
  readonly menu: MenuView | null;
130
147
  readonly authors: AuthorsView | null;
package/dist/popups.js CHANGED
@@ -7,6 +7,7 @@ export function createPopupStore() {
7
7
  pick: null,
8
8
  rename: null,
9
9
  peek: null,
10
+ callHierarchy: null,
10
11
  symbols: null,
11
12
  menu: null,
12
13
  authors: null
@@ -10,6 +10,10 @@ export interface LanguageDocumentHandle {
10
10
  readonly ready: Promise<void>;
11
11
  release(): void;
12
12
  }
13
+ export type FileOperationTarget = string | {
14
+ readonly path: string;
15
+ readonly directory?: boolean;
16
+ };
13
17
  export declare class ProjectLanguage {
14
18
  readonly service: LanguageService;
15
19
  readonly host: LanguageHost;
@@ -25,6 +29,10 @@ export declare class ProjectLanguage {
25
29
  constructor(service: LanguageService, host?: LanguageHost);
26
30
  pathOfLocation(uri: string): string | null;
27
31
  applyWorkspaceEdit(edit: WorkspaceEdit): Promise<ApplyWorkspaceEditResult>;
32
+ willCreateFiles(targets: readonly FileOperationTarget[]): Promise<WorkspaceEdit | null>;
33
+ didCreateFiles(targets: readonly FileOperationTarget[]): Promise<void>;
34
+ willDeleteFiles(targets: readonly FileOperationTarget[]): Promise<WorkspaceEdit | null>;
35
+ didDeleteFiles(targets: readonly FileOperationTarget[]): Promise<void>;
28
36
  readText(uri: string): Promise<string | null>;
29
37
  openPlace(place: Place): void;
30
38
  noteCaret(place: Place): void;
@@ -1,5 +1,6 @@
1
+ import { disposeSnippetTransforms } from './snippet-transform-queue.js';
1
2
  import i18next, {} from 'i18next';
2
- import { fileUriToPath } from '@adecore/lsp';
3
+ import { fileUriToPath, pathToFileUri } from '@adecore/lsp';
3
4
  import { NavigationHistory } from './navigation-history.js';
4
5
  import { ProjectProblems } from './project-problems.js';
5
6
  import { applyWorkspaceEdit } from './workspace-edit.js';
@@ -51,6 +52,12 @@ class DocumentSync {
51
52
  void this.service.changeDocument(this.uri, changes).catch(() => undefined);
52
53
  }
53
54
  }
55
+ function filesOf(targets) {
56
+ return targets.map((target) => {
57
+ const { path, directory } = typeof target === 'string' ? { path: target, directory: false } : { path: target.path, directory: target.directory === true };
58
+ return { uri: pathToFileUri(path), directory };
59
+ });
60
+ }
54
61
  /*
55
62
  * A shared language service and document ownership for the editors of a project. Two editors on the same
56
63
  * file hold one document, and only the first one sends changes, since both would report the same edit.
@@ -84,7 +91,31 @@ export class ProjectLanguage {
84
91
  }
85
92
  /* Makes an edit of a language server: an open document takes it as one undo step and any other file gets an unsaved draft. */
86
93
  applyWorkspaceEdit(edit) {
87
- return applyWorkspaceEdit(edit, { editorOf: (uri) => this.holders.get(uri)?.editors[0], files: this.files });
94
+ const { service } = this;
95
+ return applyWorkspaceEdit(edit, {
96
+ editorOf: (uri) => this.holders.get(uri)?.editors[0],
97
+ files: this.files,
98
+ ...(service.versionOf === undefined ? {} : { versionOf: (uri) => service.versionOf(uri) })
99
+ });
100
+ }
101
+ /*
102
+ * What the servers want changed before files are made, for a host that makes them outside a language server's
103
+ * edit: apply the edit with `applyWorkspaceEdit`, then make the files. Null when no server has anything to say.
104
+ */
105
+ willCreateFiles(targets) {
106
+ return this.service.willCreateFiles?.(filesOf(targets)) ?? Promise.resolve(null);
107
+ }
108
+ /* Tells the servers that files were made, once they are. */
109
+ didCreateFiles(targets) {
110
+ return this.service.didCreateFiles?.(filesOf(targets)) ?? Promise.resolve();
111
+ }
112
+ /* What the servers want changed before files are deleted, such as the references to them; null when no server has anything to say. */
113
+ willDeleteFiles(targets) {
114
+ return this.service.willDeleteFiles?.(filesOf(targets)) ?? Promise.resolve(null);
115
+ }
116
+ /* Tells the servers that files were deleted, once they are. */
117
+ didDeleteFiles(targets) {
118
+ return this.service.didDeleteFiles?.(filesOf(targets)) ?? Promise.resolve();
88
119
  }
89
120
  /* The text of a file as it stands now: an open document's, else a draft's or the machine's; null when it is no text file. */
90
121
  async readText(uri) {
@@ -179,6 +210,7 @@ export class ProjectLanguage {
179
210
  return;
180
211
  }
181
212
  this.disposed = true;
213
+ disposeSnippetTransforms(this);
182
214
  for (const [uri, holder] of this.holders) {
183
215
  holder.sync.dispose();
184
216
  void holder.sync.ready
@@ -9,5 +9,6 @@ export declare class Refresher {
9
9
  constructor(run: (signal: AbortSignal) => Promise<void>, delay: number, timers?: Timers);
10
10
  later(): void;
11
11
  now(): void;
12
+ cancel(): void;
12
13
  dispose(): void;
13
14
  }
package/dist/refresher.js CHANGED
@@ -36,6 +36,11 @@ export class Refresher {
36
36
  }
37
37
  });
38
38
  }
39
+ /* Drops the wait and the ask in flight, and keeps the refresher usable. */
40
+ cancel() {
41
+ this.timers.clear(this.timer);
42
+ this.controller?.abort();
43
+ }
39
44
  dispose() {
40
45
  this.disposed = true;
41
46
  this.timers.clear(this.timer);
@@ -22,4 +22,4 @@ export interface RenameFile {
22
22
  readonly rows: readonly RenameRow[];
23
23
  }
24
24
  export declare function renameRowsOf(text: string, edits: readonly TextEdit[]): RenameRow[];
25
- export declare function renamePreviewOf(edit: WorkspaceEdit, textOf: (uri: string) => Promise<string | null>): Promise<RenameFile[] | null>;
25
+ export declare function renamePreviewOf(edit: WorkspaceEdit, textOf: (uri: string) => Promise<string | null>): Promise<RenameFile[]>;
@@ -1,5 +1,5 @@
1
1
  import { isIdentifierCharacter } from './completion-model.js';
2
- import { entriesOf } from './workspace-edit.js';
2
+ import { textEntriesOf } from './workspace-edit.js';
3
3
  /* The identifier the caret is in or touches; null where there is none. */
4
4
  export function wordRangeAt(lineText, position) {
5
5
  let start = Math.min(position.character, lineText.length);
@@ -78,14 +78,10 @@ export function renameRowsOf(text, edits) {
78
78
  return { line: line + 1, before, after };
79
79
  });
80
80
  }
81
- /* Resource renames are omitted; create/delete operations refuse the text-only preview with null. */
81
+ /* The lines of the files an edit edits; the files it makes, moves or deletes are listed by `resourceOperationsOf`. */
82
82
  export async function renamePreviewOf(edit, textOf) {
83
- const entries = entriesOf(edit);
84
- if (entries === null) {
85
- return null;
86
- }
87
83
  const files = [];
88
- for (const [uri, list] of entries) {
84
+ for (const [uri, list] of textEntriesOf(edit)) {
89
85
  const text = await textOf(uri);
90
86
  files.push({ uri, rows: text === null ? [] : renameRowsOf(text, list.flat()) });
91
87
  }
package/dist/rename.js CHANGED
@@ -2,6 +2,7 @@ import { StaleResultError } from '@adecore/lsp';
2
2
  import { lineTextOf } from './line-text.js';
3
3
  import { occurrencesOf, renamePreviewOf, renameTargetOf, wordRangeAt } from './rename-model.js';
4
4
  import { isShortcut } from './shortcut-keys.js';
5
+ import { resourceOperationsOf } from './workspace-edit.js';
5
6
  import { messageOf } from './error-message.js';
6
7
  const METHOD = 'textDocument/rename';
7
8
  const TOAST_ID = 'language-rename';
@@ -66,7 +67,7 @@ export class RenameFeature {
66
67
  }
67
68
  const session = { target, original: editor.textInRange(target.range), edit: null, names: new AbortController() };
68
69
  this.session = session;
69
- this.publish({ phase: 'input', busy: false, name: target.placeholder, files: [], occurrences: null, suggestions: [] });
70
+ this.publish({ phase: 'input', busy: false, name: target.placeholder, files: [], operations: [], occurrences: null, suggestions: [] });
70
71
  void this.light(session).then((locations) => this.suggest(session, locations));
71
72
  }
72
73
  /* The name was typed: Enter applies it, Shift+Enter lists what it would change first. */
@@ -100,12 +101,7 @@ export class RenameFeature {
100
101
  if (this.session !== session) {
101
102
  return;
102
103
  }
103
- if (files === null) {
104
- // An edit that renames files too has no list of lines to show, so it is applied or refused as a whole.
105
- await this.apply(session);
106
- return;
107
- }
108
- this.publish({ phase: 'preview', busy: false, name: next, files });
104
+ this.publish({ phase: 'preview', busy: false, name: next, files, operations: resourceOperationsOf(edit) });
109
105
  }
110
106
  catch (error) {
111
107
  if (this.session === session) {
@@ -148,7 +144,7 @@ export class RenameFeature {
148
144
  fail(message) {
149
145
  if (this.session !== null) {
150
146
  this.session.edit = null;
151
- this.publish({ phase: 'input', busy: false, files: [], error: this.say('failed', { message }) });
147
+ this.publish({ phase: 'input', busy: false, files: [], operations: [], error: this.say('failed', { message }) });
152
148
  }
153
149
  }
154
150
  finish() {
@@ -244,6 +240,7 @@ export class RenameFeature {
244
240
  error: null,
245
241
  name: session.target.placeholder,
246
242
  files: [],
243
+ operations: [],
247
244
  suggestions: [],
248
245
  ...current,
249
246
  ...patch