@adecore/editor-react 0.31.11 → 0.32.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 (137) hide show
  1. package/dist/AnchoredPopup.js +73 -19
  2. package/dist/AttributionCard.js +162 -4
  3. package/dist/BaselineStatus.js +62 -8
  4. package/dist/CallHierarchyPanel.js +977 -60
  5. package/dist/ChangeReview.js +76 -25
  6. package/dist/CodeAuthorsCard.js +314 -14
  7. package/dist/CompletionPopup.js +490 -83
  8. package/dist/DiffView.js +548 -181
  9. package/dist/EditorContextMenu.js +352 -34
  10. package/dist/EditorView.js +276 -99
  11. package/dist/FindReplace.js +525 -67
  12. package/dist/HoverCard.js +378 -38
  13. package/dist/HoverSections.js +367 -56
  14. package/dist/LanguagePopups.js +95 -34
  15. package/dist/Markdown.js +41 -6
  16. package/dist/PanelResizeHandle.js +199 -66
  17. package/dist/PathText.js +49 -7
  18. package/dist/PeekPanel.js +675 -72
  19. package/dist/PickPopup.js +304 -34
  20. package/dist/RenameCard.js +679 -85
  21. package/dist/RenderedDocs.js +329 -58
  22. package/dist/SignatureCard.js +91 -7
  23. package/dist/SplitDiffView.js +344 -254
  24. package/dist/SymbolPicker.js +440 -85
  25. package/dist/call-hierarchy-model.js +102 -68
  26. package/dist/call-hierarchy.js +458 -359
  27. package/dist/code-actions-model.js +88 -76
  28. package/dist/code-actions.js +372 -283
  29. package/dist/code-authors.js +64 -55
  30. package/dist/code-vision-model.js +134 -127
  31. package/dist/code-vision.js +224 -206
  32. package/dist/completion-call.js +94 -67
  33. package/dist/completion-model.js +334 -244
  34. package/dist/completion.js +840 -649
  35. package/dist/conflict-model.js +35 -29
  36. package/dist/context-menu-rows.js +42 -17
  37. package/dist/context-menu.js +64 -46
  38. package/dist/definition-link.js +88 -85
  39. package/dist/diagnostics-model.js +74 -55
  40. package/dist/diagnostics.js +121 -100
  41. package/dist/diff-expansion.js +38 -35
  42. package/dist/diff-overlays.js +144 -22
  43. package/dist/diff-syntax.js +65 -42
  44. package/dist/diff-wordmarks.js +26 -23
  45. package/dist/doc-comment-model.js +265 -216
  46. package/dist/editor-language.js +177 -152
  47. package/dist/error-message.js +2 -2
  48. package/dist/find-query.js +49 -36
  49. package/dist/folding.js +81 -62
  50. package/dist/format-on-type.js +86 -67
  51. package/dist/highlight-layers.js +19 -20
  52. package/dist/highlights.js +105 -83
  53. package/dist/history.js +44 -38
  54. package/dist/holder.js +18 -18
  55. package/dist/host-types.js +1 -1
  56. package/dist/hover-content.js +167 -129
  57. package/dist/hover.js +285 -245
  58. package/dist/index.js +10 -10
  59. package/dist/inlay-hints.js +369 -324
  60. package/dist/key-priority.js +34 -31
  61. package/dist/language-ids.js +45 -42
  62. package/dist/line-edits.js +74 -28
  63. package/dist/line-text.js +18 -3
  64. package/dist/locales/en.json +38 -38
  65. package/dist/locales/nl.json +41 -41
  66. package/dist/models.js +1 -1
  67. package/dist/navigation-history.js +40 -41
  68. package/dist/navigation.js +141 -123
  69. package/dist/panel-size.js +8 -8
  70. package/dist/patch-decorations.js +236 -160
  71. package/dist/patch-layout.js +331 -262
  72. package/dist/paths.js +3 -3
  73. package/dist/peek-model.js +86 -69
  74. package/dist/peek.js +254 -192
  75. package/dist/pick.js +164 -157
  76. package/dist/popup-focus.js +5 -5
  77. package/dist/popup-placement.js +36 -21
  78. package/dist/popups.js +13 -13
  79. package/dist/project-language.js +228 -211
  80. package/dist/project-problems.js +81 -69
  81. package/dist/proposal-model.js +163 -126
  82. package/dist/recent-choices.js +25 -25
  83. package/dist/refresher.js +44 -49
  84. package/dist/rename-model.js +113 -113
  85. package/dist/rename.js +321 -233
  86. package/dist/rendered-docs.d.ts +4 -2
  87. package/dist/rendered-docs.js +384 -318
  88. package/dist/rendering-context.js +5 -3
  89. package/dist/rendering.js +22 -4
  90. package/dist/review-group.js +42 -39
  91. package/dist/row-host.d.ts +4 -2
  92. package/dist/row-host.js +90 -78
  93. package/dist/selection-ranges.js +21 -17
  94. package/dist/semantic-delta.js +22 -19
  95. package/dist/semantic-model.js +86 -73
  96. package/dist/semantic-tokens.js +201 -162
  97. package/dist/shortcut-keys.js +16 -10
  98. package/dist/signature-model.js +34 -28
  99. package/dist/signature.js +177 -129
  100. package/dist/snippet-field-transforms.js +118 -106
  101. package/dist/snippet-indent.js +48 -43
  102. package/dist/snippet-range-order.js +11 -11
  103. package/dist/snippet-session.js +703 -667
  104. package/dist/snippet-transform-client.js +59 -59
  105. package/dist/snippet-transform-format.js +285 -263
  106. package/dist/snippet-transform-protocol.js +1 -1
  107. package/dist/snippet-transform-queue.js +163 -153
  108. package/dist/snippet-transform-worker.js +42 -35
  109. package/dist/snippet-variable-names.js +1 -36
  110. package/dist/snippet-variables.js +53 -63
  111. package/dist/snippet.js +477 -405
  112. package/dist/split-alignment.js +47 -35
  113. package/dist/split-scroll.js +42 -40
  114. package/dist/symbol-links.js +33 -25
  115. package/dist/symbol-picker-model.js +126 -116
  116. package/dist/symbol-picker.js +98 -59
  117. package/dist/symbols.js +117 -88
  118. package/dist/testing.js +248 -165
  119. package/dist/timers.js +35 -30
  120. package/dist/use-panel-body.js +49 -8
  121. package/dist/use-problem-counts.js +29 -15
  122. package/dist/view-state.js +54 -33
  123. package/dist/word-diff.js +18 -18
  124. package/dist/workspace-edit.js +300 -249
  125. package/package.json +6 -6
  126. package/src/CallHierarchyPanel.tsx +2 -2
  127. package/src/DiffView.tsx +3 -3
  128. package/src/FindReplace.tsx +2 -2
  129. package/src/LanguagePopups.tsx +2 -0
  130. package/src/PeekPanel.tsx +3 -3
  131. package/src/RenderedDocs.tsx +9 -10
  132. package/src/SymbolPicker.tsx +2 -2
  133. package/src/diff-overlays.tsx +6 -4
  134. package/src/locales/en.json +38 -38
  135. package/src/locales/nl.json +41 -41
  136. package/src/rendered-docs.ts +14 -3
  137. package/src/row-host.ts +14 -3
@@ -19,381 +19,480 @@ const OWNER = 'call-hierarchy';
19
19
  * shortcut that opened the tree switches between callers and callees. Typing closes it.
20
20
  */
21
21
  export class CallHierarchyFeature {
22
- language;
23
- timers;
24
- nodes = new Map();
25
- roots = [];
26
- prepared = [];
27
- direction = 'incoming';
28
- controller = new AbortController();
29
- /* Raised whenever the tree starts over or closes, so an answer for the tree that was never lands in the one that is. */
30
- epoch = 0;
31
- previewEpoch = 0;
32
- texts = new Map();
33
- constructor(language, timers = realTimers) {
34
- this.language = language;
35
- this.timers = timers;
36
- const { editor } = language;
37
- const offs = [
38
- language.onKey((event) => {
39
- if (isShortcut(this.language.shortcuts.callHierarchy, event)) {
40
- if (this.isOpen) {
41
- this.setDirection(this.direction === 'incoming' ? 'outgoing' : 'incoming');
42
- }
43
- else {
44
- void this.open();
45
- }
46
- return true;
47
- }
48
- return this.key(event);
49
- }, KEY_PRIORITY.callHierarchy),
50
- editor.onTextChange(() => this.close())
51
- ];
52
- language.onDispose(() => {
53
- for (const off of offs) {
54
- off();
55
- }
56
- this.close();
57
- });
58
- }
59
- get isOpen() {
60
- return this.language.popups.getState().callHierarchy !== null;
61
- }
62
- /* Whether a server of this document answers the three requests the tree is made of. */
63
- get supported() {
64
- const { project, uri } = this.language;
65
- const { service } = project;
66
- return (service.prepareCallHierarchy !== undefined &&
67
- service.incomingCalls !== undefined &&
68
- service.outgoingCalls !== undefined &&
69
- service.supports(PREPARE, uri));
70
- }
71
- /* Opens the tree for the symbol at the position, on its callers unless told otherwise. */
72
- async open(position = this.language.editor.getCaret(), direction = 'incoming') {
73
- const { editor, project, uri } = this.language;
74
- if (!this.supported) {
75
- this.tell(this.say('unavailable'));
76
- return;
77
- }
78
- this.close();
79
- const epoch = this.epoch;
80
- let items;
81
- try {
82
- items = await project.service.prepareCallHierarchy(uri, position, { signal: this.controller.signal });
83
- }
84
- catch (error) {
85
- if (epoch === this.epoch && !(error instanceof StaleResultError)) {
86
- this.tell(this.say('failed', { message: messageOf(error) }));
87
- }
88
- return;
89
- }
90
- if (epoch !== this.epoch) {
91
- return;
92
- }
93
- if (items === null || items.length === 0) {
94
- this.tell(this.say('none'));
95
- return;
96
- }
97
- this.language.peek.close();
98
- this.prepared = items;
99
- this.direction = direction;
100
- this.seed();
101
- const active = this.roots[0];
102
- this.language.popups.setState({
103
- callHierarchy: { direction, container: null, name: items[0].name, rows: rowsOf(this.nodes, this.roots), active, site: 0, sites: 0, preview: null }
104
- });
105
- this.select(active);
106
- // The editor draws a row once per id, so a tree opened over another needs an id of its own to get a container.
107
- editor.setWidgets([{ id: `call-hierarchy-${epoch}`, line: position.line, height: CALL_HIERARCHY_HEIGHT, render: (container) => this.mounted(container) }], OWNER);
108
- editor.focus();
109
- for (const root of this.roots) {
110
- void this.load(root);
111
- }
22
+ language;
23
+ timers;
24
+ nodes = new Map();
25
+ roots = [];
26
+ prepared = [];
27
+ direction = 'incoming';
28
+ controller = new AbortController();
29
+ /* Raised whenever the tree starts over or closes, so an answer for the tree that was never lands in the one that is. */
30
+ epoch = 0;
31
+ previewEpoch = 0;
32
+ texts = new Map();
33
+ constructor(language, timers = realTimers) {
34
+ this.language = language;
35
+ this.timers = timers;
36
+ const {
37
+ editor
38
+ } = language;
39
+ const offs = [language.onKey(event => {
40
+ if (isShortcut(this.language.shortcuts.callHierarchy, event)) {
41
+ if (this.isOpen) {
42
+ this.setDirection(this.direction === 'incoming' ? 'outgoing' : 'incoming');
43
+ } else {
44
+ void this.open();
45
+ }
46
+ return true;
47
+ }
48
+ return this.key(event);
49
+ }, KEY_PRIORITY.callHierarchy), editor.onTextChange(() => this.close())];
50
+ language.onDispose(() => {
51
+ for (const off of offs) {
52
+ off();
53
+ }
54
+ this.close();
55
+ });
56
+ }
57
+ get isOpen() {
58
+ return this.language.popups.getState().callHierarchy !== null;
59
+ }
60
+ /* Whether a server of this document answers the three requests the tree is made of. */
61
+ get supported() {
62
+ const {
63
+ project,
64
+ uri
65
+ } = this.language;
66
+ const {
67
+ service
68
+ } = project;
69
+ return service.prepareCallHierarchy !== undefined && service.incomingCalls !== undefined && service.outgoingCalls !== undefined && service.supports(PREPARE, uri);
70
+ }
71
+ /* Opens the tree for the symbol at the position, on its callers unless told otherwise. */
72
+ async open(position = this.language.editor.getCaret(), direction = 'incoming') {
73
+ const {
74
+ editor,
75
+ project,
76
+ uri
77
+ } = this.language;
78
+ if (!this.supported) {
79
+ this.tell(this.say('unavailable'));
80
+ return;
112
81
  }
113
- close() {
114
- this.reset();
115
- if (this.language.popups.getState().callHierarchy === null) {
116
- return;
117
- }
118
- this.language.popups.setState({ callHierarchy: null });
119
- this.language.editor.setWidgets([], OWNER);
120
- this.language.editor.setInputPopup(null);
121
- this.language.editor.focus();
122
- }
123
- /* Starts the tree over from the same symbols for the other direction. */
124
- setDirection(direction) {
125
- const view = this.language.popups.getState().callHierarchy;
126
- if (view === null || view.direction === direction) {
127
- return;
128
- }
129
- this.reset();
130
- this.direction = direction;
131
- this.seed();
132
- const active = this.roots[0];
133
- this.language.popups.setState({
134
- callHierarchy: { ...view, direction, rows: rowsOf(this.nodes, this.roots), active, site: 0, sites: 0, preview: null }
135
- });
136
- this.select(active);
137
- for (const root of this.roots) {
138
- void this.load(root);
139
- }
82
+ this.close();
83
+ const epoch = this.epoch;
84
+ let items;
85
+ try {
86
+ items = await project.service.prepareCallHierarchy(uri, position, {
87
+ signal: this.controller.signal
88
+ });
89
+ } catch (error) {
90
+ if (epoch === this.epoch && !(error instanceof StaleResultError)) {
91
+ this.tell(this.say('failed', {
92
+ message: messageOf(error)
93
+ }));
94
+ }
95
+ return;
140
96
  }
141
- /* Opens a node, asking the server for its calls the first time, or folds it away. */
142
- toggle(id) {
143
- const node = this.nodes.get(id);
144
- if (node === undefined) {
145
- return;
146
- }
147
- if (node.expanded) {
148
- this.patch(id, { expanded: false });
149
- const view = this.language.popups.getState().callHierarchy;
150
- if (view !== null && this.hasAncestor(view.active, id)) {
151
- this.select(id);
152
- }
153
- return;
154
- }
155
- this.patch(id, { expanded: true });
156
- if (node.load === 'idle' || node.load === 'failed') {
157
- void this.load(id);
158
- }
97
+ if (epoch !== this.epoch) {
98
+ return;
159
99
  }
160
- /* Asks again for the calls of a node whose last try failed. */
161
- retry(id) {
162
- if (this.nodes.get(id)?.load === 'failed') {
163
- void this.load(id);
164
- }
100
+ if (items === null || items.length === 0) {
101
+ this.tell(this.say('none'));
102
+ return;
165
103
  }
166
- /* Brings the next stretch of calls of a level. */
167
- showMore(parent) {
168
- const node = this.nodes.get(parent);
169
- if (node === undefined) {
170
- return;
171
- }
172
- this.patch(parent, { shown: Math.min(node.children.length, node.shown + CALL_LEVEL_LIMIT) });
173
- const first = node.children[node.shown];
174
- const view = this.language.popups.getState().callHierarchy;
175
- // The row that offered more is gone once everything shows, so the selection moves on to what it brought.
176
- if (first !== undefined && view?.active === moreIdOf(parent)) {
177
- this.select(first);
178
- }
104
+ this.language.peek.close();
105
+ this.prepared = items;
106
+ this.direction = direction;
107
+ this.seed();
108
+ const active = this.roots[0];
109
+ this.language.popups.setState({
110
+ callHierarchy: {
111
+ direction,
112
+ container: null,
113
+ name: items[0].name,
114
+ rows: rowsOf(this.nodes, this.roots),
115
+ active,
116
+ site: 0,
117
+ sites: 0,
118
+ preview: null
119
+ }
120
+ });
121
+ this.select(active);
122
+ // The editor draws a row once per id, so a tree opened over another needs an id of its own to get a container.
123
+ editor.setWidgets([{
124
+ id: `call-hierarchy-${epoch}`,
125
+ line: position.line,
126
+ height: CALL_HIERARCHY_HEIGHT,
127
+ render: container => this.mounted(container)
128
+ }], OWNER);
129
+ editor.focus();
130
+ for (const root of this.roots) {
131
+ void this.load(root);
179
132
  }
180
- /* Makes a row the active one and shows the code of its call. */
181
- select(id, site = 0) {
182
- const view = this.language.popups.getState().callHierarchy;
183
- const node = this.nodes.get(id);
184
- if (view === null || !view.rows.some((row) => row.id === id)) {
185
- return;
186
- }
187
- const sites = node?.sites.length ?? view.sites;
188
- const next = node === undefined ? view.site : Math.max(0, Math.min(Math.max(0, sites - 1), site));
189
- this.language.popups.setState({ callHierarchy: { ...view, active: id, site: next, sites, preview: node === undefined ? view.preview : null } });
190
- if (node !== undefined) {
191
- void this.preview(node, next);
192
- }
133
+ }
134
+ close() {
135
+ this.reset();
136
+ if (this.language.popups.getState().callHierarchy === null) {
137
+ return;
193
138
  }
194
- /* Steps through the places a call is made from when the active one has more than one. */
195
- stepSite(direction) {
196
- const view = this.language.popups.getState().callHierarchy;
197
- if (view !== null && view.sites > 1) {
198
- this.select(view.active, (view.site + direction + view.sites) % view.sites);
199
- }
139
+ this.language.popups.setState({
140
+ callHierarchy: null
141
+ });
142
+ this.language.editor.setWidgets([], OWNER);
143
+ this.language.editor.setInputPopup(null);
144
+ this.language.editor.focus();
145
+ }
146
+ /* Starts the tree over from the same symbols for the other direction. */
147
+ setDirection(direction) {
148
+ const view = this.language.popups.getState().callHierarchy;
149
+ if (view === null || view.direction === direction) {
150
+ return;
200
151
  }
201
- /* Goes to the call of a row, or to the symbol of a root, and closes the tree. */
202
- go(id) {
203
- const view = this.language.popups.getState().callHierarchy;
204
- const node = this.nodes.get(id);
205
- if (view === null || node === undefined) {
206
- return;
207
- }
208
- const { uri, range } = targetOf(node, id === view.active ? view.site : 0);
152
+ this.reset();
153
+ this.direction = direction;
154
+ this.seed();
155
+ const active = this.roots[0];
156
+ this.language.popups.setState({
157
+ callHierarchy: {
158
+ ...view,
159
+ direction,
160
+ rows: rowsOf(this.nodes, this.roots),
161
+ active,
162
+ site: 0,
163
+ sites: 0,
164
+ preview: null
165
+ }
166
+ });
167
+ this.select(active);
168
+ for (const root of this.roots) {
169
+ void this.load(root);
170
+ }
171
+ }
172
+ /* Opens a node, asking the server for its calls the first time, or folds it away. */
173
+ toggle(id) {
174
+ const node = this.nodes.get(id);
175
+ if (node === undefined) {
176
+ return;
177
+ }
178
+ if (node.expanded) {
179
+ this.patch(id, {
180
+ expanded: false
181
+ });
182
+ const view = this.language.popups.getState().callHierarchy;
183
+ if (view !== null && this.hasAncestor(view.active, id)) {
184
+ this.select(id);
185
+ }
186
+ return;
187
+ }
188
+ this.patch(id, {
189
+ expanded: true
190
+ });
191
+ if (node.load === 'idle' || node.load === 'failed') {
192
+ void this.load(id);
193
+ }
194
+ }
195
+ /* Asks again for the calls of a node whose last try failed. */
196
+ retry(id) {
197
+ if (this.nodes.get(id)?.load === 'failed') {
198
+ void this.load(id);
199
+ }
200
+ }
201
+ /* Brings the next stretch of calls of a level. */
202
+ showMore(parent) {
203
+ const node = this.nodes.get(parent);
204
+ if (node === undefined) {
205
+ return;
206
+ }
207
+ this.patch(parent, {
208
+ shown: Math.min(node.children.length, node.shown + CALL_LEVEL_LIMIT)
209
+ });
210
+ const first = node.children[node.shown];
211
+ const view = this.language.popups.getState().callHierarchy;
212
+ // The row that offered more is gone once everything shows, so the selection moves on to what it brought.
213
+ if (first !== undefined && view?.active === moreIdOf(parent)) {
214
+ this.select(first);
215
+ }
216
+ }
217
+ /* Makes a row the active one and shows the code of its call. */
218
+ select(id, site = 0) {
219
+ const view = this.language.popups.getState().callHierarchy;
220
+ const node = this.nodes.get(id);
221
+ if (view === null || !view.rows.some(row => row.id === id)) {
222
+ return;
223
+ }
224
+ const sites = node?.sites.length ?? view.sites;
225
+ const next = node === undefined ? view.site : Math.max(0, Math.min(Math.max(0, sites - 1), site));
226
+ this.language.popups.setState({
227
+ callHierarchy: {
228
+ ...view,
229
+ active: id,
230
+ site: next,
231
+ sites,
232
+ preview: node === undefined ? view.preview : null
233
+ }
234
+ });
235
+ if (node !== undefined) {
236
+ void this.preview(node, next);
237
+ }
238
+ }
239
+ /* Steps through the places a call is made from when the active one has more than one. */
240
+ stepSite(direction) {
241
+ const view = this.language.popups.getState().callHierarchy;
242
+ if (view !== null && view.sites > 1) {
243
+ this.select(view.active, (view.site + direction + view.sites) % view.sites);
244
+ }
245
+ }
246
+ /* Goes to the call of a row, or to the symbol of a root, and closes the tree. */
247
+ go(id) {
248
+ const view = this.language.popups.getState().callHierarchy;
249
+ const node = this.nodes.get(id);
250
+ if (view === null || node === undefined) {
251
+ return;
252
+ }
253
+ const {
254
+ uri,
255
+ range
256
+ } = targetOf(node, id === view.active ? view.site : 0);
257
+ this.close();
258
+ this.language.goTo({
259
+ uri,
260
+ range
261
+ });
262
+ }
263
+ reset() {
264
+ this.epoch++;
265
+ this.previewEpoch++;
266
+ this.controller.abort();
267
+ this.controller = new AbortController();
268
+ this.nodes = new Map();
269
+ this.roots = [];
270
+ this.texts = new Map();
271
+ }
272
+ seed() {
273
+ const roots = rootsOf(this.prepared);
274
+ this.roots = roots.map(root => root.id);
275
+ this.nodes = new Map(roots.map(root => [root.id, root]));
276
+ }
277
+ mounted(container) {
278
+ const view = this.language.popups.getState().callHierarchy;
279
+ if (view !== null && view.container !== container) {
280
+ this.language.popups.setState({
281
+ callHierarchy: {
282
+ ...view,
283
+ container
284
+ }
285
+ });
286
+ revealRow(this.timers, container);
287
+ }
288
+ }
289
+ patch(id, change) {
290
+ const node = this.nodes.get(id);
291
+ const view = this.language.popups.getState().callHierarchy;
292
+ if (node === undefined || view === null) {
293
+ return;
294
+ }
295
+ this.nodes.set(id, {
296
+ ...node,
297
+ ...change
298
+ });
299
+ this.language.popups.setState({
300
+ callHierarchy: {
301
+ ...view,
302
+ rows: rowsOf(this.nodes, this.roots)
303
+ }
304
+ });
305
+ }
306
+ async load(id) {
307
+ const node = this.nodes.get(id);
308
+ if (node === undefined || node.load === 'loading') {
309
+ return;
310
+ }
311
+ const {
312
+ service,
313
+ i18n
314
+ } = this.language.project;
315
+ const {
316
+ uri,
317
+ editor
318
+ } = this.language;
319
+ const {
320
+ direction,
321
+ epoch
322
+ } = this;
323
+ const options = {
324
+ signal: this.controller.signal,
325
+ parallel: true
326
+ };
327
+ this.patch(id, {
328
+ load: 'loading',
329
+ error: null
330
+ });
331
+ try {
332
+ const calls = direction === 'incoming' ? await service.incomingCalls(uri, node.item, options) : await service.outgoingCalls(uri, node.item, options);
333
+ if (epoch !== this.epoch) {
334
+ return;
335
+ }
336
+ const children = childrenOf(direction, node, calls ?? []);
337
+ for (const child of children) {
338
+ this.nodes.set(child.id, child);
339
+ }
340
+ this.patch(id, {
341
+ load: 'loaded',
342
+ children: children.map(child => child.id),
343
+ shown: Math.min(children.length, CALL_LEVEL_LIMIT)
344
+ });
345
+ editor.announce(i18n.t(`editor:language.callHierarchy.announce.${direction}`, {
346
+ count: children.length,
347
+ name: node.item.name
348
+ }));
349
+ } catch (error) {
350
+ if (epoch !== this.epoch) {
351
+ return;
352
+ }
353
+ if (error instanceof StaleResultError) {
209
354
  this.close();
210
- this.language.goTo({ uri, range });
211
- }
212
- reset() {
213
- this.epoch++;
214
- this.previewEpoch++;
215
- this.controller.abort();
216
- this.controller = new AbortController();
217
- this.nodes = new Map();
218
- this.roots = [];
219
- this.texts = new Map();
220
- }
221
- seed() {
222
- const roots = rootsOf(this.prepared);
223
- this.roots = roots.map((root) => root.id);
224
- this.nodes = new Map(roots.map((root) => [root.id, root]));
225
- }
226
- mounted(container) {
227
- const view = this.language.popups.getState().callHierarchy;
228
- if (view !== null && view.container !== container) {
229
- this.language.popups.setState({ callHierarchy: { ...view, container } });
230
- revealRow(this.timers, container);
231
- }
355
+ return;
356
+ }
357
+ this.patch(id, {
358
+ load: 'failed',
359
+ error: messageOf(error)
360
+ });
232
361
  }
233
- patch(id, change) {
234
- const node = this.nodes.get(id);
235
- const view = this.language.popups.getState().callHierarchy;
236
- if (node === undefined || view === null) {
237
- return;
238
- }
239
- this.nodes.set(id, { ...node, ...change });
240
- this.language.popups.setState({ callHierarchy: { ...view, rows: rowsOf(this.nodes, this.roots) } });
362
+ }
363
+ async preview(node, site) {
364
+ const {
365
+ project
366
+ } = this.language;
367
+ const epoch = ++this.previewEpoch;
368
+ const {
369
+ uri,
370
+ range
371
+ } = targetOf(node, site);
372
+ let text = this.texts.get(uri);
373
+ if (text === undefined) {
374
+ try {
375
+ text = (await project.readText(uri)) ?? undefined;
376
+ } catch {
377
+ // A file that cannot be read shows no code, as one that is no text file does.
378
+ text = undefined;
379
+ }
380
+ if (epoch !== this.previewEpoch) {
381
+ return;
382
+ }
383
+ if (text !== undefined) {
384
+ this.texts.set(uri, text);
385
+ }
241
386
  }
242
- async load(id) {
243
- const node = this.nodes.get(id);
244
- if (node === undefined || node.load === 'loading') {
245
- return;
246
- }
247
- const { service, i18n } = this.language.project;
248
- const { uri, editor } = this.language;
249
- const { direction, epoch } = this;
250
- const options = { signal: this.controller.signal, parallel: true };
251
- this.patch(id, { load: 'loading', error: null });
252
- try {
253
- const calls = direction === 'incoming' ? await service.incomingCalls(uri, node.item, options) : await service.outgoingCalls(uri, node.item, options);
254
- if (epoch !== this.epoch) {
255
- return;
256
- }
257
- const children = childrenOf(direction, node, calls ?? []);
258
- for (const child of children) {
259
- this.nodes.set(child.id, child);
260
- }
261
- this.patch(id, { load: 'loaded', children: children.map((child) => child.id), shown: Math.min(children.length, CALL_LEVEL_LIMIT) });
262
- editor.announce(i18n.t(`editor:language.callHierarchy.announce.${direction}`, { count: children.length, name: node.item.name }));
263
- }
264
- catch (error) {
265
- if (epoch !== this.epoch) {
266
- return;
267
- }
268
- if (error instanceof StaleResultError) {
269
- this.close();
270
- return;
271
- }
272
- this.patch(id, { load: 'failed', error: messageOf(error) });
273
- }
387
+ const view = this.language.popups.getState().callHierarchy;
388
+ if (view === null || view.active !== node.id) {
389
+ return;
274
390
  }
275
- async preview(node, site) {
276
- const { project } = this.language;
277
- const epoch = ++this.previewEpoch;
278
- const { uri, range } = targetOf(node, site);
279
- let text = this.texts.get(uri);
280
- if (text === undefined) {
281
- try {
282
- text = (await project.readText(uri)) ?? undefined;
283
- }
284
- catch {
285
- // A file that cannot be read shows no code, as one that is no text file does.
286
- text = undefined;
287
- }
288
- if (epoch !== this.previewEpoch) {
289
- return;
290
- }
291
- if (text !== undefined) {
292
- this.texts.set(uri, text);
293
- }
294
- }
295
- const view = this.language.popups.getState().callHierarchy;
296
- if (view === null || view.active !== node.id) {
297
- return;
298
- }
299
- const snippet = text === undefined ? null : snippetOf(text, range.start.line, range);
300
- this.language.popups.setState({ callHierarchy: { ...view, preview: snippet === null ? null : { ...snippet, uri } } });
301
- }
302
- /* Whether a row stands under a node; the `more` and `status` rows belong to the node they are about. */
303
- hasAncestor(id, ancestor) {
304
- const owner = [moreIdOf(''), statusIdOf('')]
305
- .map((suffix) => (id.endsWith(suffix) ? id.slice(0, -suffix.length) : null))
306
- .find((found) => found !== null);
307
- if (owner !== undefined && owner !== null) {
308
- return owner === ancestor || this.hasAncestor(owner, ancestor);
309
- }
310
- for (let node = this.nodes.get(id); node !== undefined && node.parent !== null; node = this.nodes.get(node.parent)) {
311
- if (node.parent === ancestor) {
312
- return true;
313
- }
314
- }
391
+ const snippet = text === undefined ? null : snippetOf(text, range.start.line, range);
392
+ this.language.popups.setState({
393
+ callHierarchy: {
394
+ ...view,
395
+ preview: snippet === null ? null : {
396
+ ...snippet,
397
+ uri
398
+ }
399
+ }
400
+ });
401
+ }
402
+ /* Whether a row stands under a node; the `more` and `status` rows belong to the node they are about. */
403
+ hasAncestor(id, ancestor) {
404
+ const owner = [moreIdOf(''), statusIdOf('')].map(suffix => id.endsWith(suffix) ? id.slice(0, -suffix.length) : null).find(found => found !== null);
405
+ if (owner !== undefined && owner !== null) {
406
+ return owner === ancestor || this.hasAncestor(owner, ancestor);
407
+ }
408
+ for (let node = this.nodes.get(id); node !== undefined && node.parent !== null; node = this.nodes.get(node.parent)) {
409
+ if (node.parent === ancestor) {
410
+ return true;
411
+ }
412
+ }
413
+ return false;
414
+ }
415
+ key(event) {
416
+ const view = this.language.popups.getState().callHierarchy;
417
+ if (view === null || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.isComposing) {
418
+ return false;
419
+ }
420
+ const steps = stepsOf(view.rows);
421
+ const index = steps.findIndex(row => row.id === view.active);
422
+ const row = steps[index];
423
+ switch (event.key) {
424
+ case 'ArrowDown':
425
+ this.land(steps[Math.min(steps.length - 1, index + 1)]);
426
+ return true;
427
+ case 'ArrowUp':
428
+ this.land(steps[Math.max(0, index - 1)]);
429
+ return true;
430
+ case 'Home':
431
+ this.land(steps[0]);
432
+ return true;
433
+ case 'End':
434
+ this.land(steps.at(-1));
435
+ return true;
436
+ case 'ArrowRight':
437
+ this.expandOrEnter(row, steps[index + 1]);
438
+ return true;
439
+ case 'ArrowLeft':
440
+ this.collapseOrLeave(row);
441
+ return true;
442
+ case 'Enter':
443
+ if (row?.kind === 'more') {
444
+ this.showMore(row.parent);
445
+ } else if (row !== undefined) {
446
+ this.go(row.id);
447
+ }
448
+ return true;
449
+ case 'Escape':
450
+ this.close();
451
+ return true;
452
+ default:
315
453
  return false;
316
454
  }
317
- key(event) {
318
- const view = this.language.popups.getState().callHierarchy;
319
- if (view === null || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.isComposing) {
320
- return false;
321
- }
322
- const steps = stepsOf(view.rows);
323
- const index = steps.findIndex((row) => row.id === view.active);
324
- const row = steps[index];
325
- switch (event.key) {
326
- case 'ArrowDown':
327
- this.land(steps[Math.min(steps.length - 1, index + 1)]);
328
- return true;
329
- case 'ArrowUp':
330
- this.land(steps[Math.max(0, index - 1)]);
331
- return true;
332
- case 'Home':
333
- this.land(steps[0]);
334
- return true;
335
- case 'End':
336
- this.land(steps.at(-1));
337
- return true;
338
- case 'ArrowRight':
339
- this.expandOrEnter(row, steps[index + 1]);
340
- return true;
341
- case 'ArrowLeft':
342
- this.collapseOrLeave(row);
343
- return true;
344
- case 'Enter':
345
- if (row?.kind === 'more') {
346
- this.showMore(row.parent);
347
- }
348
- else if (row !== undefined) {
349
- this.go(row.id);
350
- }
351
- return true;
352
- case 'Escape':
353
- this.close();
354
- return true;
355
- default:
356
- return false;
357
- }
455
+ }
456
+ land(row) {
457
+ if (row !== undefined) {
458
+ this.select(row.id);
358
459
  }
359
- land(row) {
360
- if (row !== undefined) {
361
- this.select(row.id);
362
- }
460
+ }
461
+ /* Right opens a closed node, tries a failed one again, and steps into an open one. */
462
+ expandOrEnter(row, next) {
463
+ if (row?.kind !== 'node') {
464
+ return;
363
465
  }
364
- /* Right opens a closed node, tries a failed one again, and steps into an open one. */
365
- expandOrEnter(row, next) {
366
- if (row?.kind !== 'node') {
367
- return;
368
- }
369
- if (!row.expanded) {
370
- this.toggle(row.id);
371
- }
372
- else if (row.load === 'failed') {
373
- this.retry(row.id);
374
- }
375
- else if (next !== undefined && next.level > row.level) {
376
- this.land(next);
377
- }
466
+ if (!row.expanded) {
467
+ this.toggle(row.id);
468
+ } else if (row.load === 'failed') {
469
+ this.retry(row.id);
470
+ } else if (next !== undefined && next.level > row.level) {
471
+ this.land(next);
378
472
  }
379
- /* Left closes an open node and steps out of a closed one. */
380
- collapseOrLeave(row) {
381
- if (row === undefined) {
382
- return;
383
- }
384
- if (row.kind === 'node' && row.expanded && !row.leaf) {
385
- this.toggle(row.id);
386
- return;
387
- }
388
- const parent = row.kind === 'node' ? (this.nodes.get(row.id)?.parent ?? null) : row.parent;
389
- if (parent !== null) {
390
- this.select(parent);
391
- }
473
+ }
474
+ /* Left closes an open node and steps out of a closed one. */
475
+ collapseOrLeave(row) {
476
+ if (row === undefined) {
477
+ return;
392
478
  }
393
- tell(title) {
394
- this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title });
479
+ if (row.kind === 'node' && row.expanded && !row.leaf) {
480
+ this.toggle(row.id);
481
+ return;
395
482
  }
396
- say(key, options) {
397
- return this.language.project.i18n.t(`editor:language.callHierarchy.${key}`, options);
483
+ const parent = row.kind === 'node' ? this.nodes.get(row.id)?.parent ?? null : row.parent;
484
+ if (parent !== null) {
485
+ this.select(parent);
398
486
  }
399
- }
487
+ }
488
+ tell(title) {
489
+ this.language.project.host.notify?.({
490
+ id: TOAST_ID,
491
+ kind: 'error',
492
+ title
493
+ });
494
+ }
495
+ say(key, options) {
496
+ return this.language.project.i18n.t(`editor:language.callHierarchy.${key}`, options);
497
+ }
498
+ }