@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,5 +1,7 @@
1
1
  import type { Editor, EditorInlayClick, EditorInlayHint, EditorPosition, EditorRange } from '@adecore/editor';
2
2
  import type { Command, InlayHint, InlayHintLabelPart, Location, MarkupContent, TextEdit } from '@adecore/lsp';
3
+ import { followModKey } from './definition-link.ts';
4
+ import { comparePositions } from './diagnostics-model.ts';
3
5
  import type { EditorLanguage } from './editor-language.ts';
4
6
  import { hoverTextOf, isEmptyHover, type HoverText } from './hover-content.ts';
5
7
  import { SHOW_DELAY_MS } from './hover.ts';
@@ -12,21 +14,19 @@ const METHOD = 'textDocument/inlayHint';
12
14
  const RESOLVE_METHOD = 'inlayHint/resolve';
13
15
  const TOAST_ID = 'language-inlay-hint';
14
16
  const PAUSE_MS = 300;
17
+
15
18
  /* How long a click on a hint that a double click would also act on waits for its second press; the editor's own interval for a double press. */
16
19
  const DOUBLE_PRESS_MS = 500;
20
+
17
21
  /* Lines asked for above and below what is in view, so a little scrolling stays inside what is already drawn. */
18
22
  const MARGIN_LINES = 60;
19
23
 
20
- /* A hint's label is a string or a list of parts, which are read as one text. */
21
24
  export function hintLabelOf(hint: InlayHint): string {
22
25
  return typeof hint.label === 'string' ? hint.label : hint.label.map((part) => part.value).join('');
23
26
  }
24
27
 
25
- /* The hints worth drawing, in the order of the text. */
26
28
  export function drawableHints(hints: readonly InlayHint[]): InlayHint[] {
27
- return hints
28
- .filter((hint) => hintLabelOf(hint) !== '')
29
- .sort((left, right) => left.position.line - right.position.line || left.position.character - right.position.character);
29
+ return hints.filter((hint) => hintLabelOf(hint) !== '').sort((left, right) => comparePositions(left.position, right.position));
30
30
  }
31
31
 
32
32
  /* A hint as the editor draws it: a label with parts keeps them apart so each can be pointed at. */
@@ -41,18 +41,13 @@ export function editorHintOf(hint: InlayHint, id: string): EditorInlayHint {
41
41
  };
42
42
  }
43
43
 
44
- export function editorHintsOf(hints: readonly InlayHint[]): EditorInlayHint[] {
45
- return drawableHints(hints).map((hint, index) => editorHintOf(hint, `hint-${index}`));
46
- }
47
-
48
- /* The lines in view with a margin of lines around them, from the start of the first to the end of the last. */
49
44
  export function withMargin(visible: EditorRange, endOfDocument: EditorPosition, endOfLine: (line: number) => EditorPosition): EditorRange {
50
45
  const first = Math.max(0, visible.start.line - MARGIN_LINES);
51
46
  const last = Math.min(endOfDocument.line, visible.end.line + MARGIN_LINES);
47
+
52
48
  return { start: { line: first, character: 0 }, end: last === endOfDocument.line ? endOfDocument : endOfLine(last) };
53
49
  }
54
50
 
55
- /* Whether what was asked for already holds every line in view. */
56
51
  export function covers(asked: EditorRange, visible: EditorRange): boolean {
57
52
  return asked.start.line <= visible.start.line && asked.end.line >= visible.end.line;
58
53
  }
@@ -62,11 +57,12 @@ export function tooltipTextOf(tooltip: string | MarkupContent | undefined): Hove
62
57
  if (tooltip === undefined) {
63
58
  return null;
64
59
  }
60
+
65
61
  const text = hoverTextOf({ contents: typeof tooltip === 'string' ? { kind: 'plaintext', value: tooltip } : tooltip });
62
+
66
63
  return isEmptyHover(text) ? null : text;
67
64
  }
68
65
 
69
- /* One part of a hint's label, or null for a hint whose label is a string. */
70
66
  export function partOf(hint: InlayHint, index: number | null): InlayHintLabelPart | null {
71
67
  return index === null || typeof hint.label === 'string' ? null : (hint.label[index] ?? null);
72
68
  }
@@ -75,9 +71,11 @@ export function partOf(hint: InlayHint, index: number | null): InlayHintLabelPar
75
71
  export function tooltipOf(hint: InlayHint, index: number | null): HoverText | null {
76
72
  const part = partOf(hint, index);
77
73
  const own = part?.tooltip ?? hint.tooltip;
74
+
78
75
  if (own !== undefined || index !== null || typeof hint.label === 'string') {
79
76
  return tooltipTextOf(own);
80
77
  }
78
+
81
79
  return tooltipTextOf(hint.label.find((candidate) => candidate.tooltip !== undefined)?.tooltip);
82
80
  }
83
81
 
@@ -86,13 +84,16 @@ export function lacksInfo(hint: InlayHint, index: number | null): boolean {
86
84
  if (hint.tooltip !== undefined) {
87
85
  return false;
88
86
  }
87
+
89
88
  const parts = typeof hint.label === 'string' ? [] : index === null ? hint.label : [hint.label[index]];
89
+
90
90
  return !parts.some((part) => part !== undefined && (part.tooltip !== undefined || part.location !== undefined || part.command !== undefined));
91
91
  }
92
92
 
93
93
  /* Whether the edits fit the text as it is now: positions inside it, each range in order, none overlapping the next. */
94
94
  export function editsFit(editor: Editor, edits: readonly TextEdit[]): boolean {
95
95
  const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
96
+
96
97
  const inside = (position: EditorPosition): boolean =>
97
98
  Number.isInteger(position.line) &&
98
99
  Number.isInteger(position.character) &&
@@ -100,11 +101,14 @@ export function editsFit(editor: Editor, edits: readonly TextEdit[]): boolean {
100
101
  position.character >= 0 &&
101
102
  position.line <= end.line &&
102
103
  position.character <= lineTextOf(editor, position.line).length;
103
- const before = (left: EditorPosition, right: EditorPosition): boolean =>
104
- left.line < right.line || (left.line === right.line && left.character <= right.character);
104
+
105
+ const before = (left: EditorPosition, right: EditorPosition): boolean => comparePositions(left, right) <= 0;
106
+
105
107
  const ordered = [...edits].sort((left, right) => (before(left.range.start, right.range.start) ? -1 : 1));
108
+
106
109
  return ordered.every((edit, index) => {
107
110
  const next = ordered[index + 1];
111
+
108
112
  return (
109
113
  inside(edit.range.start) &&
110
114
  inside(edit.range.end) &&
@@ -124,14 +128,16 @@ export function actionOf(hint: InlayHint, click: Pick<EditorInlayClick, 'part' |
124
128
  if (click.clicks === 2) {
125
129
  return hint.textEdits !== undefined && hint.textEdits.length > 0 ? { kind: 'insert', edits: hint.textEdits } : null;
126
130
  }
131
+
127
132
  const part = partOf(hint, click.part);
133
+
128
134
  if (click.mod) {
129
135
  return part?.location === undefined ? null : { kind: 'go', location: part.location };
130
136
  }
137
+
131
138
  return part?.command === undefined ? null : { kind: 'run', command: part.command };
132
139
  }
133
140
 
134
- /* A hint on screen and what has become known of it. */
135
141
  interface Entry {
136
142
  readonly id: string;
137
143
  hint: InlayHint;
@@ -144,36 +150,38 @@ interface Entry {
144
150
 
145
151
  /*
146
152
  * Types and parameter names the servers infer, asked for the lines in view and a margin around them. The
147
- * last ones stay until the answer comes, so nothing blinks while typing; scrolling asks again only once the
148
- * view leaves the lines that were asked for.
153
+ * last ones stay until the answer comes, so nothing blinks while typing.
149
154
  *
150
- * A hint can be acted on with the pointer. The server is asked to resolve a hint as the pointer arrives, and
151
- * resting on it for the hover delay shows its tooltip. Mod+click on a part follows its place, a click on a
152
- * part runs its command and a double click inserts its text edits as one undo step, never together with the
153
- * command: when a hint has both, the command waits for the double press interval. A press on a hint that
154
- * offers nothing (yet) moves the caret as beside the text. A finger has no hover, so a tap shows the
155
- * tooltip and does nothing more. All of it is for the pointer: the hints are not part
156
- * of the text and stay out of the keyboard's way, and Quick Info on the caret shows the tooltip of a hint that
157
- * sits there.
155
+ * Mod+click on a part follows its place, a click runs its command and a double click inserts its text edits
156
+ * as one undo step, never together with the command. A press on a hint that offers nothing (yet) moves the
157
+ * caret; a tap only shows the tooltip. The hints stay out of the keyboard's way: Quick Info on the caret
158
+ * shows the tooltip of a hint that sits there.
158
159
  */
159
160
  export class InlayHintsFeature {
160
161
  private readonly language: EditorLanguage;
161
162
  private readonly timers: Timers;
162
163
  private readonly refresher: Refresher;
164
+
163
165
  /* The lines the hints on screen were asked for. */
164
166
  private asked: EditorRange | null = null;
167
+
165
168
  private readonly entries = new Map<string, Entry>();
166
169
  private shown: EditorInlayHint[] = [];
167
170
  private generation = 0;
171
+
168
172
  /* Counts the edits of the text, which hints and answers made for an earlier one are tested against. */
169
173
  private epoch = 0;
174
+
170
175
  private target: { id: string; part: number | null } | null = null;
171
176
  private modHeld = false;
172
177
  private showTimer: unknown;
178
+
173
179
  /* The command of a click that may still become a double click. */
174
180
  private commandTimer: unknown;
181
+
175
182
  /* Counts presses, so one that waited for a resolve gives way to a later one. */
176
183
  private pressCount = 0;
184
+
177
185
  /* Counts what the pointer does, so a tooltip that was waited for is dropped once the pointer has moved on. */
178
186
  private request = 0;
179
187
 
@@ -181,19 +189,24 @@ export class InlayHintsFeature {
181
189
  this.language = language;
182
190
  this.timers = timers;
183
191
  const { editor, project, uri } = language;
192
+
184
193
  this.refresher = new Refresher(
185
194
  async (signal) => {
186
195
  const { service } = project;
196
+
187
197
  if (!service.supports(METHOD, uri)) {
188
198
  return;
189
199
  }
200
+
190
201
  const range = withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), (line) => ({
191
202
  line,
192
203
  character: lineTextOf(editor, line).length
193
204
  }));
205
+
194
206
  this.asked = range;
195
207
  const epoch = this.epoch;
196
208
  const hints = await service.inlayHints(uri, range, { signal });
209
+
197
210
  if (!signal.aborted && epoch === this.epoch) {
198
211
  this.install(hints === null ? [] : drawableHints(hints), epoch);
199
212
  }
@@ -201,6 +214,7 @@ export class InlayHintsFeature {
201
214
  PAUSE_MS,
202
215
  timers
203
216
  );
217
+
204
218
  const offs = [
205
219
  editor.onTextChange(() => {
206
220
  this.epoch++;
@@ -216,33 +230,22 @@ export class InlayHintsFeature {
216
230
  this.refresher.now();
217
231
  })
218
232
  ];
233
+
219
234
  if (editor.onInlayHover !== undefined) {
220
235
  offs.push(editor.onInlayHover((target) => this.hovered(target === null ? null : { id: target.id, part: target.part })));
221
236
  }
237
+
222
238
  if (editor.onInlayClick !== undefined) {
223
239
  offs.push(editor.onInlayClick((click) => this.pressed(click)));
224
240
  }
225
- if (typeof window !== 'undefined') {
226
- const modKey = language.apple ? 'Meta' : 'Control';
227
- const keys = (event: KeyboardEvent): void => {
228
- if (event.key === modKey) {
229
- this.setModHeld(event.type === 'keydown');
230
- }
231
- };
232
- const lost = (): void => this.setModHeld(false);
233
- window.addEventListener('keydown', keys);
234
- window.addEventListener('keyup', keys);
235
- window.addEventListener('blur', lost);
236
- offs.push(() => {
237
- window.removeEventListener('keydown', keys);
238
- window.removeEventListener('keyup', keys);
239
- window.removeEventListener('blur', lost);
240
- });
241
- }
241
+
242
+ offs.push(followModKey(language.apple, (held) => this.setModHeld(held)));
243
+
242
244
  language.onDispose(() => {
243
245
  for (const off of offs) {
244
246
  off();
245
247
  }
248
+
246
249
  this.cancelHover();
247
250
  this.timers.clear(this.commandTimer);
248
251
  this.refresher.dispose();
@@ -261,21 +264,26 @@ export class InlayHintsFeature {
261
264
 
262
265
  /* 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. */
263
266
  async showTooltipAt(position: EditorPosition): Promise<boolean> {
264
- const found = [...this.entries].find(([, entry]) => entry.epoch === this.epoch && sameSpot(entry.hint.position, position));
267
+ const found = [...this.entries].find(([, entry]) => entry.epoch === this.epoch && comparePositions(entry.hint.position, position) === 0);
268
+
265
269
  if (found === undefined) {
266
270
  return false;
267
271
  }
272
+
268
273
  return this.showTooltip({ id: found[0], part: null }, this.request, false);
269
274
  }
270
275
 
271
276
  private install(hints: readonly InlayHint[], epoch: number): void {
272
277
  this.generation++;
273
278
  this.entries.clear();
279
+
274
280
  this.shown = hints.map((hint, index) => {
275
281
  const id = `hint-${this.generation}-${index}`;
276
282
  this.entries.set(id, { id, hint, epoch, resolution: null, answered: false });
283
+
277
284
  return editorHintOf(hint, id);
278
285
  });
286
+
279
287
  this.language.editor.setInlayHints(this.shown);
280
288
  this.target = null;
281
289
  this.updateLink();
@@ -286,15 +294,19 @@ export class InlayHintsFeature {
286
294
  this.timers.clear(this.showTimer);
287
295
  const request = ++this.request;
288
296
  this.updateLink();
297
+
289
298
  if (target === null) {
290
299
  if (this.cardOpen()) {
291
300
  this.language.hover.release();
292
301
  }
302
+
293
303
  return;
294
304
  }
305
+
295
306
  this.showTimer = this.timers.set(() => void this.showTooltip(target, request), SHOW_DELAY_MS);
296
307
  // A press has to know what the hint offers when it comes, so the answer is asked for as the pointer arrives.
297
308
  const entry = this.entries.get(target.id);
309
+
298
310
  if (entry !== undefined && this.resolvable(entry)) {
299
311
  void this.resolve(entry).then(() => this.updateLink());
300
312
  }
@@ -305,6 +317,7 @@ export class InlayHintsFeature {
305
317
  this.request++;
306
318
  this.target = null;
307
319
  this.updateLink();
320
+
308
321
  if (this.cardOpen()) {
309
322
  this.language.hover.hide();
310
323
  }
@@ -318,24 +331,32 @@ export class InlayHintsFeature {
318
331
  /* The card for a hint or part, once the server has said what it can. Says whether it opened. */
319
332
  private async showTooltip(target: { id: string; part: number | null }, request: number, pointer = true): Promise<boolean> {
320
333
  const entry = this.entries.get(target.id);
334
+
321
335
  if (entry === undefined) {
322
336
  return false;
323
337
  }
338
+
324
339
  if (lacksInfo(entry.hint, target.part)) {
325
340
  await this.resolve(entry);
326
341
  this.updateLink();
327
342
  }
343
+
328
344
  if ((pointer && request !== this.request) || entry.epoch !== this.epoch) {
329
345
  return false;
330
346
  }
347
+
331
348
  const text = tooltipOf(entry.hint, target.part);
349
+
332
350
  if (text === null) {
333
351
  if (this.cardOpen()) {
334
352
  this.language.hover.hide();
335
353
  }
354
+
336
355
  return false;
337
356
  }
357
+
338
358
  this.language.hover.showText(entry.hint.position, text);
359
+
339
360
  return true;
340
361
  }
341
362
 
@@ -344,22 +365,27 @@ export class InlayHintsFeature {
344
365
  if (entry.resolution !== null) {
345
366
  return entry.resolution;
346
367
  }
368
+
347
369
  const { service } = this.language.project;
370
+
348
371
  if (entry.epoch !== this.epoch || !service.supports(RESOLVE_METHOD, this.language.uri)) {
349
372
  return Promise.resolve(entry.hint);
350
373
  }
374
+
351
375
  entry.resolution = service
352
376
  .resolveInlayHint(this.language.uri, entry.hint)
353
377
  .then((resolved) => {
354
378
  if (entry.epoch === this.epoch) {
355
379
  entry.hint = { ...entry.hint, ...resolved, position: entry.hint.position };
356
380
  }
381
+
357
382
  return entry.hint;
358
383
  })
359
384
  .catch(() => entry.hint)
360
385
  .finally(() => {
361
386
  entry.answered = true;
362
387
  });
388
+
363
389
  return entry.resolution;
364
390
  }
365
391
 
@@ -379,23 +405,30 @@ export class InlayHintsFeature {
379
405
 
380
406
  private pressed(click: EditorInlayClick): boolean {
381
407
  const entry = this.entries.get(click.id);
408
+
382
409
  if (entry === undefined || entry.epoch !== this.epoch) {
383
410
  return false;
384
411
  }
412
+
385
413
  if (click.touch) {
386
414
  this.timers.clear(this.showTimer);
387
415
  const request = ++this.request;
388
416
  const worth = tooltipOf(entry.hint, click.part) !== null || this.resolvable(entry);
417
+
389
418
  if (worth) {
390
419
  void this.showTooltip({ id: click.id, part: click.part }, request);
391
420
  }
421
+
392
422
  return worth;
393
423
  }
424
+
394
425
  if (click.alt || click.shift) {
395
426
  return false;
396
427
  }
428
+
397
429
  this.timers.clear(this.commandTimer);
398
430
  const press = ++this.pressCount;
431
+
399
432
  if (this.resolvable(entry) && (click.clicks === 2 || actionOf(entry.hint, click) !== null)) {
400
433
  // The server may still add edits, which would make this the first press of a double click.
401
434
  void this.resolve(entry).then(() => {
@@ -403,8 +436,10 @@ export class InlayHintsFeature {
403
436
  this.press(entry, click);
404
437
  }
405
438
  });
439
+
406
440
  return true;
407
441
  }
442
+
408
443
  return this.press(entry, click);
409
444
  }
410
445
 
@@ -412,26 +447,33 @@ export class InlayHintsFeature {
412
447
  this.timers.clear(this.commandTimer);
413
448
  const action = actionOf(entry.hint, click);
414
449
  const insertable = entry.hint.textEdits !== undefined && entry.hint.textEdits.length > 0;
450
+
415
451
  if (action?.kind === 'run' && insertable) {
416
452
  // A double click inserts the edits, so the command of its first press waits to see whether a second one comes.
417
453
  const epoch = this.epoch;
454
+
418
455
  this.commandTimer = this.timers.set(() => {
419
456
  if (epoch === this.epoch) {
420
457
  void this.perform(entry, action);
421
458
  }
422
459
  }, DOUBLE_PRESS_MS);
460
+
423
461
  return true;
424
462
  }
463
+
425
464
  if (action !== null) {
426
465
  void this.perform(entry, action);
466
+
427
467
  return true;
428
468
  }
469
+
429
470
  // The first press of a double click must not move the caret either, or the second would land beside the hint.
430
471
  return click.clicks === 1 && !click.mod && insertable;
431
472
  }
432
473
 
433
474
  private async perform(entry: Entry, action: Action): Promise<void> {
434
475
  const { editor, project, uri } = this.language;
476
+
435
477
  try {
436
478
  if (action.kind === 'go') {
437
479
  this.cancelHover();
@@ -441,6 +483,7 @@ export class InlayHintsFeature {
441
483
  } else if (entry.epoch === this.epoch && editsFit(editor, action.edits)) {
442
484
  this.cancelHover();
443
485
  const applied = editor.applyEdits(action.edits.map((edit) => ({ range: edit.range, text: edit.newText })));
486
+
444
487
  if (applied) {
445
488
  this.shown = this.shown.filter((hint) => hint.id !== entry.id);
446
489
  editor.setInlayHints(this.shown);
@@ -455,7 +498,3 @@ export class InlayHintsFeature {
455
498
  }
456
499
  }
457
500
  }
458
-
459
- function sameSpot(left: EditorPosition, right: EditorPosition): boolean {
460
- return left.line === right.line && left.character === right.character;
461
- }
@@ -35,8 +35,10 @@ export class KeyDispatcher {
35
35
  const entry = { handler, priority };
36
36
  const index = this.entries.findIndex((other) => other.priority < priority);
37
37
  this.entries.splice(index === -1 ? this.entries.length : index, 0, entry);
38
+
38
39
  return () => {
39
40
  const at = this.entries.indexOf(entry);
41
+
40
42
  if (at !== -1) {
41
43
  this.entries.splice(at, 1);
42
44
  }
package/src/line-edits.ts CHANGED
@@ -19,16 +19,21 @@ export function replaceLines(text: string, replacement: LineReplacement): Editor
19
19
  const raw = text.split('\n');
20
20
  const count = splitLines(text).length;
21
21
  const body = lines.join(eol);
22
+
22
23
  if (to < count || text === '' || text.endsWith('\n')) {
23
24
  return { range: { start: { line: from, character: 0 }, end: { line: to, character: 0 } }, text: lines.length === 0 ? '' : `${body}${eol}` };
24
25
  }
26
+
25
27
  const end = { line: count - 1, character: raw[count - 1]!.length };
28
+
26
29
  if (from === count) {
27
30
  return { range: { start: end, end }, text: lines.length === 0 ? '' : `${eol}${body}` };
28
31
  }
32
+
29
33
  if (lines.length === 0 && from > 0) {
30
34
  return { range: { start: { line: from - 1, character: raw[from - 1]!.length }, end }, text: '' };
31
35
  }
36
+
32
37
  return { range: { start: { line: from, character: 0 }, end }, text: body };
33
38
  }
34
39
 
@@ -36,13 +41,16 @@ export function replaceLines(text: string, replacement: LineReplacement): Editor
36
41
  export function replaceAllLines(text: string, replacements: readonly LineReplacement[]): EditorContentChange[] {
37
42
  const ordered = [...replacements].sort((left, right) => left.from - right.from);
38
43
  const merged: LineReplacement[] = [];
44
+
39
45
  for (const next of ordered) {
40
46
  const last = merged.at(-1);
47
+
41
48
  if (last !== undefined && last.to >= next.from) {
42
49
  merged[merged.length - 1] = { from: last.from, to: Math.max(last.to, next.to), lines: [...last.lines, ...next.lines] };
43
50
  } else {
44
51
  merged.push(next);
45
52
  }
46
53
  }
54
+
47
55
  return merged.map((replacement) => replaceLines(text, replacement));
48
56
  }
package/src/models.ts CHANGED
@@ -12,6 +12,7 @@ export { type ActionGroup, type ActionEntry, type EditPreview } from './code-act
12
12
  export { type ResourceOperationView } from './workspace-edit.ts';
13
13
  export { type CodeVisionDeclaration, type DeclarationSource } from './code-vision-model.ts';
14
14
  export { type CodeAuthor, type CodeAuthorship } from './code-authors.ts';
15
+
15
16
  export {
16
17
  type SnippetTransform,
17
18
  type SnippetStop,
@@ -24,6 +25,7 @@ export {
24
25
  parseSnippet,
25
26
  tabOrder
26
27
  } from './snippet.ts';
28
+
27
29
  export {
28
30
  type LineSpan,
29
31
  lineSpanOf,
@@ -40,8 +42,10 @@ export {
40
42
  replaceRange,
41
43
  locateSelection
42
44
  } from './proposal-model.ts';
45
+
43
46
  export { type LineReplacement, replaceLines, replaceAllLines } from './line-edits.ts';
44
47
  export { replacedWords } from './word-diff.ts';
48
+
45
49
  export {
46
50
  type FindOptions,
47
51
  type FindQuery,
@@ -53,8 +57,10 @@ export {
53
57
  matchesIn,
54
58
  stepIndex
55
59
  } from './find-query.ts';
60
+
56
61
  export { type PopupSize, type PopupPlacement, type PlaceOptions } from './popup-placement.ts';
57
62
  export { planConflict, type ConflictStretch, type ConflictPlan } from './conflict-model.ts';
63
+
58
64
  export {
59
65
  patchLayoutOf,
60
66
  type PatchLayout,
@@ -27,10 +27,12 @@ export class NavigationHistory {
27
27
  this.backs[this.backs.length - 1] = from;
28
28
  } else {
29
29
  this.backs.push(from);
30
+
30
31
  if (this.backs.length > LIMIT) {
31
32
  this.backs.shift();
32
33
  }
33
34
  }
35
+
34
36
  this.forwards = [];
35
37
  }
36
38
 
@@ -59,11 +61,14 @@ export class NavigationHistory {
59
61
  private walk(from: Place[], to: Place[], current: Place): Place | null {
60
62
  while (from.length > 0) {
61
63
  const place = from.pop()!;
64
+
62
65
  if (!samePlace(place, current)) {
63
66
  to.push(current);
67
+
64
68
  return place;
65
69
  }
66
70
  }
71
+
67
72
  return null;
68
73
  }
69
74
  }