@adecore/editor-react 0.31.12 → 0.33.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
@@ -14,96 +14,112 @@ const DOUBLE_PRESS_MS = 500;
14
14
  const MARGIN_LINES = 60;
15
15
  /* A hint's label is a string or a list of parts, which are read as one text. */
16
16
  export function hintLabelOf(hint) {
17
- return typeof hint.label === 'string' ? hint.label : hint.label.map((part) => part.value).join('');
17
+ return typeof hint.label === 'string' ? hint.label : hint.label.map(part => part.value).join('');
18
18
  }
19
19
  /* The hints worth drawing, in the order of the text. */
20
20
  export function drawableHints(hints) {
21
- return hints
22
- .filter((hint) => hintLabelOf(hint) !== '')
23
- .sort((left, right) => left.position.line - right.position.line || left.position.character - right.position.character);
21
+ return hints.filter(hint => hintLabelOf(hint) !== '').sort((left, right) => left.position.line - right.position.line || left.position.character - right.position.character);
24
22
  }
25
23
  /* A hint as the editor draws it: a label with parts keeps them apart so each can be pointed at. */
26
24
  export function editorHintOf(hint, id) {
27
- return {
28
- id,
29
- position: hint.position,
30
- label: hintLabelOf(hint),
31
- ...(typeof hint.label === 'string' ? {} : { parts: hint.label.map((part) => part.value) }),
32
- ...(hint.paddingLeft ? { paddingLeft: true } : {}),
33
- ...(hint.paddingRight ? { paddingRight: true } : {})
34
- };
25
+ return {
26
+ id,
27
+ position: hint.position,
28
+ label: hintLabelOf(hint),
29
+ ...(typeof hint.label === 'string' ? {} : {
30
+ parts: hint.label.map(part => part.value)
31
+ }),
32
+ ...(hint.paddingLeft ? {
33
+ paddingLeft: true
34
+ } : {}),
35
+ ...(hint.paddingRight ? {
36
+ paddingRight: true
37
+ } : {})
38
+ };
35
39
  }
36
40
  export function editorHintsOf(hints) {
37
- return drawableHints(hints).map((hint, index) => editorHintOf(hint, `hint-${index}`));
41
+ return drawableHints(hints).map((hint, index) => editorHintOf(hint, `hint-${index}`));
38
42
  }
39
43
  /* The lines in view with a margin of lines around them, from the start of the first to the end of the last. */
40
44
  export function withMargin(visible, endOfDocument, endOfLine) {
41
- const first = Math.max(0, visible.start.line - MARGIN_LINES);
42
- const last = Math.min(endOfDocument.line, visible.end.line + MARGIN_LINES);
43
- return { start: { line: first, character: 0 }, end: last === endOfDocument.line ? endOfDocument : endOfLine(last) };
45
+ const first = Math.max(0, visible.start.line - MARGIN_LINES);
46
+ const last = Math.min(endOfDocument.line, visible.end.line + MARGIN_LINES);
47
+ return {
48
+ start: {
49
+ line: first,
50
+ character: 0
51
+ },
52
+ end: last === endOfDocument.line ? endOfDocument : endOfLine(last)
53
+ };
44
54
  }
45
55
  /* Whether what was asked for already holds every line in view. */
46
56
  export function covers(asked, visible) {
47
- return asked.start.line <= visible.start.line && asked.end.line >= visible.end.line;
57
+ return asked.start.line <= visible.start.line && asked.end.line >= visible.end.line;
48
58
  }
49
59
  /* The words of a tooltip: plain text is escaped so it reads as typed, Markdown goes through the hover card's own rendering. */
50
60
  export function tooltipTextOf(tooltip) {
51
- if (tooltip === undefined) {
52
- return null;
53
- }
54
- const text = hoverTextOf({ contents: typeof tooltip === 'string' ? { kind: 'plaintext', value: tooltip } : tooltip });
55
- return isEmptyHover(text) ? null : text;
61
+ if (tooltip === undefined) {
62
+ return null;
63
+ }
64
+ const text = hoverTextOf({
65
+ contents: typeof tooltip === 'string' ? {
66
+ kind: 'plaintext',
67
+ value: tooltip
68
+ } : tooltip
69
+ });
70
+ return isEmptyHover(text) ? null : text;
56
71
  }
57
72
  /* One part of a hint's label, or null for a hint whose label is a string. */
58
73
  export function partOf(hint, index) {
59
- return index === null || typeof hint.label === 'string' ? null : (hint.label[index] ?? null);
74
+ return index === null || typeof hint.label === 'string' ? null : hint.label[index] ?? null;
60
75
  }
61
76
  /* What a hover shows: the part's tooltip, else the hint's, else (with no part to point at) that of the first part that has one. */
62
77
  export function tooltipOf(hint, index) {
63
- const part = partOf(hint, index);
64
- const own = part?.tooltip ?? hint.tooltip;
65
- if (own !== undefined || index !== null || typeof hint.label === 'string') {
66
- return tooltipTextOf(own);
67
- }
68
- return tooltipTextOf(hint.label.find((candidate) => candidate.tooltip !== undefined)?.tooltip);
78
+ const part = partOf(hint, index);
79
+ const own = part?.tooltip ?? hint.tooltip;
80
+ if (own !== undefined || index !== null || typeof hint.label === 'string') {
81
+ return tooltipTextOf(own);
82
+ }
83
+ return tooltipTextOf(hint.label.find(candidate => candidate.tooltip !== undefined)?.tooltip);
69
84
  }
70
85
  /* Whether a server could still say more about the hint: nothing in it gives a tooltip, a place or a command yet. */
71
86
  export function lacksInfo(hint, index) {
72
- if (hint.tooltip !== undefined) {
73
- return false;
74
- }
75
- const parts = typeof hint.label === 'string' ? [] : index === null ? hint.label : [hint.label[index]];
76
- return !parts.some((part) => part !== undefined && (part.tooltip !== undefined || part.location !== undefined || part.command !== undefined));
87
+ if (hint.tooltip !== undefined) {
88
+ return false;
89
+ }
90
+ const parts = typeof hint.label === 'string' ? [] : index === null ? hint.label : [hint.label[index]];
91
+ return !parts.some(part => part !== undefined && (part.tooltip !== undefined || part.location !== undefined || part.command !== undefined));
77
92
  }
78
93
  /* Whether the edits fit the text as it is now: positions inside it, each range in order, none overlapping the next. */
79
94
  export function editsFit(editor, edits) {
80
- const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
81
- const inside = (position) => Number.isInteger(position.line) &&
82
- Number.isInteger(position.character) &&
83
- position.line >= 0 &&
84
- position.character >= 0 &&
85
- position.line <= end.line &&
86
- position.character <= lineTextOf(editor, position.line).length;
87
- const before = (left, right) => left.line < right.line || (left.line === right.line && left.character <= right.character);
88
- const ordered = [...edits].sort((left, right) => (before(left.range.start, right.range.start) ? -1 : 1));
89
- return ordered.every((edit, index) => {
90
- const next = ordered[index + 1];
91
- return (inside(edit.range.start) &&
92
- inside(edit.range.end) &&
93
- before(edit.range.start, edit.range.end) &&
94
- (next === undefined || before(edit.range.end, next.range.start)));
95
- });
95
+ const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
96
+ const inside = position => Number.isInteger(position.line) && Number.isInteger(position.character) && position.line >= 0 && position.character >= 0 && position.line <= end.line && position.character <= lineTextOf(editor, position.line).length;
97
+ const before = (left, right) => left.line < right.line || left.line === right.line && left.character <= right.character;
98
+ const ordered = [...edits].sort((left, right) => before(left.range.start, right.range.start) ? -1 : 1);
99
+ return ordered.every((edit, index) => {
100
+ const next = ordered[index + 1];
101
+ return inside(edit.range.start) && inside(edit.range.end) && before(edit.range.start, edit.range.end) && (next === undefined || before(edit.range.end, next.range.start));
102
+ });
96
103
  }
97
104
  /* What a press does, from what is known of the hint: a double click inserts it and nothing else, Mod+click follows a part's place, a plain click runs its command. */
98
105
  export function actionOf(hint, click) {
99
- if (click.clicks === 2) {
100
- return hint.textEdits !== undefined && hint.textEdits.length > 0 ? { kind: 'insert', edits: hint.textEdits } : null;
101
- }
102
- const part = partOf(hint, click.part);
103
- if (click.mod) {
104
- return part?.location === undefined ? null : { kind: 'go', location: part.location };
105
- }
106
- return part?.command === undefined ? null : { kind: 'run', command: part.command };
106
+ if (click.clicks === 2) {
107
+ return hint.textEdits !== undefined && hint.textEdits.length > 0 ? {
108
+ kind: 'insert',
109
+ edits: hint.textEdits
110
+ } : null;
111
+ }
112
+ const part = partOf(hint, click.part);
113
+ if (click.mod) {
114
+ return part?.location === undefined ? null : {
115
+ kind: 'go',
116
+ location: part.location
117
+ };
118
+ }
119
+ return part?.command === undefined ? null : {
120
+ kind: 'run',
121
+ command: part.command
122
+ };
107
123
  }
108
124
  /*
109
125
  * Types and parameter names the servers infer, asked for the lines in view and a margin around them. The
@@ -120,289 +136,318 @@ export function actionOf(hint, click) {
120
136
  * sits there.
121
137
  */
122
138
  export class InlayHintsFeature {
123
- language;
124
- timers;
125
- refresher;
126
- /* The lines the hints on screen were asked for. */
127
- asked = null;
128
- entries = new Map();
129
- shown = [];
130
- generation = 0;
131
- /* Counts the edits of the text, which hints and answers made for an earlier one are tested against. */
132
- epoch = 0;
133
- target = null;
134
- modHeld = false;
135
- showTimer;
136
- /* The command of a click that may still become a double click. */
137
- commandTimer;
138
- /* Counts presses, so one that waited for a resolve gives way to a later one. */
139
- pressCount = 0;
140
- /* Counts what the pointer does, so a tooltip that was waited for is dropped once the pointer has moved on. */
141
- request = 0;
142
- constructor(language, timers = realTimers) {
143
- this.language = language;
144
- this.timers = timers;
145
- const { editor, project, uri } = language;
146
- this.refresher = new Refresher(async (signal) => {
147
- const { service } = project;
148
- if (!service.supports(METHOD, uri)) {
149
- return;
150
- }
151
- const range = withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), (line) => ({
152
- line,
153
- character: lineTextOf(editor, line).length
154
- }));
155
- this.asked = range;
156
- const epoch = this.epoch;
157
- const hints = await service.inlayHints(uri, range, { signal });
158
- if (!signal.aborted && epoch === this.epoch) {
159
- this.install(hints === null ? [] : drawableHints(hints), epoch);
160
- }
161
- }, PAUSE_MS, timers);
162
- const offs = [
163
- editor.onTextChange(() => {
164
- this.epoch++;
165
- this.cancelHover();
166
- this.refresher.later();
167
- }),
168
- editor.onViewChange(() => {
169
- if (this.asked !== null && !covers(this.asked, editor.getVisibleRange())) {
170
- this.refresher.later();
171
- }
172
- }),
173
- language.onProvidersChanged(() => {
174
- this.refresher.now();
175
- })
176
- ];
177
- if (editor.onInlayHover !== undefined) {
178
- offs.push(editor.onInlayHover((target) => this.hovered(target === null ? null : { id: target.id, part: target.part })));
179
- }
180
- if (editor.onInlayClick !== undefined) {
181
- offs.push(editor.onInlayClick((click) => this.pressed(click)));
182
- }
183
- if (typeof window !== 'undefined') {
184
- const modKey = language.apple ? 'Meta' : 'Control';
185
- const keys = (event) => {
186
- if (event.key === modKey) {
187
- this.setModHeld(event.type === 'keydown');
188
- }
189
- };
190
- const lost = () => this.setModHeld(false);
191
- window.addEventListener('keydown', keys);
192
- window.addEventListener('keyup', keys);
193
- window.addEventListener('blur', lost);
194
- offs.push(() => {
195
- window.removeEventListener('keydown', keys);
196
- window.removeEventListener('keyup', keys);
197
- window.removeEventListener('blur', lost);
198
- });
199
- }
200
- language.onDispose(() => {
201
- for (const off of offs) {
202
- off();
203
- }
204
- this.cancelHover();
205
- this.timers.clear(this.commandTimer);
206
- this.refresher.dispose();
207
- this.entries.clear();
208
- editor.setInlayHints([]);
209
- });
139
+ language;
140
+ timers;
141
+ refresher;
142
+ /* The lines the hints on screen were asked for. */
143
+ asked = null;
144
+ entries = new Map();
145
+ shown = [];
146
+ generation = 0;
147
+ /* Counts the edits of the text, which hints and answers made for an earlier one are tested against. */
148
+ epoch = 0;
149
+ target = null;
150
+ modHeld = false;
151
+ showTimer;
152
+ /* The command of a click that may still become a double click. */
153
+ commandTimer;
154
+ /* Counts presses, so one that waited for a resolve gives way to a later one. */
155
+ pressCount = 0;
156
+ /* Counts what the pointer does, so a tooltip that was waited for is dropped once the pointer has moved on. */
157
+ request = 0;
158
+ constructor(language, timers = realTimers) {
159
+ this.language = language;
160
+ this.timers = timers;
161
+ const {
162
+ editor,
163
+ project,
164
+ uri
165
+ } = language;
166
+ this.refresher = new Refresher(async signal => {
167
+ const {
168
+ service
169
+ } = project;
170
+ if (!service.supports(METHOD, uri)) {
171
+ return;
172
+ }
173
+ const range = withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), line => ({
174
+ line,
175
+ character: lineTextOf(editor, line).length
176
+ }));
177
+ this.asked = range;
178
+ const epoch = this.epoch;
179
+ const hints = await service.inlayHints(uri, range, {
180
+ signal
181
+ });
182
+ if (!signal.aborted && epoch === this.epoch) {
183
+ this.install(hints === null ? [] : drawableHints(hints), epoch);
184
+ }
185
+ }, PAUSE_MS, timers);
186
+ const offs = [editor.onTextChange(() => {
187
+ this.epoch++;
188
+ this.cancelHover();
189
+ this.refresher.later();
190
+ }), editor.onViewChange(() => {
191
+ if (this.asked !== null && !covers(this.asked, editor.getVisibleRange())) {
192
+ this.refresher.later();
193
+ }
194
+ }), language.onProvidersChanged(() => {
195
+ this.refresher.now();
196
+ })];
197
+ if (editor.onInlayHover !== undefined) {
198
+ offs.push(editor.onInlayHover(target => this.hovered(target === null ? null : {
199
+ id: target.id,
200
+ part: target.part
201
+ })));
210
202
  }
211
- /* The key that follows a place is down or up; it comes from the window, as the editor only says where the pointer is. */
212
- setModHeld(held) {
213
- if (held !== this.modHeld) {
214
- this.modHeld = held;
215
- this.updateLink();
216
- }
203
+ if (editor.onInlayClick !== undefined) {
204
+ offs.push(editor.onInlayClick(click => this.pressed(click)));
217
205
  }
218
- /* The tooltip of a hint that sits at a position, as a card; false when there is none. This is how the keyboard reaches it. */
219
- async showTooltipAt(position) {
220
- const found = [...this.entries].find(([, entry]) => entry.epoch === this.epoch && sameSpot(entry.hint.position, position));
221
- if (found === undefined) {
222
- return false;
206
+ if (typeof window !== 'undefined') {
207
+ const modKey = language.apple ? 'Meta' : 'Control';
208
+ const keys = event => {
209
+ if (event.key === modKey) {
210
+ this.setModHeld(event.type === 'keydown');
223
211
  }
224
- return this.showTooltip({ id: found[0], part: null }, this.request, false);
212
+ };
213
+ const lost = () => this.setModHeld(false);
214
+ window.addEventListener('keydown', keys);
215
+ window.addEventListener('keyup', keys);
216
+ window.addEventListener('blur', lost);
217
+ offs.push(() => {
218
+ window.removeEventListener('keydown', keys);
219
+ window.removeEventListener('keyup', keys);
220
+ window.removeEventListener('blur', lost);
221
+ });
225
222
  }
226
- install(hints, epoch) {
227
- this.generation++;
228
- this.entries.clear();
229
- this.shown = hints.map((hint, index) => {
230
- const id = `hint-${this.generation}-${index}`;
231
- this.entries.set(id, { id, hint, epoch, resolution: null, answered: false });
232
- return editorHintOf(hint, id);
233
- });
234
- this.language.editor.setInlayHints(this.shown);
235
- this.target = null;
236
- this.updateLink();
223
+ language.onDispose(() => {
224
+ for (const off of offs) {
225
+ off();
226
+ }
227
+ this.cancelHover();
228
+ this.timers.clear(this.commandTimer);
229
+ this.refresher.dispose();
230
+ this.entries.clear();
231
+ editor.setInlayHints([]);
232
+ });
233
+ }
234
+ /* The key that follows a place is down or up; it comes from the window, as the editor only says where the pointer is. */
235
+ setModHeld(held) {
236
+ if (held !== this.modHeld) {
237
+ this.modHeld = held;
238
+ this.updateLink();
237
239
  }
238
- hovered(target) {
239
- this.target = target;
240
- this.timers.clear(this.showTimer);
241
- const request = ++this.request;
242
- this.updateLink();
243
- if (target === null) {
244
- if (this.cardOpen()) {
245
- this.language.hover.release();
246
- }
247
- return;
248
- }
249
- this.showTimer = this.timers.set(() => void this.showTooltip(target, request), SHOW_DELAY_MS);
250
- // A press has to know what the hint offers when it comes, so the answer is asked for as the pointer arrives.
251
- const entry = this.entries.get(target.id);
252
- if (entry !== undefined && this.resolvable(entry)) {
253
- void this.resolve(entry).then(() => this.updateLink());
254
- }
240
+ }
241
+ /* The tooltip of a hint that sits at a position, as a card; false when there is none. This is how the keyboard reaches it. */
242
+ async showTooltipAt(position) {
243
+ const found = [...this.entries].find(([, entry]) => entry.epoch === this.epoch && sameSpot(entry.hint.position, position));
244
+ if (found === undefined) {
245
+ return false;
255
246
  }
256
- cancelHover() {
257
- this.timers.clear(this.showTimer);
258
- this.request++;
259
- this.target = null;
260
- this.updateLink();
261
- if (this.cardOpen()) {
262
- this.language.hover.hide();
263
- }
247
+ return this.showTooltip({
248
+ id: found[0],
249
+ part: null
250
+ }, this.request, false);
251
+ }
252
+ install(hints, epoch) {
253
+ this.generation++;
254
+ this.entries.clear();
255
+ this.shown = hints.map((hint, index) => {
256
+ const id = `hint-${this.generation}-${index}`;
257
+ this.entries.set(id, {
258
+ id,
259
+ hint,
260
+ epoch,
261
+ resolution: null,
262
+ answered: false
263
+ });
264
+ return editorHintOf(hint, id);
265
+ });
266
+ this.language.editor.setInlayHints(this.shown);
267
+ this.target = null;
268
+ this.updateLink();
269
+ }
270
+ hovered(target) {
271
+ this.target = target;
272
+ this.timers.clear(this.showTimer);
273
+ const request = ++this.request;
274
+ this.updateLink();
275
+ if (target === null) {
276
+ if (this.cardOpen()) {
277
+ this.language.hover.release();
278
+ }
279
+ return;
264
280
  }
265
- /* Whether the card up is a tooltip, which is the only kind that has no footer. */
266
- cardOpen() {
267
- return this.language.popups.getState().hover?.info?.plain === true;
281
+ this.showTimer = this.timers.set(() => void this.showTooltip(target, request), SHOW_DELAY_MS);
282
+ // A press has to know what the hint offers when it comes, so the answer is asked for as the pointer arrives.
283
+ const entry = this.entries.get(target.id);
284
+ if (entry !== undefined && this.resolvable(entry)) {
285
+ void this.resolve(entry).then(() => this.updateLink());
268
286
  }
269
- /* The card for a hint or part, once the server has said what it can. Says whether it opened. */
270
- async showTooltip(target, request, pointer = true) {
271
- const entry = this.entries.get(target.id);
272
- if (entry === undefined) {
273
- return false;
274
- }
275
- if (lacksInfo(entry.hint, target.part)) {
276
- await this.resolve(entry);
277
- this.updateLink();
278
- }
279
- if ((pointer && request !== this.request) || entry.epoch !== this.epoch) {
280
- return false;
281
- }
282
- const text = tooltipOf(entry.hint, target.part);
283
- if (text === null) {
284
- if (this.cardOpen()) {
285
- this.language.hover.hide();
286
- }
287
- return false;
288
- }
289
- this.language.hover.showText(entry.hint.position, text);
290
- return true;
287
+ }
288
+ cancelHover() {
289
+ this.timers.clear(this.showTimer);
290
+ this.request++;
291
+ this.target = null;
292
+ this.updateLink();
293
+ if (this.cardOpen()) {
294
+ this.language.hover.hide();
291
295
  }
292
- /* Asks the server for more of a hint, once; what it says is kept only while the text is the one the hint was made for. */
293
- resolve(entry) {
294
- if (entry.resolution !== null) {
295
- return entry.resolution;
296
- }
297
- const { service } = this.language.project;
298
- if (entry.epoch !== this.epoch || !service.supports(RESOLVE_METHOD, this.language.uri)) {
299
- return Promise.resolve(entry.hint);
300
- }
301
- entry.resolution = service
302
- .resolveInlayHint(this.language.uri, entry.hint)
303
- .then((resolved) => {
304
- if (entry.epoch === this.epoch) {
305
- entry.hint = { ...entry.hint, ...resolved, position: entry.hint.position };
306
- }
307
- return entry.hint;
308
- })
309
- .catch(() => entry.hint)
310
- .finally(() => {
311
- entry.answered = true;
312
- });
313
- return entry.resolution;
296
+ }
297
+ /* Whether the card up is a tooltip, which is the only kind that has no footer. */
298
+ cardOpen() {
299
+ return this.language.popups.getState().hover?.info?.plain === true;
300
+ }
301
+ /* The card for a hint or part, once the server has said what it can. Says whether it opened. */
302
+ async showTooltip(target, request, pointer = true) {
303
+ const entry = this.entries.get(target.id);
304
+ if (entry === undefined) {
305
+ return false;
314
306
  }
315
- /* Whether asking the server could still change what the hint offers. */
316
- resolvable(entry) {
317
- return !entry.answered && entry.epoch === this.epoch && this.language.project.service.supports(RESOLVE_METHOD, this.language.uri);
307
+ if (lacksInfo(entry.hint, target.part)) {
308
+ await this.resolve(entry);
309
+ this.updateLink();
318
310
  }
319
- /* Underlines the part under the pointer when a press would do something with it: a command always, a place while the key is down. */
320
- updateLink() {
321
- const { target } = this;
322
- const entry = target === null ? undefined : this.entries.get(target.id);
323
- const part = entry === undefined || target === null || entry.epoch !== this.epoch ? null : partOf(entry.hint, target.part);
324
- const link = part !== null && (part.command !== undefined || (this.modHeld && part.location !== undefined));
325
- this.language.editor.setInlayLink?.(link ? target : null);
311
+ if (pointer && request !== this.request || entry.epoch !== this.epoch) {
312
+ return false;
326
313
  }
327
- pressed(click) {
328
- const entry = this.entries.get(click.id);
329
- if (entry === undefined || entry.epoch !== this.epoch) {
330
- return false;
331
- }
332
- if (click.touch) {
333
- this.timers.clear(this.showTimer);
334
- const request = ++this.request;
335
- const worth = tooltipOf(entry.hint, click.part) !== null || this.resolvable(entry);
336
- if (worth) {
337
- void this.showTooltip({ id: click.id, part: click.part }, request);
338
- }
339
- return worth;
340
- }
341
- if (click.alt || click.shift) {
342
- return false;
343
- }
344
- this.timers.clear(this.commandTimer);
345
- const press = ++this.pressCount;
346
- if (this.resolvable(entry) && (click.clicks === 2 || actionOf(entry.hint, click) !== null)) {
347
- // The server may still add edits, which would make this the first press of a double click.
348
- void this.resolve(entry).then(() => {
349
- if (press === this.pressCount) {
350
- this.press(entry, click);
351
- }
352
- });
353
- return true;
354
- }
355
- return this.press(entry, click);
314
+ const text = tooltipOf(entry.hint, target.part);
315
+ if (text === null) {
316
+ if (this.cardOpen()) {
317
+ this.language.hover.hide();
318
+ }
319
+ return false;
356
320
  }
357
- press(entry, click) {
358
- this.timers.clear(this.commandTimer);
359
- const action = actionOf(entry.hint, click);
360
- const insertable = entry.hint.textEdits !== undefined && entry.hint.textEdits.length > 0;
361
- if (action?.kind === 'run' && insertable) {
362
- // A double click inserts the edits, so the command of its first press waits to see whether a second one comes.
363
- const epoch = this.epoch;
364
- this.commandTimer = this.timers.set(() => {
365
- if (epoch === this.epoch) {
366
- void this.perform(entry, action);
367
- }
368
- }, DOUBLE_PRESS_MS);
369
- return true;
370
- }
371
- if (action !== null) {
372
- void this.perform(entry, action);
373
- return true;
321
+ this.language.hover.showText(entry.hint.position, text);
322
+ return true;
323
+ }
324
+ /* Asks the server for more of a hint, once; what it says is kept only while the text is the one the hint was made for. */
325
+ resolve(entry) {
326
+ if (entry.resolution !== null) {
327
+ return entry.resolution;
328
+ }
329
+ const {
330
+ service
331
+ } = this.language.project;
332
+ if (entry.epoch !== this.epoch || !service.supports(RESOLVE_METHOD, this.language.uri)) {
333
+ return Promise.resolve(entry.hint);
334
+ }
335
+ entry.resolution = service.resolveInlayHint(this.language.uri, entry.hint).then(resolved => {
336
+ if (entry.epoch === this.epoch) {
337
+ entry.hint = {
338
+ ...entry.hint,
339
+ ...resolved,
340
+ position: entry.hint.position
341
+ };
342
+ }
343
+ return entry.hint;
344
+ }).catch(() => entry.hint).finally(() => {
345
+ entry.answered = true;
346
+ });
347
+ return entry.resolution;
348
+ }
349
+ /* Whether asking the server could still change what the hint offers. */
350
+ resolvable(entry) {
351
+ return !entry.answered && entry.epoch === this.epoch && this.language.project.service.supports(RESOLVE_METHOD, this.language.uri);
352
+ }
353
+ /* Underlines the part under the pointer when a press would do something with it: a command always, a place while the key is down. */
354
+ updateLink() {
355
+ const {
356
+ target
357
+ } = this;
358
+ const entry = target === null ? undefined : this.entries.get(target.id);
359
+ const part = entry === undefined || target === null || entry.epoch !== this.epoch ? null : partOf(entry.hint, target.part);
360
+ const link = part !== null && (part.command !== undefined || this.modHeld && part.location !== undefined);
361
+ this.language.editor.setInlayLink?.(link ? target : null);
362
+ }
363
+ pressed(click) {
364
+ const entry = this.entries.get(click.id);
365
+ if (entry === undefined || entry.epoch !== this.epoch) {
366
+ return false;
367
+ }
368
+ if (click.touch) {
369
+ this.timers.clear(this.showTimer);
370
+ const request = ++this.request;
371
+ const worth = tooltipOf(entry.hint, click.part) !== null || this.resolvable(entry);
372
+ if (worth) {
373
+ void this.showTooltip({
374
+ id: click.id,
375
+ part: click.part
376
+ }, request);
377
+ }
378
+ return worth;
379
+ }
380
+ if (click.alt || click.shift) {
381
+ return false;
382
+ }
383
+ this.timers.clear(this.commandTimer);
384
+ const press = ++this.pressCount;
385
+ if (this.resolvable(entry) && (click.clicks === 2 || actionOf(entry.hint, click) !== null)) {
386
+ // The server may still add edits, which would make this the first press of a double click.
387
+ void this.resolve(entry).then(() => {
388
+ if (press === this.pressCount) {
389
+ this.press(entry, click);
374
390
  }
375
- // The first press of a double click must not move the caret either, or the second would land beside the hint.
376
- return click.clicks === 1 && !click.mod && insertable;
391
+ });
392
+ return true;
377
393
  }
378
- async perform(entry, action) {
379
- const { editor, project, uri } = this.language;
380
- try {
381
- if (action.kind === 'go') {
382
- this.cancelHover();
383
- this.language.goTo(action.location);
384
- }
385
- else if (action.kind === 'run') {
386
- await project.service.executeCommand(uri, action.command);
387
- }
388
- else if (entry.epoch === this.epoch && editsFit(editor, action.edits)) {
389
- this.cancelHover();
390
- const applied = editor.applyEdits(action.edits.map((edit) => ({ range: edit.range, text: edit.newText })));
391
- if (applied) {
392
- this.shown = this.shown.filter((hint) => hint.id !== entry.id);
393
- editor.setInlayHints(this.shown);
394
- }
395
- }
394
+ return this.press(entry, click);
395
+ }
396
+ press(entry, click) {
397
+ this.timers.clear(this.commandTimer);
398
+ const action = actionOf(entry.hint, click);
399
+ const insertable = entry.hint.textEdits !== undefined && entry.hint.textEdits.length > 0;
400
+ if (action?.kind === 'run' && insertable) {
401
+ // A double click inserts the edits, so the command of its first press waits to see whether a second one comes.
402
+ const epoch = this.epoch;
403
+ this.commandTimer = this.timers.set(() => {
404
+ if (epoch === this.epoch) {
405
+ void this.perform(entry, action);
396
406
  }
397
- catch (error) {
398
- this.language.project.host.notify?.({
399
- id: TOAST_ID,
400
- kind: 'error',
401
- title: project.i18n.t('editor:language.inlay.failed', { message: messageOf(error) })
402
- });
407
+ }, DOUBLE_PRESS_MS);
408
+ return true;
409
+ }
410
+ if (action !== null) {
411
+ void this.perform(entry, action);
412
+ return true;
413
+ }
414
+ // The first press of a double click must not move the caret either, or the second would land beside the hint.
415
+ return click.clicks === 1 && !click.mod && insertable;
416
+ }
417
+ async perform(entry, action) {
418
+ const {
419
+ editor,
420
+ project,
421
+ uri
422
+ } = this.language;
423
+ try {
424
+ if (action.kind === 'go') {
425
+ this.cancelHover();
426
+ this.language.goTo(action.location);
427
+ } else if (action.kind === 'run') {
428
+ await project.service.executeCommand(uri, action.command);
429
+ } else if (entry.epoch === this.epoch && editsFit(editor, action.edits)) {
430
+ this.cancelHover();
431
+ const applied = editor.applyEdits(action.edits.map(edit => ({
432
+ range: edit.range,
433
+ text: edit.newText
434
+ })));
435
+ if (applied) {
436
+ this.shown = this.shown.filter(hint => hint.id !== entry.id);
437
+ editor.setInlayHints(this.shown);
403
438
  }
439
+ }
440
+ } catch (error) {
441
+ this.language.project.host.notify?.({
442
+ id: TOAST_ID,
443
+ kind: 'error',
444
+ title: project.i18n.t('editor:language.inlay.failed', {
445
+ message: messageOf(error)
446
+ })
447
+ });
404
448
  }
449
+ }
405
450
  }
406
451
  function sameSpot(left, right) {
407
- return left.line === right.line && left.character === right.character;
408
- }
452
+ return left.line === right.line && left.character === right.character;
453
+ }