@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
@@ -20,177 +20,216 @@ export const DEFAULT_RANGE_CHARACTERS = 500_000;
20
20
  * for the lines in view first when the document is large, and for them alone when it offers nothing else.
21
21
  */
22
22
  export class SemanticTokensFeature {
23
- refresher;
24
- scrolled;
25
- language;
26
- options;
27
- held = null;
28
- heldLegend = '';
29
- /* True once the whole document is drawn from a full answer; a range answer then no longer paints. */
30
- complete = false;
31
- /* The lines a range answer drew, null when nothing of that kind is on screen. */
32
- covered = null;
33
- drawn = [];
34
- epoch = 0;
35
- constructor(language, timers = realTimers, options = {}) {
36
- const { editor } = language;
37
- this.language = language;
38
- this.options = options;
39
- this.refresher = new Refresher((signal) => this.refresh(signal), PAUSE_MS, timers);
40
- this.scrolled = new Refresher((signal) => this.fillViewport(signal), SCROLL_PAUSE_MS, timers);
41
- const edits = editor.onTextChange(() => {
42
- this.epoch++;
43
- this.covered = null;
44
- this.refresher.later();
45
- });
46
- const view = editor.onViewChange(() => {
47
- if (!this.complete && this.covered !== null && !covers(this.covered, editor.getVisibleRange())) {
48
- this.scrolled.later();
49
- }
50
- });
51
- const providers = language.onProvidersChanged(() => {
52
- this.epoch++;
53
- this.scrolled.cancel();
54
- this.forget();
55
- this.refresher.now();
56
- });
57
- language.onDispose(() => {
58
- edits();
59
- view();
60
- providers();
61
- this.refresher.dispose();
62
- this.scrolled.dispose();
63
- this.forget();
64
- editor.setSemanticTokens(null);
65
- });
23
+ refresher;
24
+ scrolled;
25
+ language;
26
+ options;
27
+ held = null;
28
+ heldLegend = '';
29
+ /* True once the whole document is drawn from a full answer; a range answer then no longer paints. */
30
+ complete = false;
31
+ /* The lines a range answer drew, null when nothing of that kind is on screen. */
32
+ covered = null;
33
+ drawn = [];
34
+ epoch = 0;
35
+ constructor(language, timers = realTimers, options = {}) {
36
+ const {
37
+ editor
38
+ } = language;
39
+ this.language = language;
40
+ this.options = options;
41
+ this.refresher = new Refresher(signal => this.refresh(signal), PAUSE_MS, timers);
42
+ this.scrolled = new Refresher(signal => this.fillViewport(signal), SCROLL_PAUSE_MS, timers);
43
+ const edits = editor.onTextChange(() => {
44
+ this.epoch++;
45
+ this.covered = null;
46
+ this.refresher.later();
47
+ });
48
+ const view = editor.onViewChange(() => {
49
+ if (!this.complete && this.covered !== null && !covers(this.covered, editor.getVisibleRange())) {
50
+ this.scrolled.later();
51
+ }
52
+ });
53
+ const providers = language.onProvidersChanged(() => {
54
+ this.epoch++;
55
+ this.scrolled.cancel();
56
+ this.forget();
57
+ this.refresher.now();
58
+ });
59
+ language.onDispose(() => {
60
+ edits();
61
+ view();
62
+ providers();
63
+ this.refresher.dispose();
64
+ this.scrolled.dispose();
65
+ this.forget();
66
+ editor.setSemanticTokens(null);
67
+ });
68
+ }
69
+ /* Drops what the next delta would be computed against, which a refresh, a restart or a new legend makes meaningless. */
70
+ forget() {
71
+ this.held = null;
72
+ this.complete = false;
73
+ this.covered = null;
74
+ }
75
+ legend() {
76
+ const {
77
+ service
78
+ } = this.language.project;
79
+ const {
80
+ uri
81
+ } = this.language;
82
+ const legend = (service.providerOptions(FULL, uri) ?? service.providerOptions(RANGE, uri))?.legend;
83
+ const key = legend === undefined ? '' : JSON.stringify(legend);
84
+ if (key !== this.heldLegend) {
85
+ this.heldLegend = key;
86
+ this.forget();
66
87
  }
67
- /* Drops what the next delta would be computed against, which a refresh, a restart or a new legend makes meaningless. */
68
- forget() {
69
- this.held = null;
70
- this.complete = false;
71
- this.covered = null;
88
+ return legend;
89
+ }
90
+ isLarge() {
91
+ const {
92
+ editor
93
+ } = this.language;
94
+ const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
95
+ return end.line + 1 > (this.options.rangeLines ?? DEFAULT_RANGE_LINES) || editor.offsetAt(end) > (this.options.rangeCharacters ?? DEFAULT_RANGE_CHARACTERS);
96
+ }
97
+ viewport() {
98
+ const {
99
+ editor
100
+ } = this.language;
101
+ return withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), line => ({
102
+ line,
103
+ character: lineTextOf(editor, line).length
104
+ }));
105
+ }
106
+ async refresh(signal) {
107
+ const {
108
+ service
109
+ } = this.language.project;
110
+ const {
111
+ editor,
112
+ uri
113
+ } = this.language;
114
+ const legend = this.legend();
115
+ const full = service.supports(FULL, uri);
116
+ const range = service.supports(RANGE, uri);
117
+ if (legend === undefined || !full && !range) {
118
+ return;
72
119
  }
73
- legend() {
74
- const { service } = this.language.project;
75
- const { uri } = this.language;
76
- const legend = (service.providerOptions(FULL, uri) ?? service.providerOptions(RANGE, uri))?.legend;
77
- const key = legend === undefined ? '' : JSON.stringify(legend);
78
- if (key !== this.heldLegend) {
79
- this.heldLegend = key;
80
- this.forget();
81
- }
82
- return legend;
83
- }
84
- isLarge() {
85
- const { editor } = this.language;
86
- const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
87
- return (end.line + 1 > (this.options.rangeLines ?? DEFAULT_RANGE_LINES) || editor.offsetAt(end) > (this.options.rangeCharacters ?? DEFAULT_RANGE_CHARACTERS));
120
+ if (!full || range && !this.complete && this.held === null && this.isLarge()) {
121
+ await this.askRange(this.viewport(), legend, signal, true);
122
+ if (!full) {
123
+ return;
124
+ }
88
125
  }
89
- viewport() {
90
- const { editor } = this.language;
91
- return withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), (line) => ({
92
- line,
93
- character: lineTextOf(editor, line).length
94
- }));
126
+ const epoch = this.epoch;
127
+ const tokens = await this.askFull(signal);
128
+ if (signal.aborted || epoch !== this.epoch) {
129
+ return;
95
130
  }
96
- async refresh(signal) {
97
- const { service } = this.language.project;
98
- const { editor, uri } = this.language;
99
- const legend = this.legend();
100
- const full = service.supports(FULL, uri);
101
- const range = service.supports(RANGE, uri);
102
- if (legend === undefined || (!full && !range)) {
103
- return;
104
- }
105
- if (!full || (range && !this.complete && this.held === null && this.isLarge())) {
106
- await this.askRange(this.viewport(), legend, signal, true);
107
- if (!full) {
108
- return;
109
- }
131
+ this.complete = tokens !== null;
132
+ this.covered = null;
133
+ this.drawn = [];
134
+ editor.setSemanticTokens(tokens === null ? null : decodeSemanticTokens(tokens, legend));
135
+ }
136
+ /* The whole document: as a delta when the last answer can be built on, else in full. Null when the server has no tokens. */
137
+ async askFull(signal) {
138
+ const {
139
+ service
140
+ } = this.language.project;
141
+ const {
142
+ uri
143
+ } = this.language;
144
+ const base = this.held;
145
+ if (base !== null && service.supports(DELTA, uri)) {
146
+ const answer = await service.semanticTokensDelta(uri, base.resultId, {
147
+ signal
148
+ }).catch(error => {
149
+ if (signal.aborted || error instanceof StaleResultError) {
150
+ throw error;
110
151
  }
111
- const epoch = this.epoch;
112
- const tokens = await this.askFull(signal);
113
- if (signal.aborted || epoch !== this.epoch) {
114
- return;
152
+ // A server that no longer knows our result id is asked in full instead, once.
153
+ return null;
154
+ });
155
+ signal.throwIfAborted();
156
+ if (answer !== null && this.held === base) {
157
+ const tokens = this.fromDelta(base, answer);
158
+ if (tokens !== null) {
159
+ this.hold(tokens);
160
+ return tokens;
115
161
  }
116
- this.complete = tokens !== null;
117
- this.covered = null;
118
- this.drawn = [];
119
- editor.setSemanticTokens(tokens === null ? null : decodeSemanticTokens(tokens, legend));
162
+ }
163
+ this.held = null;
120
164
  }
121
- /* The whole document: as a delta when the last answer can be built on, else in full. Null when the server has no tokens. */
122
- async askFull(signal) {
123
- const { service } = this.language.project;
124
- const { uri } = this.language;
125
- const base = this.held;
126
- if (base !== null && service.supports(DELTA, uri)) {
127
- const answer = await service.semanticTokensDelta(uri, base.resultId, { signal }).catch((error) => {
128
- if (signal.aborted || error instanceof StaleResultError) {
129
- throw error;
130
- }
131
- // A server that no longer knows our result id is asked in full instead, once.
132
- return null;
133
- });
134
- signal.throwIfAborted();
135
- if (answer !== null && this.held === base) {
136
- const tokens = this.fromDelta(base, answer);
137
- if (tokens !== null) {
138
- this.hold(tokens);
139
- return tokens;
140
- }
141
- }
142
- this.held = null;
143
- }
144
- const tokens = await service.semanticTokens(uri, { signal });
145
- signal.throwIfAborted();
146
- this.hold(tokens);
147
- return tokens;
165
+ const tokens = await service.semanticTokens(uri, {
166
+ signal
167
+ });
168
+ signal.throwIfAborted();
169
+ this.hold(tokens);
170
+ return tokens;
171
+ }
172
+ /* Null when the answer is a delta that does not fit the data it should be applied to. */
173
+ fromDelta(base, answer) {
174
+ if (!('edits' in answer)) {
175
+ return answer;
148
176
  }
149
- /* Null when the answer is a delta that does not fit the data it should be applied to. */
150
- fromDelta(base, answer) {
151
- if (!('edits' in answer)) {
152
- return answer;
153
- }
154
- const data = applySemanticEdits(base.data, answer.edits);
155
- return data === null ? null : { ...(answer.resultId === undefined ? {} : { resultId: answer.resultId }), data };
177
+ const data = applySemanticEdits(base.data, answer.edits);
178
+ return data === null ? null : {
179
+ ...(answer.resultId === undefined ? {} : {
180
+ resultId: answer.resultId
181
+ }),
182
+ data
183
+ };
184
+ }
185
+ hold(tokens) {
186
+ this.held = tokens?.resultId === undefined ? null : {
187
+ resultId: tokens.resultId,
188
+ data: tokens.data
189
+ };
190
+ }
191
+ /* Draws the lines of a range answer. A fresh one replaces everything drawn, an extension keeps the lines it did not cover. */
192
+ async askRange(range, legend, signal, fresh) {
193
+ const {
194
+ service
195
+ } = this.language.project;
196
+ const {
197
+ editor,
198
+ uri
199
+ } = this.language;
200
+ const epoch = this.epoch;
201
+ const before = this.covered;
202
+ const tokens = await service.semanticTokensRange(uri, range, {
203
+ signal
204
+ });
205
+ if (signal.aborted || epoch !== this.epoch || this.complete) {
206
+ return;
156
207
  }
157
- hold(tokens) {
158
- this.held = tokens?.resultId === undefined ? null : { resultId: tokens.resultId, data: tokens.data };
208
+ const decoded = tokens === null ? [] : decodeSemanticTokens(tokens, legend);
209
+ if (fresh || before === null || range.start.line > before.end.line + 1 || range.end.line < before.start.line - 1) {
210
+ this.covered = range;
211
+ this.drawn = decoded;
212
+ } else {
213
+ const kept = this.drawn.filter(token => token.line < range.start.line || token.line > range.end.line);
214
+ this.drawn = [...kept, ...decoded];
215
+ this.covered = {
216
+ start: before.start.line < range.start.line ? before.start : range.start,
217
+ end: before.end.line > range.end.line ? before.end : range.end
218
+ };
159
219
  }
160
- /* Draws the lines of a range answer. A fresh one replaces everything drawn, an extension keeps the lines it did not cover. */
161
- async askRange(range, legend, signal, fresh) {
162
- const { service } = this.language.project;
163
- const { editor, uri } = this.language;
164
- const epoch = this.epoch;
165
- const before = this.covered;
166
- const tokens = await service.semanticTokensRange(uri, range, { signal });
167
- if (signal.aborted || epoch !== this.epoch || this.complete) {
168
- return;
169
- }
170
- const decoded = tokens === null ? [] : decodeSemanticTokens(tokens, legend);
171
- if (fresh || before === null || range.start.line > before.end.line + 1 || range.end.line < before.start.line - 1) {
172
- this.covered = range;
173
- this.drawn = decoded;
174
- }
175
- else {
176
- const kept = this.drawn.filter((token) => token.line < range.start.line || token.line > range.end.line);
177
- this.drawn = [...kept, ...decoded];
178
- this.covered = {
179
- start: before.start.line < range.start.line ? before.start : range.start,
180
- end: before.end.line > range.end.line ? before.end : range.end
181
- };
182
- }
183
- editor.setSemanticTokens(this.drawn);
184
- if (!this.complete && this.covered !== null && !covers(this.covered, editor.getVisibleRange())) {
185
- this.scrolled.later();
186
- }
220
+ editor.setSemanticTokens(this.drawn);
221
+ if (!this.complete && this.covered !== null && !covers(this.covered, editor.getVisibleRange())) {
222
+ this.scrolled.later();
187
223
  }
188
- async fillViewport(signal) {
189
- const { service } = this.language.project;
190
- const legend = this.legend();
191
- if (legend === undefined || !service.supports(RANGE, this.language.uri) || this.complete) {
192
- return;
193
- }
194
- await this.askRange(this.viewport(), legend, signal, false);
224
+ }
225
+ async fillViewport(signal) {
226
+ const {
227
+ service
228
+ } = this.language.project;
229
+ const legend = this.legend();
230
+ if (legend === undefined || !service.supports(RANGE, this.language.uri) || this.complete) {
231
+ return;
195
232
  }
196
- }
233
+ await this.askRange(this.viewport(), legend, signal, false);
234
+ }
235
+ }
@@ -1,16 +1,22 @@
1
1
  import { chordOf, KEYMAP_IDS, parseChord, typesInsteadOfChord } from '@adecore/editor/keymap';
2
2
  import { matchesShortcut } from '@adecore/ui';
3
3
  export function shortcutsOf(keymap, apple) {
4
- const shortcuts = Object.fromEntries(KEYMAP_IDS.map((id) => {
5
- const text = chordOf(id, apple, keymap);
6
- return [id, text === null ? null : { ...parseChord(text), apple }];
7
- }));
8
- return { ...shortcuts, rename: shortcuts.renameSymbol };
4
+ const shortcuts = Object.fromEntries(KEYMAP_IDS.map(id => {
5
+ const text = chordOf(id, apple, keymap);
6
+ return [id, text === null ? null : {
7
+ ...parseChord(text),
8
+ apple
9
+ }];
10
+ }));
11
+ return {
12
+ ...shortcuts,
13
+ rename: shortcuts.renameSymbol
14
+ };
9
15
  }
10
16
  /* Ctrl and Alt together that type a character, as AltGr does on some layouts, are no shortcut. */
11
17
  export function isShortcut(target, event) {
12
- if (target === null || typesInsteadOfChord(event, target.apple)) {
13
- return false;
14
- }
15
- return matchesShortcut(target, event, target.apple);
16
- }
18
+ if (target === null || typesInsteadOfChord(event, target.apple)) {
19
+ return false;
20
+ }
21
+ return matchesShortcut(target, event, target.apple);
22
+ }
@@ -1,33 +1,39 @@
1
1
  import { documentationText } from './completion-model.js';
2
2
  /* Where a parameter's label stands in the signature's: given as a pair of offsets, or as the text to find in it. */
3
3
  export function parameterSpan(signature, index) {
4
- const parameter = signature.parameters?.[index];
5
- if (parameter === undefined) {
6
- return null;
7
- }
8
- if (typeof parameter.label !== 'string') {
9
- const [start, end] = parameter.label;
10
- return start >= 0 && end <= signature.label.length && start < end ? { start, end } : null;
11
- }
12
- const start = signature.label.indexOf(parameter.label);
13
- return start < 0 ? null : { start, end: start + parameter.label.length };
4
+ const parameter = signature.parameters?.[index];
5
+ if (parameter === undefined) {
6
+ return null;
7
+ }
8
+ if (typeof parameter.label !== 'string') {
9
+ const [start, end] = parameter.label;
10
+ return start >= 0 && end <= signature.label.length && start < end ? {
11
+ start,
12
+ end
13
+ } : null;
14
+ }
15
+ const start = signature.label.indexOf(parameter.label);
16
+ return start < 0 ? null : {
17
+ start,
18
+ end: start + parameter.label.length
19
+ };
14
20
  }
15
21
  export function signatureViewOf(help) {
16
- if (help === null || help.signatures.length === 0) {
17
- return null;
18
- }
19
- const index = Math.min(Math.max(0, help.activeSignature ?? 0), help.signatures.length - 1);
20
- const signature = help.signatures[index];
21
- const parameterIndex = signature.activeParameter ?? help.activeParameter ?? 0;
22
- const active = parameterSpan(signature, parameterIndex);
23
- const parameter = signature.parameters?.[parameterIndex];
24
- return {
25
- label: signature.label,
26
- active,
27
- parameterName: active === null ? '' : signature.label.slice(active.start, active.end).replace(/[?:].*$/s, ''),
28
- parameterDocumentation: documentationText(parameter?.documentation),
29
- documentation: documentationText(signature.documentation),
30
- index,
31
- count: help.signatures.length
32
- };
33
- }
22
+ if (help === null || help.signatures.length === 0) {
23
+ return null;
24
+ }
25
+ const index = Math.min(Math.max(0, help.activeSignature ?? 0), help.signatures.length - 1);
26
+ const signature = help.signatures[index];
27
+ const parameterIndex = signature.activeParameter ?? help.activeParameter ?? 0;
28
+ const active = parameterSpan(signature, parameterIndex);
29
+ const parameter = signature.parameters?.[parameterIndex];
30
+ return {
31
+ label: signature.label,
32
+ active,
33
+ parameterName: active === null ? '' : signature.label.slice(active.start, active.end).replace(/[?:].*$/s, ''),
34
+ parameterDocumentation: documentationText(parameter?.documentation),
35
+ documentation: documentationText(signature.documentation),
36
+ index,
37
+ count: help.signatures.length
38
+ };
39
+ }