@adecore/editor-react 0.39.0 → 0.40.1

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 (154) hide show
  1. package/dist/CallHierarchyPanel.js +30 -55
  2. package/dist/CodeAuthorsCard.js +0 -1
  3. package/dist/CompletionPopup.js +150 -181
  4. package/dist/DiffView.d.ts +1 -1
  5. package/dist/DiffView.js +2 -29
  6. package/dist/PickPopup.js +54 -80
  7. package/dist/SplitDiffView.js +27 -57
  8. package/dist/SymbolPicker.js +2 -8
  9. package/dist/code-actions-model.js +2 -9
  10. package/dist/code-actions.js +10 -11
  11. package/dist/code-vision.js +2 -1
  12. package/dist/completion-model.js +2 -2
  13. package/dist/completion.js +13 -34
  14. package/dist/definition-link.d.ts +1 -0
  15. package/dist/definition-link.js +22 -18
  16. package/dist/diagnostics-model.d.ts +1 -0
  17. package/dist/diagnostics-model.js +3 -1
  18. package/dist/diagnostics.js +1 -2
  19. package/dist/diff-syntax.d.ts +1 -0
  20. package/dist/diff-syntax.js +31 -0
  21. package/dist/doc-comment-model.d.ts +1 -0
  22. package/dist/doc-comment-model.js +1 -1
  23. package/dist/folding.js +2 -3
  24. package/dist/format-on-type.js +6 -10
  25. package/dist/hover-content.d.ts +0 -2
  26. package/dist/hover-content.js +1 -9
  27. package/dist/inlay-hints.d.ts +0 -1
  28. package/dist/inlay-hints.js +11 -41
  29. package/dist/navigation.js +4 -14
  30. package/dist/peek.js +2 -12
  31. package/dist/popup-focus.d.ts +3 -1
  32. package/dist/popup-focus.js +33 -0
  33. package/dist/recent-choices.d.ts +0 -1
  34. package/dist/recent-choices.js +0 -4
  35. package/dist/rendered-docs.d.ts +1 -0
  36. package/dist/rendered-docs.js +15 -28
  37. package/dist/snippet-field-transforms.d.ts +5 -0
  38. package/dist/snippet-field-transforms.js +27 -16
  39. package/dist/snippet-session.js +8 -20
  40. package/dist/symbol-links.js +3 -10
  41. package/dist/symbol-picker-model.d.ts +1 -0
  42. package/dist/symbol-picker-model.js +7 -0
  43. package/dist/unique.d.ts +10 -0
  44. package/dist/unique.js +15 -0
  45. package/package.json +9 -9
  46. package/src/AnchoredPopup.tsx +2 -0
  47. package/src/BaselineStatus.tsx +1 -0
  48. package/src/CallHierarchyPanel.tsx +16 -6
  49. package/src/ChangeReview.tsx +7 -0
  50. package/src/CodeAuthorsCard.tsx +2 -5
  51. package/src/CompletionPopup.tsx +21 -15
  52. package/src/DiffView.tsx +53 -30
  53. package/src/EditorContextMenu.tsx +7 -0
  54. package/src/EditorView.tsx +28 -0
  55. package/src/FindReplace.tsx +8 -0
  56. package/src/HoverCard.tsx +8 -0
  57. package/src/HoverSections.tsx +11 -0
  58. package/src/LanguagePopups.tsx +1 -0
  59. package/src/PanelResizeHandle.tsx +8 -0
  60. package/src/PathText.tsx +1 -0
  61. package/src/PeekPanel.tsx +13 -0
  62. package/src/PickPopup.tsx +7 -6
  63. package/src/RenameCard.tsx +13 -0
  64. package/src/RenderedDocs.tsx +11 -0
  65. package/src/SplitDiffView.tsx +91 -56
  66. package/src/SymbolPicker.tsx +10 -18
  67. package/src/call-hierarchy-model.ts +10 -0
  68. package/src/call-hierarchy.ts +82 -0
  69. package/src/code-actions-model.ts +21 -9
  70. package/src/code-actions.ts +83 -7
  71. package/src/code-authors.ts +20 -0
  72. package/src/code-vision-model.ts +37 -0
  73. package/src/code-vision.ts +47 -3
  74. package/src/completion-call.ts +19 -0
  75. package/src/completion-model.ts +58 -2
  76. package/src/completion.ts +156 -30
  77. package/src/conflict-model.ts +4 -0
  78. package/src/context-menu-rows.ts +1 -0
  79. package/src/context-menu.ts +7 -0
  80. package/src/definition-link.ts +42 -18
  81. package/src/diagnostics-model.ts +19 -1
  82. package/src/diagnostics.ts +31 -2
  83. package/src/diff-expansion.ts +9 -0
  84. package/src/diff-overlays.tsx +8 -0
  85. package/src/diff-syntax.ts +54 -0
  86. package/src/diff-wordmarks.ts +8 -0
  87. package/src/doc-comment-model.ts +51 -1
  88. package/src/editor-language.ts +11 -0
  89. package/src/find-query.ts +7 -0
  90. package/src/folding.ts +10 -3
  91. package/src/format-on-type.ts +23 -10
  92. package/src/highlight-layers.ts +3 -0
  93. package/src/highlights.ts +18 -0
  94. package/src/history.ts +11 -0
  95. package/src/holder.ts +3 -0
  96. package/src/hover-content.ts +34 -17
  97. package/src/hover.ts +57 -0
  98. package/src/index.ts +4 -0
  99. package/src/inlay-hints.ts +86 -47
  100. package/src/key-priority.ts +2 -0
  101. package/src/line-edits.ts +8 -0
  102. package/src/models.ts +6 -0
  103. package/src/navigation-history.ts +5 -0
  104. package/src/navigation.ts +28 -13
  105. package/src/panel-size.ts +7 -0
  106. package/src/patch-decorations.ts +39 -0
  107. package/src/patch-layout.ts +47 -0
  108. package/src/peek-model.ts +21 -0
  109. package/src/peek.ts +49 -13
  110. package/src/pick.ts +35 -0
  111. package/src/popup-focus.ts +11 -1
  112. package/src/popup-placement.ts +5 -0
  113. package/src/popups.ts +2 -6
  114. package/src/project-language.ts +33 -0
  115. package/src/project-problems.ts +11 -0
  116. package/src/proposal-model.ts +40 -0
  117. package/src/recent-choices.ts +3 -5
  118. package/src/refresher.ts +3 -0
  119. package/src/rename-model.ts +28 -0
  120. package/src/rename.ts +56 -0
  121. package/src/rendered-docs.ts +89 -19
  122. package/src/review-group.ts +4 -0
  123. package/src/row-host.ts +12 -0
  124. package/src/selection-ranges.ts +6 -0
  125. package/src/semantic-delta.ts +7 -0
  126. package/src/semantic-model.ts +11 -0
  127. package/src/semantic-tokens.ts +37 -0
  128. package/src/shortcut-keys.ts +3 -0
  129. package/src/signature-model.ts +7 -0
  130. package/src/signature.ts +27 -0
  131. package/src/snippet-field-transforms.ts +48 -13
  132. package/src/snippet-indent.ts +10 -0
  133. package/src/snippet-range-order.ts +5 -0
  134. package/src/snippet-session.ts +173 -31
  135. package/src/snippet-transform-client.ts +14 -0
  136. package/src/snippet-transform-format.ts +98 -0
  137. package/src/snippet-transform-protocol.ts +5 -0
  138. package/src/snippet-transform-queue.ts +53 -0
  139. package/src/snippet-transform-worker.ts +14 -0
  140. package/src/snippet-variables.ts +9 -0
  141. package/src/snippet.ts +107 -0
  142. package/src/split-alignment.ts +10 -0
  143. package/src/split-scroll.ts +8 -0
  144. package/src/symbol-links.ts +9 -12
  145. package/src/symbol-picker-model.ts +25 -0
  146. package/src/symbol-picker.ts +9 -0
  147. package/src/symbols.ts +21 -0
  148. package/src/testing.ts +7 -0
  149. package/src/timers.ts +5 -0
  150. package/src/unique.ts +23 -0
  151. package/src/use-problem-counts.ts +4 -0
  152. package/src/view-state.ts +9 -0
  153. package/src/word-diff.ts +5 -0
  154. package/src/workspace-edit.ts +84 -0
@@ -1,7 +1,9 @@
1
1
  import { findDocComments, type DocComment, type EditorPosition, type EditorRange, type EditorReplacement, type EditorTrackedRange } from '@adecore/editor';
2
+ import { comparePositions } from './diagnostics-model.ts';
2
3
  import { KEY_PRIORITY } from './key-priority.ts';
3
4
  import type { EditorLanguage } from './editor-language.ts';
4
5
  import { parseDocComment, type ParsedDocComment } from './doc-comment-model.ts';
6
+ import { lineTextOf } from './line-text.ts';
5
7
  import { isShortcut } from './shortcut-keys.ts';
6
8
  import { realTimers, type Timers } from './timers.ts';
7
9
 
@@ -9,8 +11,10 @@ const OWNER = 'rendered-docs';
9
11
  const DOCS_PREFIX = 'docs-';
10
12
  const SUMMARY_WORDS = 6;
11
13
  const SETTLE_MS = 100;
14
+
12
15
  /* What a row is taken to be tall until its content is in and measured. */
13
16
  const ESTIMATED_LINE_HEIGHT = 20;
17
+
14
18
  const MAX_ESTIMATED_LINES = 8;
15
19
  const MAX_PARSED = 200;
16
20
 
@@ -43,23 +47,31 @@ function touches(range: EditorRange, comment: DocComment): boolean {
43
47
  if (range.end.line < comment.startLine || range.start.line > comment.endLine) {
44
48
  return false;
45
49
  }
46
- const empty = range.start.line === range.end.line && range.start.character === range.end.character;
50
+
51
+ const empty = comparePositions(range.start, range.end) === 0;
52
+
47
53
  if (!empty && range.end.line === comment.startLine && range.end.character === 0) {
48
54
  return false;
49
55
  }
56
+
50
57
  return empty || range.start.line >= comment.startLine || range.end.line <= comment.endLine;
51
58
  }
52
59
 
53
60
  /* The first words of what a comment says, to tell its row from the others for a person who hears it. */
54
61
  function summaryOf(body: string): string {
55
- const lead = body.split('\n').findIndex((line) => /^\s*@/.test(line));
56
- const text = (lead === -1 ? body : body.split('\n').slice(0, lead).join('\n')).replace(/[`*_#>[\]()]/g, ' ');
57
- const words = text.split(/\s+/).filter((word) => word !== '');
58
- return words.slice(0, SUMMARY_WORDS).join(' ');
62
+ const lines = body.split('\n');
63
+ const lead = lines.findIndex((line) => /^\s*@/.test(line));
64
+ const text = (lead === -1 ? body : lines.slice(0, lead).join('\n')).replace(/[`*_#>[\]()]/g, ' ');
65
+
66
+ return text
67
+ .split(/\s+/)
68
+ .filter((word) => word !== '')
69
+ .slice(0, SUMMARY_WORDS)
70
+ .join(' ');
59
71
  }
60
72
 
61
73
  function samePlace(range: EditorRange | null, position: EditorPosition): boolean {
62
- return range !== null && range.start.line === position.line && range.start.character === position.character;
74
+ return range !== null && comparePositions(range.start, position) === 0;
63
75
  }
64
76
 
65
77
  /*
@@ -79,8 +91,10 @@ export class RenderedDocsFeature {
79
91
  private readonly markerLines = new Map<string, number>();
80
92
  private readonly parsed = new Map<string, ParsedDocComment>();
81
93
  private indexes = new Map<DocComment, number>();
94
+
82
95
  /* Comments a reveal showed as source until the caret has settled where it was going, or the comment has scrolled out of view if the caret never moves. */
83
96
  private readonly revealed = new Set<DocComment>();
97
+
84
98
  private readonly listeners = new Set<() => void>();
85
99
  private version = 0;
86
100
  private shown: { version: number; entries: readonly RenderedDocEntry[] } | null = null;
@@ -88,14 +102,17 @@ export class RenderedDocsFeature {
88
102
  private caretTimer: unknown;
89
103
  private drawn = '';
90
104
  private marked = '';
105
+
91
106
  /* The comments are from before the last edit, so nothing is decided from them until they are read again. */
92
107
  private stale = false;
108
+
93
109
  private disposed = false;
94
110
 
95
111
  constructor(language: EditorLanguage, timers: Timers = realTimers) {
96
112
  this.language = language;
97
113
  this.timers = timers;
98
114
  const { editor } = language;
115
+
99
116
  const offs = [
100
117
  editor.onTextChange(() => {
101
118
  this.stale = true;
@@ -105,6 +122,7 @@ export class RenderedDocsFeature {
105
122
  // The caret is told before the text change that moved it, so what the lines are is decided a moment later.
106
123
  editor.onCaret(() => {
107
124
  this.timers.clear(this.caretTimer);
125
+
108
126
  this.caretTimer = this.timers.set(() => {
109
127
  this.revealed.clear();
110
128
  this.publish();
@@ -112,6 +130,7 @@ export class RenderedDocsFeature {
112
130
  }),
113
131
  editor.onGutterMarker((id) => {
114
132
  const line = this.markerLines.get(id);
133
+
115
134
  if (line !== undefined) {
116
135
  this.toggleAt(line);
117
136
  }
@@ -124,37 +143,42 @@ export class RenderedDocsFeature {
124
143
  this.releaseRevealed();
125
144
  })
126
145
  ];
146
+
127
147
  language.onDispose(() => {
128
148
  this.disposed = true;
149
+
129
150
  for (const off of offs) {
130
151
  off();
131
152
  }
153
+
132
154
  this.timers.clear(this.settleTimer);
133
155
  this.timers.clear(this.caretTimer);
134
- for (const override of this.overrides) {
135
- override.tracked.dispose();
136
- }
137
- this.overrides = [];
156
+ this.clearOverrides();
138
157
  editor.setReplacements?.([], OWNER);
139
158
  editor.setGutterMarkers([], OWNER);
140
159
  this.containers.clear();
141
160
  this.bump();
142
161
  });
162
+
143
163
  this.read();
144
164
  }
145
165
 
146
166
  /* Whether every documentation comment is drawn as text until a person switches it. Changing it puts every comment back under it. */
147
167
  configure(settings: RenderedDocsSettings): void {
148
168
  const enabled = settings.enabled !== false;
169
+
149
170
  if (settings.renderDocComments === this.settings.renderDocComments && enabled === this.enabled) {
150
171
  return;
151
172
  }
173
+
152
174
  const sameRendering = settings.renderDocComments === this.settings.renderDocComments;
153
175
  this.settings = settings;
154
176
  this.enabled = enabled;
177
+
155
178
  if (!sameRendering) {
156
179
  this.clearOverrides();
157
180
  }
181
+
158
182
  this.publish();
159
183
  }
160
184
 
@@ -171,34 +195,39 @@ export class RenderedDocsFeature {
171
195
  /* Switches the comment the caret is in between formatted and source. False when the caret is in none. */
172
196
  toggle(): boolean {
173
197
  this.settle();
174
- const comment = this.at(this.language.editor.getCaret().line);
175
- if (!this.enabled || comment === undefined || !this.canRender(comment)) {
176
- return false;
177
- }
178
- this.setRendered(comment, !this.wantsRendered(comment));
179
- return true;
198
+
199
+ return this.switch(this.at(this.language.editor.getCaret().line));
180
200
  }
181
201
 
182
202
  /* Switches the comment that starts on a line, which is what its gutter button does. */
183
203
  toggleAt(startLine: number): boolean {
184
204
  this.settle();
185
- const comment = this.comments.find((candidate) => candidate.startLine === startLine);
205
+
206
+ return this.switch(this.comments.find((candidate) => candidate.startLine === startLine));
207
+ }
208
+
209
+ private switch(comment: DocComment | undefined): boolean {
186
210
  if (!this.enabled || comment === undefined || !this.canRender(comment)) {
187
211
  return false;
188
212
  }
213
+
189
214
  this.setRendered(comment, !this.wantsRendered(comment));
215
+
190
216
  return true;
191
217
  }
192
218
 
193
219
  /* The elements of the rows now in view, for a host that draws them itself; `RenderedDocs` does. */
194
220
  entries(): readonly RenderedDocEntry[] {
195
221
  const result: RenderedDocEntry[] = [];
222
+
196
223
  for (const [id, container] of this.containers) {
197
224
  const comment = this.comments[Number(id.slice(DOCS_PREFIX.length))];
225
+
198
226
  if (comment !== undefined) {
199
227
  result.push({ id, comment, parsed: this.parse(comment), container });
200
228
  }
201
229
  }
230
+
202
231
  return result;
203
232
  }
204
233
 
@@ -211,11 +240,13 @@ export class RenderedDocsFeature {
211
240
  if (this.shown?.version !== this.version) {
212
241
  this.shown = { version: this.version, entries: this.entries() };
213
242
  }
243
+
214
244
  return this.shown.entries;
215
245
  }
216
246
 
217
247
  subscribe(listener: () => void): () => void {
218
248
  this.listeners.add(listener);
249
+
219
250
  return () => {
220
251
  this.listeners.delete(listener);
221
252
  };
@@ -224,6 +255,7 @@ export class RenderedDocsFeature {
224
255
  /* Whether names in `{@link}` and `@see` can be followed, which takes a server that finds symbols by name. */
225
256
  get canFollow(): boolean {
226
257
  const { project, uri } = this.language;
258
+
227
259
  return project.service.supports('workspace/symbol', uri);
228
260
  }
229
261
 
@@ -248,8 +280,10 @@ export class RenderedDocsFeature {
248
280
  if (!this.enabled || !this.canRender(comment)) {
249
281
  return false;
250
282
  }
283
+
251
284
  const start = startOf(comment);
252
285
  const override = this.overrides.find((candidate) => samePlace(candidate.tracked.get(), start));
286
+
253
287
  return override?.rendered ?? this.settings.renderDocComments;
254
288
  }
255
289
 
@@ -264,17 +298,21 @@ export class RenderedDocsFeature {
264
298
  private setRendered(comment: DocComment, rendered: boolean): void {
265
299
  const start = startOf(comment);
266
300
  const existing = this.overrides.find((candidate) => samePlace(candidate.tracked.get(), start));
301
+
267
302
  if (existing !== undefined) {
268
303
  existing.tracked.dispose();
269
304
  this.overrides = this.overrides.filter((candidate) => candidate !== existing);
270
305
  }
306
+
271
307
  if (rendered !== this.settings.renderDocComments) {
272
308
  const { editor } = this.language;
273
309
  this.overrides.push({ tracked: editor.trackRange({ start, end: editor.positionAt(comment.to) }), rendered });
274
310
  }
311
+
275
312
  if (rendered) {
276
313
  this.leave(comment);
277
314
  }
315
+
278
316
  this.publish();
279
317
  }
280
318
 
@@ -282,15 +320,19 @@ export class RenderedDocsFeature {
282
320
  private leave(comment: DocComment): void {
283
321
  const { editor } = this.language;
284
322
  const selections = editor.getSelections();
323
+
285
324
  if (!selections.some((range) => touches(range, comment))) {
286
325
  return;
287
326
  }
327
+
288
328
  const count = editor.positionAt(Number.MAX_SAFE_INTEGER).line + 1;
289
329
  const line = comment.endLine + 1 < count ? comment.endLine + 1 : comment.startLine - 1;
330
+
290
331
  if (line < 0) {
291
332
  return;
292
333
  }
293
- const text = editor.textInRange({ start: { line, character: 0 }, end: { line, character: Number.MAX_SAFE_INTEGER } });
334
+
335
+ const text = lineTextOf(editor, line);
294
336
  const character = line > comment.endLine ? (/^[ \t]*/.exec(text)?.[0].length ?? 0) : text.length;
295
337
  const place = { line, character };
296
338
  editor.setSelections(selections.map((range) => (touches(range, comment) ? { start: place, end: place } : range)));
@@ -306,8 +348,10 @@ export class RenderedDocsFeature {
306
348
  if (this.revealed.size === 0) {
307
349
  return;
308
350
  }
351
+
309
352
  const { start, end } = this.language.editor.getVisibleRange();
310
353
  const away = [...this.revealed].filter((comment) => comment.endLine < start.line || comment.startLine > end.line);
354
+
311
355
  if (away.length > 0) {
312
356
  away.forEach((comment) => this.revealed.delete(comment));
313
357
  this.publish();
@@ -318,6 +362,7 @@ export class RenderedDocsFeature {
318
362
  for (const override of this.overrides) {
319
363
  override.tracked.dispose();
320
364
  }
365
+
321
366
  this.overrides = [];
322
367
  }
323
368
 
@@ -333,34 +378,42 @@ export class RenderedDocsFeature {
333
378
  if (this.disposed) {
334
379
  return;
335
380
  }
381
+
336
382
  this.stale = false;
337
383
  const { editor, languageId } = this.language;
338
384
  this.comments = findDocComments(editor.getText(), languageId);
339
385
  this.indexes = new Map(this.comments.map((comment, index) => [comment, index]));
340
386
  this.revealed.clear();
341
387
  const starts = this.comments.map(startOf);
388
+
342
389
  this.overrides = this.overrides.filter((override) => {
343
390
  const kept = starts.some((start) => samePlace(override.tracked.get(), start));
391
+
344
392
  if (!kept) {
345
393
  override.tracked.dispose();
346
394
  }
395
+
347
396
  return kept;
348
397
  });
398
+
349
399
  // An edit that replaced everything leaves the rows on offsets at the end, which only a new set of rows repairs.
350
400
  this.publish(true);
351
401
  this.bump();
352
402
  }
353
403
 
354
404
  private parse(comment: DocComment): ParsedDocComment {
355
- const key = `${comment.body}`;
405
+ const key = comment.body;
356
406
  let result = this.parsed.get(key);
407
+
357
408
  if (result === undefined) {
358
409
  if (this.parsed.size >= MAX_PARSED) {
359
410
  this.parsed.delete(this.parsed.keys().next().value!);
360
411
  }
412
+
361
413
  result = parseDocComment(comment.body, { php: /^php$/i.test(this.language.languageId) });
362
414
  this.parsed.set(key, result);
363
415
  }
416
+
364
417
  return result;
365
418
  }
366
419
 
@@ -369,13 +422,18 @@ export class RenderedDocsFeature {
369
422
  if (this.disposed || this.stale) {
370
423
  return;
371
424
  }
425
+
372
426
  const { editor } = this.language;
427
+
373
428
  const t = (key: string, options?: Record<string, string>): string => this.language.project.i18n.t(`editor:language.docs.${key}`, options);
429
+
374
430
  const rendered = this.comments.filter((comment) => this.isRendered(comment));
375
431
  const signature = rendered.map((comment) => `${this.idOf(comment)}@${comment.startLine}-${comment.endLine}`).join('|');
432
+
376
433
  if (force || signature !== this.drawn) {
377
434
  this.drawn = signature;
378
435
  const lineHeight = Math.max(editor.getBaseLineHeight?.() ?? 0, ESTIMATED_LINE_HEIGHT);
436
+
379
437
  const rows = rendered.map((comment): EditorReplacement => ({
380
438
  id: this.idOf(comment),
381
439
  startLine: comment.startLine,
@@ -385,23 +443,31 @@ export class RenderedDocsFeature {
385
443
  onReveal: () => this.reveal(comment),
386
444
  render: (container) => this.mounted(this.idOf(comment), container)
387
445
  }));
446
+
388
447
  const kept = new Set(rows.map((row) => row.id));
448
+
389
449
  for (const id of [...this.containers.keys()]) {
390
450
  if (!kept.has(id)) {
391
451
  this.containers.delete(id);
392
452
  }
393
453
  }
454
+
394
455
  editor.setReplacements?.(rows, OWNER);
395
456
  this.bump();
396
457
  }
458
+
397
459
  this.markerLines.clear();
460
+
398
461
  const markers = (this.enabled ? this.comments : []).map((comment) => {
399
462
  const id = `docs:${comment.startLine}`;
400
463
  this.markerLines.set(id, comment.startLine);
401
464
  const on = this.wantsRendered(comment);
465
+
402
466
  return { id, line: comment.startLine, label: t(on ? 'showSource' : 'render'), icon: 'book-open' as const, pressed: on };
403
467
  });
468
+
404
469
  const marking = markers.map((marker) => `${marker.line}${marker.pressed}`).join('|');
470
+
405
471
  if (marking !== this.marked) {
406
472
  this.marked = marking;
407
473
  editor.setGutterMarkers(markers, OWNER);
@@ -410,6 +476,7 @@ export class RenderedDocsFeature {
410
476
 
411
477
  private labelOf(comment: DocComment, t: (key: string, options?: Record<string, string>) => string): string {
412
478
  const summary = summaryOf(comment.body);
479
+
413
480
  return summary === '' ? t('label') : t('labelNamed', { summary });
414
481
  }
415
482
 
@@ -422,12 +489,14 @@ export class RenderedDocsFeature {
422
489
  /* The editor drops the element of a row that scrolled out of view, and what React drew into it goes with it. The element just made is not in the page yet. */
423
490
  private sweep(fresh?: HTMLElement): void {
424
491
  let removed = false;
492
+
425
493
  for (const [id, container] of this.containers) {
426
494
  if (container !== fresh && !container.isConnected) {
427
495
  this.containers.delete(id);
428
496
  removed = true;
429
497
  }
430
498
  }
499
+
431
500
  if (removed && fresh === undefined) {
432
501
  this.bump();
433
502
  }
@@ -435,6 +504,7 @@ export class RenderedDocsFeature {
435
504
 
436
505
  private bump(): void {
437
506
  this.version++;
507
+
438
508
  for (const listener of [...this.listeners]) {
439
509
  listener();
440
510
  }
@@ -17,6 +17,7 @@ export class ReviewGroup {
17
17
 
18
18
  join(member: ReviewMember): () => void {
19
19
  this.members.add(member);
20
+
20
21
  return () => {
21
22
  this.members.delete(member);
22
23
  };
@@ -50,14 +51,17 @@ export function joinReviewGroup(key: string): { group: ReviewGroup; leave(): voi
50
51
  groups.set(key, held);
51
52
  held.holders++;
52
53
  let left = false;
54
+
53
55
  return {
54
56
  group: held.group,
55
57
  leave: () => {
56
58
  if (left) {
57
59
  return;
58
60
  }
61
+
59
62
  left = true;
60
63
  held.holders--;
64
+
61
65
  if (held.holders === 0 && groups.get(key) === held) {
62
66
  groups.delete(key);
63
67
  }
package/src/row-host.ts CHANGED
@@ -40,11 +40,13 @@ abstract class ContainerHost {
40
40
  if (this.shown?.version !== this.version) {
41
41
  this.shown = { version: this.version, containers: new Map(this.containers) };
42
42
  }
43
+
43
44
  return this.shown.containers;
44
45
  }
45
46
 
46
47
  subscribe(listener: () => void): () => void {
47
48
  this.listeners.add(listener);
49
+
48
50
  return () => {
49
51
  this.listeners.delete(listener);
50
52
  };
@@ -59,13 +61,16 @@ abstract class ContainerHost {
59
61
  if (signature === this.signature) {
60
62
  return false;
61
63
  }
64
+
62
65
  this.signature = signature;
63
66
  const kept = new Set(ids);
67
+
64
68
  for (const id of [...this.containers.keys()]) {
65
69
  if (!kept.has(id)) {
66
70
  this.containers.delete(id);
67
71
  }
68
72
  }
73
+
69
74
  return true;
70
75
  }
71
76
 
@@ -76,6 +81,7 @@ abstract class ContainerHost {
76
81
 
77
82
  protected bump(): void {
78
83
  this.version++;
84
+
79
85
  for (const listener of [...this.listeners]) {
80
86
  listener();
81
87
  }
@@ -90,6 +96,7 @@ abstract class ContainerHost {
90
96
  export class RowHost extends ContainerHost {
91
97
  set(rows: readonly HostedRow[]): void {
92
98
  const signature = rows.map((row) => `${row.id}@${row.line}${row.placement}:${row.height}:${row.fixed}:${row.filler}`).join('|');
99
+
93
100
  if (
94
101
  !this.replace(
95
102
  signature,
@@ -98,6 +105,7 @@ export class RowHost extends ContainerHost {
98
105
  ) {
99
106
  return;
100
107
  }
108
+
101
109
  this.editor.setWidgets(
102
110
  rows.map((row) => ({
103
111
  id: row.filler ? `${row.id}-filler` : row.id,
@@ -115,6 +123,7 @@ export class RowHost extends ContainerHost {
115
123
  })),
116
124
  this.owner
117
125
  );
126
+
118
127
  this.bump();
119
128
  }
120
129
 
@@ -131,6 +140,7 @@ export class RowHost extends ContainerHost {
131
140
  export class LineActionHost extends ContainerHost {
132
141
  set(actions: readonly HostedAction[]): void {
133
142
  const signature = actions.map((action) => `${action.id}@${action.line}`).join('|');
143
+
134
144
  if (
135
145
  !this.replace(
136
146
  signature,
@@ -139,6 +149,7 @@ export class LineActionHost extends ContainerHost {
139
149
  ) {
140
150
  return;
141
151
  }
152
+
142
153
  this.editor.setLineActions(
143
154
  actions.map((action) => ({
144
155
  id: action.id,
@@ -147,6 +158,7 @@ export class LineActionHost extends ContainerHost {
147
158
  })),
148
159
  this.owner
149
160
  );
161
+
150
162
  this.bump();
151
163
  }
152
164
 
@@ -7,9 +7,11 @@ const METHOD = 'textDocument/selectionRange';
7
7
  /* The links of one answer, the smallest range first. */
8
8
  export function selectionChainOf(selection: SelectionRange): EditorRange[] {
9
9
  const chain: EditorRange[] = [];
10
+
10
11
  for (let link: SelectionRange | undefined = selection; link; link = link.parent) {
11
12
  chain.push(link.range);
12
13
  }
14
+
13
15
  return chain;
14
16
  }
15
17
 
@@ -20,13 +22,17 @@ export function selectionChainOf(selection: SelectionRange): EditorRange[] {
20
22
  export class SelectionRangesFeature {
21
23
  constructor(language: EditorLanguage) {
22
24
  const { editor, project, uri } = language;
25
+
23
26
  editor.setSelectionRanges(async (positions) => {
24
27
  if (!project.service.supports(METHOD, uri)) {
25
28
  return null;
26
29
  }
30
+
27
31
  const chains = await project.service.selectionRanges(uri, positions);
32
+
28
33
  return chains === null || chains.length !== positions.length ? null : chains.map(selectionChainOf);
29
34
  });
35
+
30
36
  language.onDispose(() => editor.setSelectionRanges(null));
31
37
  }
32
38
  }
@@ -8,22 +8,29 @@ import type { SemanticTokensDelta } from '@adecore/lsp';
8
8
  export function applySemanticEdits(data: readonly number[], edits: SemanticTokensDelta['edits']): number[] | null {
9
9
  const result: number[] = [];
10
10
  let cursor = 0;
11
+
11
12
  for (const edit of edits) {
12
13
  const { start, deleteCount } = edit;
13
14
  const inserted = edit.data ?? [];
15
+
14
16
  if (!Number.isInteger(start) || !Number.isInteger(deleteCount) || start < cursor || deleteCount < 0 || start + deleteCount > data.length) {
15
17
  return null;
16
18
  }
19
+
17
20
  for (let at = cursor; at < start; at++) {
18
21
  result.push(data[at]!);
19
22
  }
23
+
20
24
  for (const value of inserted) {
21
25
  result.push(value);
22
26
  }
27
+
23
28
  cursor = start + deleteCount;
24
29
  }
30
+
25
31
  for (let at = cursor; at < data.length; at++) {
26
32
  result.push(data[at]!);
27
33
  }
34
+
28
35
  return result.length % 5 === 0 ? result : null;
29
36
  }
@@ -34,9 +34,11 @@ export function scopesOf(type: string, modifiers: ReadonlySet<string>, scoped =
34
34
  if (modifiers.has('static')) {
35
35
  return ['entity.name.function.static'];
36
36
  }
37
+
37
38
  if (modifiers.has('declaration')) {
38
39
  return ['entity.name.function'];
39
40
  }
41
+
40
42
  return [type === 'method' ? 'entity.name.function.method' : 'entity.name.function.call'];
41
43
  case 'decorator':
42
44
  return ['meta.decorator'];
@@ -46,6 +48,7 @@ export function scopesOf(type: string, modifiers: ReadonlySet<string>, scoped =
46
48
  if (scoped) {
47
49
  return modifiers.has('local') ? null : ['variable.other.constant'];
48
50
  }
51
+
49
52
  return modifiers.has('readonly') ? ['variable.other.constant'] : null;
50
53
  default:
51
54
  return null;
@@ -71,25 +74,33 @@ export function decodeSemanticTokens(tokens: SemanticTokens, legend: SemanticTok
71
74
  const scoped = legend.tokenModifiers.includes('local');
72
75
  let line = 0;
73
76
  let character = 0;
77
+
74
78
  for (let at = 0; at + 4 < data.length; at += 5) {
75
79
  const deltaLine = data[at]!;
76
80
  line += deltaLine;
77
81
  character = deltaLine === 0 ? character + data[at + 1]! : data[at + 1]!;
78
82
  const type = legend.tokenTypes[data[at + 3]!];
83
+
79
84
  if (type === undefined) {
80
85
  continue;
81
86
  }
87
+
82
88
  const bits = data[at + 4]!;
83
89
  const modifiers = new Set(legend.tokenModifiers.filter((_, index) => (bits & (1 << index)) !== 0));
84
90
  const scopes = scopesOf(type, modifiers, scoped);
91
+
85
92
  if (scopes !== null) {
86
93
  result.push({ line, character, length: data[at + 2]!, scopes });
94
+
87
95
  continue;
88
96
  }
97
+
89
98
  const embedded = EMBEDDED_SCOPES[type];
99
+
90
100
  if (embedded !== undefined) {
91
101
  result.push({ line, character, length: data[at + 2]!, scopes: embedded, within: 'string' });
92
102
  }
93
103
  }
104
+
94
105
  return result;
95
106
  }