@adecore/editor-react 0.39.0 → 0.40.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 (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
@@ -26,8 +26,10 @@ const PAUSE_MS = 250;
26
26
  const INVOKED = 1;
27
27
  const AUTOMATIC = 2;
28
28
  const TOAST_ID = 'language-action';
29
+
29
30
  /* How many problems of the line are asked for their fixes besides the caret's own request. */
30
31
  const LINE_PROBLEMS = 3;
32
+
31
33
  /* The command of the PHP server that asks the client to rename the name a refactor just wrote; the server answers it with nothing. */
32
34
  const INLINE_RENAME_COMMAND = 'php.rename';
33
35
 
@@ -66,17 +68,21 @@ interface AskOptions {
66
68
  export class CodeActionsFeature {
67
69
  private readonly language: EditorLanguage;
68
70
  private readonly refresher: Refresher;
71
+
69
72
  /* While a list is being asked for, the lightbulb waits: both are the same request and the newer one would cancel the older. */
70
73
  private asking = false;
74
+
71
75
  private readonly resolved = new Map<ActionEntry, CodeAction>();
72
76
 
73
77
  constructor(language: EditorLanguage, timers: Timers = realTimers) {
74
78
  this.language = language;
75
79
  const { editor } = language;
76
80
  this.refresher = new Refresher((signal) => this.hint(signal), PAUSE_MS, timers);
81
+
77
82
  const providers = language.onProvidersChanged(() => {
78
83
  this.refresher.later();
79
84
  });
85
+
80
86
  const offs = [
81
87
  editor.onCaret(() => {
82
88
  editor.setGutterAction(null);
@@ -89,21 +95,27 @@ export class CodeActionsFeature {
89
95
  language.onKey((event) => {
90
96
  if (isShortcut(this.language.shortcuts.codeActions, event)) {
91
97
  void this.open();
98
+
92
99
  return true;
93
100
  }
101
+
94
102
  if (isShortcut(this.language.shortcuts.refactorThis, event)) {
95
103
  void this.refactorThis();
104
+
96
105
  return true;
97
106
  }
107
+
98
108
  return false;
99
109
  }, KEY_PRIORITY.codeActions),
100
110
  language.diagnostics.onChange(() => this.refresher.later()),
101
111
  providers
102
112
  ];
113
+
103
114
  language.onDispose(() => {
104
115
  for (const off of offs) {
105
116
  off();
106
117
  }
118
+
107
119
  this.refresher.dispose();
108
120
  editor.setGutterAction(null);
109
121
  });
@@ -116,18 +128,21 @@ export class CodeActionsFeature {
116
128
  /* The list under the caret, or under the selection's start. */
117
129
  open(): Promise<void> {
118
130
  const range = this.language.editor.getSelection();
131
+
119
132
  return this.ask({ range, anchor: range.start, withLine: true });
120
133
  }
121
134
 
122
135
  /* Refactor This: only the refactorings, grouped by what they do, under the caret or the selection. */
123
136
  refactorThis(): Promise<void> {
124
137
  const range = this.language.editor.getSelection();
138
+
125
139
  return this.ask({ range, only: ['refactor'], anchor: range.start, groups: REFACTOR_GROUPS });
126
140
  }
127
141
 
128
142
  /* The quick fixes of one problem, under it, as the Quick fix button of its card asks. */
129
143
  quickFixFor(problem: Problem): Promise<void> {
130
144
  const { range } = problem.diagnostic;
145
+
131
146
  return this.ask({ range, only: ['quickfix'], anchor: range.start, diagnostics: [problem.diagnostic] });
132
147
  }
133
148
 
@@ -136,21 +151,28 @@ export class CodeActionsFeature {
136
151
  const everything = { start: { line: 0, character: 0 }, end: editor.positionAt(Number.MAX_SAFE_INTEGER) };
137
152
  const entries = await this.request(everything, INVOKED, { only: ['source.organizeImports'] });
138
153
  const entry = entries?.find((candidate) => candidate.action.kind?.startsWith('source.organizeImports') === true);
154
+
139
155
  if (entry === undefined) {
140
156
  this.tell('success', this.say(entries === null ? 'unavailable' : 'organized'));
157
+
141
158
  return;
142
159
  }
160
+
143
161
  await this.apply(entry);
144
162
  }
145
163
 
146
164
  async formatDocument(): Promise<void> {
147
165
  const { editor, project, uri } = this.language;
166
+
148
167
  if (!project.service.supports('textDocument/formatting', uri)) {
149
168
  this.tell('error', this.say('noFormatter'));
169
+
150
170
  return;
151
171
  }
172
+
152
173
  try {
153
174
  const edits = await project.service.formatting(uri, { ...editor.getIndentation() });
175
+
154
176
  if (edits !== null && edits.length > 0 && !editor.applyEdits(edits.map((edit) => ({ range: edit.range, text: edit.newText })))) {
155
177
  this.tell('error', this.say('readOnly'));
156
178
  }
@@ -164,18 +186,22 @@ export class CodeActionsFeature {
164
186
  if (!this.supported) {
165
187
  return Promise.resolve(null);
166
188
  }
189
+
167
190
  return this.request(this.language.editor.getSelection(), AUTOMATIC, { only, order: only.includes('refactor') ? REFACTOR_GROUPS : ACTION_GROUPS });
168
191
  }
169
192
 
170
193
  private async ask(options: AskOptions): Promise<void> {
171
194
  if (!this.supported) {
172
195
  this.tell('error', this.say('unavailable'));
196
+
173
197
  return;
174
198
  }
199
+
175
200
  // The press on a card or the gutter may have taken the focus, and the list answers to the editor's keys.
176
201
  this.language.editor.focus();
177
202
  this.asking = true;
178
203
  let entries: ActionEntry[] | null;
204
+
179
205
  try {
180
206
  entries = options.withLine
181
207
  ? await this.requestWithLine(options.range, INVOKED)
@@ -183,24 +209,33 @@ export class CodeActionsFeature {
183
209
  } finally {
184
210
  this.asking = false;
185
211
  }
212
+
186
213
  if (entries === null) {
187
214
  return;
188
215
  }
216
+
189
217
  if (entries.length === 0) {
190
218
  this.tell('success', this.say('none'));
219
+
191
220
  return;
192
221
  }
222
+
193
223
  const byId = new Map(entries.map((entry) => [entry.id, entry]));
224
+
194
225
  const groups: PickGroup[] = (options.groups ?? ACTION_GROUPS).flatMap((group): PickGroup[] => {
195
226
  const rows = entries.filter((entry) => entry.group === group).map((entry) => ({ id: entry.id, label: entry.action.title, detail: '' }));
227
+
196
228
  return rows.length === 0 ? [] : [{ title: this.say(`groups.${group}`), rows }];
197
229
  });
230
+
198
231
  this.language.completion.close();
232
+
199
233
  this.language.pick.open({
200
234
  anchor: options.anchor,
201
235
  groups,
202
236
  accept: (id) => {
203
237
  const entry = byId.get(id);
238
+
204
239
  if (entry !== undefined) {
205
240
  void this.apply(entry);
206
241
  }
@@ -214,6 +249,7 @@ export class CodeActionsFeature {
214
249
  private async request(range: EditorRange, triggerKind: number, options: RequestOptions = {}): Promise<ActionEntry[] | null> {
215
250
  const { project, uri, diagnostics } = this.language;
216
251
  const { only, signal, order } = options;
252
+
217
253
  try {
218
254
  const about =
219
255
  options.about ??
@@ -221,18 +257,22 @@ export class CodeActionsFeature {
221
257
  diagnostics.problems.map((problem) => problem.diagnostic),
222
258
  range
223
259
  );
260
+
224
261
  const context = {
225
262
  diagnostics: [...about],
226
263
  ...(only ? { only: [...only] } : {}),
227
264
  triggerKind: triggerKind as 1 | 2
228
265
  };
266
+
229
267
  const entries = actionsOf(await project.service.codeActions(uri, range, context, { signal }), order);
268
+
230
269
  // A server may answer with more than the kinds asked for.
231
270
  return only ? entries.filter((entry) => only.some((kind) => entry.action.kind === kind || entry.action.kind?.startsWith(`${kind}.`))) : entries;
232
271
  } catch (error) {
233
272
  if (triggerKind === INVOKED) {
234
273
  this.fail(error);
235
274
  }
275
+
236
276
  return null;
237
277
  }
238
278
  }
@@ -243,42 +283,56 @@ export class CodeActionsFeature {
243
283
  */
244
284
  private async requestWithLine(range: EditorRange, triggerKind: number, signal?: AbortSignal): Promise<ActionEntry[] | null> {
245
285
  const entries = await this.request(range, triggerKind, { signal });
286
+
246
287
  if (entries === null || range.start.line !== range.end.line) {
247
288
  return entries;
248
289
  }
290
+
249
291
  const all = this.language.diagnostics.problems.map((problem) => problem.diagnostic);
292
+
250
293
  if (diagnosticsAt(all, range).length > 0) {
251
294
  return entries;
252
295
  }
296
+
253
297
  const onLine = fixableOnLine(all, range.start.line).slice(0, LINE_PROBLEMS);
298
+
254
299
  const fixes = await Promise.all(
255
300
  onLine.map((diagnostic) => this.request(diagnostic.range, triggerKind, { only: ['quickfix'], signal, about: [diagnostic] }))
256
301
  );
302
+
257
303
  return mergeEntries([entries, ...fixes.map((list) => list ?? [])]);
258
304
  }
259
305
 
260
306
  private async hint(signal: AbortSignal): Promise<void> {
261
307
  const { editor } = this.language;
308
+
262
309
  if (this.asking || !this.supported) {
263
310
  editor.setGutterAction(null);
311
+
264
312
  return;
265
313
  }
314
+
266
315
  const range = editor.getSelection();
267
316
  const entries = await this.requestWithLine(range, AUTOMATIC, signal);
317
+
268
318
  if (signal.aborted || this.asking) {
269
319
  return;
270
320
  }
321
+
271
322
  editor.setGutterAction(entries?.some(isHint) === true ? { line: editor.getCaret().line, label: this.say('show') } : null);
272
323
  }
273
324
 
274
325
  private async resolve(entry: ActionEntry, signal?: AbortSignal): Promise<CodeAction> {
275
326
  const known = this.resolved.get(entry);
327
+
276
328
  if (known !== undefined) {
277
329
  return known;
278
330
  }
331
+
279
332
  const { project, uri } = this.language;
280
333
  const { action } = entry;
281
334
  let result = action;
335
+
282
336
  if (action.edit === undefined && project.service.providerOptions(METHOD, uri)?.resolveProvider === true) {
283
337
  try {
284
338
  result = await project.service.resolveCodeAction(uri, action, { signal });
@@ -286,13 +340,16 @@ export class CodeActionsFeature {
286
340
  if (isRefusal(error)) {
287
341
  throw new ActionRefused((error as Error).message);
288
342
  }
343
+
289
344
  // An action the server cannot resolve is still tried as it is, unless the text moved under it.
290
345
  if (error instanceof StaleResultError) {
291
346
  throw error;
292
347
  }
293
348
  }
294
349
  }
350
+
295
351
  this.resolved.set(entry, result);
352
+
296
353
  return result;
297
354
  }
298
355
 
@@ -300,27 +357,38 @@ export class CodeActionsFeature {
300
357
  if (entry === undefined) {
301
358
  return null;
302
359
  }
360
+
303
361
  let action: CodeAction;
362
+
304
363
  try {
305
364
  action = await this.resolve(entry, signal);
306
365
  } catch (error) {
307
366
  if (error instanceof ActionRefused) {
308
367
  return { removed: [], added: [], note: null, refusal: error.message };
309
368
  }
369
+
310
370
  throw error;
311
371
  }
372
+
312
373
  const { editor, uri } = this.language;
313
374
  const found = action.edit === undefined ? null : previewOf(editor.getText(), action.edit, uri);
375
+
314
376
  if (found === null) {
315
377
  return null;
316
378
  }
317
- const notes = [
318
- ...(found.hiddenLines > 0 ? [this.say('moreLines', { count: found.hiddenLines })] : []),
319
- ...(found.otherFiles > 0 ? [this.say('otherFiles', { count: found.otherFiles })] : []),
320
- ...(found.moves > 0 ? [this.say('moves', { count: found.moves })] : []),
321
- ...((found.creates ?? 0) > 0 ? [this.say('creates', { count: found.creates })] : []),
322
- ...((found.deletes ?? 0) > 0 ? [this.say('deletes', { count: found.deletes })] : [])
323
- ];
379
+
380
+ const counts = {
381
+ moreLines: found.hiddenLines,
382
+ otherFiles: found.otherFiles,
383
+ moves: found.moves,
384
+ creates: found.creates ?? 0,
385
+ deletes: found.deletes ?? 0
386
+ };
387
+
388
+ const notes = Object.entries(counts)
389
+ .filter(([, count]) => count > 0)
390
+ .map(([key, count]) => this.say(key, { count }));
391
+
324
392
  return { removed: found.removed, added: found.added, note: notes.length === 0 ? null : notes.join(' · ') };
325
393
  }
326
394
 
@@ -329,8 +397,10 @@ export class CodeActionsFeature {
329
397
  if (entry === undefined) {
330
398
  return null;
331
399
  }
400
+
332
401
  try {
333
402
  await this.resolve(entry);
403
+
334
404
  return null;
335
405
  } catch (error) {
336
406
  return error instanceof ActionRefused ? error.message : null;
@@ -340,15 +410,20 @@ export class CodeActionsFeature {
340
410
  /* Resolves the action, makes its edit as one undo step, and then runs its command. */
341
411
  async apply(entry: ActionEntry): Promise<void> {
342
412
  const { editor, project, uri } = this.language;
413
+
343
414
  try {
344
415
  const action = await this.resolve(entry);
416
+
345
417
  if (action.edit !== undefined) {
346
418
  const result = await project.applyWorkspaceEdit(action.edit);
419
+
347
420
  if (!result.applied) {
348
421
  this.tell('error', this.say('failed', { message: result.failureReason ?? this.say('refused') }));
422
+
349
423
  return;
350
424
  }
351
425
  }
426
+
352
427
  if (action.command?.command === INLINE_RENAME_COMMAND) {
353
428
  await this.startRename(action.command);
354
429
  } else if (action.command !== undefined) {
@@ -365,6 +440,7 @@ export class CodeActionsFeature {
365
440
  /* The name a refactor wrote is offered for a rename right where it stands, the way the platform does after an extraction. */
366
441
  private async startRename(command: Command): Promise<void> {
367
442
  const target = (command.arguments?.[0] ?? null) as { textDocument?: { uri?: string }; position?: Position } | null;
443
+
368
444
  if (target?.position !== undefined && target.textDocument?.uri === this.language.uri) {
369
445
  this.language.editor.setCaret(target.position);
370
446
  await this.language.rename.start();
@@ -13,23 +13,29 @@ export const UNCOMMITTED = -1;
13
13
  */
14
14
  export function mapBlame(blame: GitBlameResult, base: string, current: readonly string[]): Int32Array | null {
15
15
  const baseLines = splitLines(base);
16
+
16
17
  if (blame.lines.length !== baseLines.length) {
17
18
  return null;
18
19
  }
20
+
19
21
  const mapped = new Int32Array(current.length).fill(UNCOMMITTED);
20
22
  let basePosition = 0;
21
23
  let position = 0;
24
+
22
25
  const carry = (until: number): void => {
23
26
  for (; position < until; position++, basePosition++) {
24
27
  mapped[position] = blame.lines[basePosition]!;
25
28
  }
26
29
  };
30
+
27
31
  for (const change of diffLines(baseLines, current)) {
28
32
  carry(change.otherStart);
29
33
  position = change.otherEnd;
30
34
  basePosition = change.baseEnd;
31
35
  }
36
+
32
37
  carry(current.length);
38
+
33
39
  return mapped;
34
40
  }
35
41
 
@@ -65,40 +71,54 @@ export function authorshipOf(commits: readonly GitBlameCommit[], mapped: Int32Ar
65
71
  let uncommittedLines = 0;
66
72
  let modified = false;
67
73
  let latest: GitBlameCommit | null = null;
74
+
68
75
  for (let line = from; line <= to && line < mapped.length; line++) {
69
76
  const index = mapped[line]!;
77
+
70
78
  if (index === UNCOMMITTED) {
71
79
  modified = true;
72
80
  }
81
+
73
82
  if (lines[line]!.trim() === '') {
74
83
  continue;
75
84
  }
85
+
76
86
  if (index === UNCOMMITTED) {
77
87
  uncommittedLines++;
88
+
78
89
  continue;
79
90
  }
91
+
80
92
  const commit = commits[index]!;
81
93
  const name = shortName(commit.author);
94
+
82
95
  if (name === '') {
83
96
  continue;
84
97
  }
98
+
85
99
  const author = byName.get(name) ?? { name, email: commit.email, lines: 0 };
86
100
  author.lines++;
87
101
  byName.set(name, author);
102
+
88
103
  if (latest === null || commit.at > latest.at) {
89
104
  latest = commit;
90
105
  }
91
106
  }
107
+
92
108
  const authors = [...byName.values()].sort((left, right) => right.lines - left.lines || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
109
+
93
110
  return { authors, uncommittedLines, modified, latest };
94
111
  }
95
112
 
96
113
  /* The row's words: the author with most lines, how many others wrote some, and a star when the range is edited since the commit. */
97
114
  export function authorsText(authorship: CodeAuthorship, i18n: i18n): string {
98
115
  const main = authorship.authors[0];
116
+
99
117
  if (main === undefined) {
100
118
  return i18n.t('editor:language.codeVision.newCode');
101
119
  }
120
+
102
121
  const others = authorship.authors.length - 1;
122
+
103
123
  return `${main.name}${others > 0 ? ` +${formatNumber(others)}` : ''}${authorship.modified ? ' *' : ''}`;
104
124
  }
@@ -7,6 +7,7 @@ export const MAX_DECLARATIONS = 3000;
7
7
 
8
8
  /* LSP's SymbolKind: the kinds a row is drawn for and the ones whose children can be declarations too. */
9
9
  const CLASS = 5;
10
+
10
11
  const METHOD = 6;
11
12
  const PROPERTY = 7;
12
13
  const FIELD = 8;
@@ -62,6 +63,7 @@ function isUsageKind(kind: number, parentKind: number | null): boolean {
62
63
  if (TYPES.has(kind) || CALLABLES.has(kind)) {
63
64
  return true;
64
65
  }
66
+
65
67
  return (kind === PROPERTY || kind === FIELD || kind === CONSTANT) && (parentKind === CLASS || parentKind === STRUCT);
66
68
  }
67
69
 
@@ -80,6 +82,7 @@ function walk(symbols: readonly DocumentSymbol[], path: readonly string[], paren
80
82
  path,
81
83
  parentKind
82
84
  });
85
+
83
86
  // What a function or a method holds is local to it, and a row for a callback is noise.
84
87
  if (CONTAINERS.has(symbol.kind)) {
85
88
  walk(symbol.children ?? [], [...path, symbol.name], symbol.kind, out);
@@ -89,20 +92,27 @@ function walk(symbols: readonly DocumentSymbol[], path: readonly string[], paren
89
92
 
90
93
  function candidatesOf(result: DocumentSymbolResult): Candidate[] {
91
94
  const out: Candidate[] = [];
95
+
92
96
  if (result === null || result.length === 0) {
93
97
  return out;
94
98
  }
99
+
95
100
  if ('range' in result[0]!) {
96
101
  walk(result as DocumentSymbol[], [], null, out);
102
+
97
103
  return out;
98
104
  }
105
+
99
106
  const flat = result as SymbolInformation[];
100
107
  const containers = new Map(flat.filter((symbol) => CONTAINERS.has(symbol.kind)).map((symbol) => [symbol.name, symbol.kind]));
108
+
101
109
  for (const symbol of flat) {
102
110
  const container = symbol.containerName ?? '';
111
+
103
112
  if (container !== '' && !containers.has(container)) {
104
113
  continue;
105
114
  }
115
+
106
116
  out.push({
107
117
  name: symbol.name,
108
118
  kind: symbol.kind,
@@ -113,6 +123,7 @@ function candidatesOf(result: DocumentSymbolResult): Candidate[] {
113
123
  parentKind: container === '' ? null : (containers.get(container) ?? null)
114
124
  });
115
125
  }
126
+
116
127
  return out;
117
128
  }
118
129
 
@@ -124,38 +135,51 @@ export function startAfterComments(source: DeclarationSource, from: Position, to
124
135
  const hash = HASH_COMMENTS.has(source.languageId);
125
136
  let line = from.line;
126
137
  let character = from.character;
138
+
127
139
  while (line <= to.line && line < source.lineCount) {
128
140
  const text = source.lineAt(line);
129
141
  let at = character;
142
+
130
143
  while (at < text.length && (text[at] === ' ' || text[at] === '\t')) {
131
144
  at++;
132
145
  }
146
+
133
147
  if (at >= text.length) {
134
148
  line++;
135
149
  character = 0;
150
+
136
151
  continue;
137
152
  }
153
+
138
154
  if (text.startsWith('/*', at)) {
139
155
  let end = text.indexOf('*/', at + 2);
140
156
  let scan = line;
157
+
141
158
  while (end === -1 && scan + 1 <= to.line && scan + 1 < source.lineCount) {
142
159
  scan++;
143
160
  end = source.lineAt(scan).indexOf('*/');
144
161
  }
162
+
145
163
  if (end === -1) {
146
164
  return null;
147
165
  }
166
+
148
167
  line = scan;
149
168
  character = end + 2;
169
+
150
170
  continue;
151
171
  }
172
+
152
173
  if (text.startsWith('//', at) || (hash && text[at] === '#' && text[at + 1] !== '[')) {
153
174
  line++;
154
175
  character = 0;
176
+
155
177
  continue;
156
178
  }
179
+
157
180
  return line > to.line ? null : { line, character: at };
158
181
  }
182
+
159
183
  return null;
160
184
  }
161
185
 
@@ -168,12 +192,14 @@ function lastLineWithCode(source: DeclarationSource, from: number, to: number):
168
192
  return line;
169
193
  }
170
194
  }
195
+
171
196
  return from;
172
197
  }
173
198
 
174
199
  /* Where the name starts when a server gave only a range: its first occurrence on the line the declaration starts on. */
175
200
  function nameIn(source: DeclarationSource, name: string, start: Position): Position | null {
176
201
  const at = source.lineAt(start.line).indexOf(name, start.character);
202
+
177
203
  return at === -1 ? null : { line: start.line, character: at };
178
204
  }
179
205
 
@@ -185,26 +211,35 @@ function nameIn(source: DeclarationSource, name: string, start: Position): Posit
185
211
  export function declarationsOf(result: DocumentSymbolResult, source: DeclarationSource): CodeVisionDeclaration[] {
186
212
  const declarations: CodeVisionDeclaration[] = [];
187
213
  const seen = new Map<string, number>();
214
+
188
215
  for (const candidate of candidatesOf(result)) {
189
216
  if (isLocalName(candidate.name)) {
190
217
  continue;
191
218
  }
219
+
192
220
  const usages = isUsageKind(candidate.kind, candidate.parentKind) && !candidate.name.startsWith('__');
193
221
  const authors = isAuthorKind(candidate.kind);
222
+
194
223
  if (!usages && !authors) {
195
224
  continue;
196
225
  }
226
+
197
227
  const start = startAfterComments(source, candidate.start, candidate.end);
228
+
198
229
  if (start === null || source.lineAt(start.line).slice(0, start.character).trim() !== '') {
199
230
  continue;
200
231
  }
232
+
201
233
  const position = candidate.namePosition ?? nameIn(source, candidate.name, start);
234
+
202
235
  if (position === null) {
203
236
  continue;
204
237
  }
238
+
205
239
  const key = `${candidate.path.join('>')}>${candidate.name}#${candidate.kind}`;
206
240
  const index = seen.get(key) ?? 0;
207
241
  seen.set(key, index + 1);
242
+
208
243
  declarations.push({
209
244
  id: `${key}#${index}`,
210
245
  name: candidate.name,
@@ -215,10 +250,12 @@ export function declarationsOf(result: DocumentSymbolResult, source: Declaration
215
250
  authorFrom: position.line,
216
251
  authorTo: lastLineWithCode(source, position.line, candidate.end.line)
217
252
  });
253
+
218
254
  if (declarations.length > MAX_DECLARATIONS) {
219
255
  return [];
220
256
  }
221
257
  }
258
+
222
259
  return declarations;
223
260
  }
224
261