@adecore/editor-react 0.22.1 → 0.23.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 (209) hide show
  1. package/dist/CallHierarchyPanel.d.ts +6 -0
  2. package/dist/CallHierarchyPanel.js +92 -0
  3. package/dist/CompletionPopup.js +11 -3
  4. package/dist/DiffView.d.ts +22 -0
  5. package/dist/DiffView.js +203 -0
  6. package/dist/EditorContextMenu.js +1 -0
  7. package/dist/EditorView.js +18 -1
  8. package/dist/FindReplace.d.ts +1 -1
  9. package/dist/FindReplace.js +41 -11
  10. package/dist/HoverCard.js +2 -2
  11. package/dist/LanguagePopups.js +4 -1
  12. package/dist/Markdown.d.ts +2 -1
  13. package/dist/Markdown.js +8 -4
  14. package/dist/PanelResizeHandle.d.ts +8 -0
  15. package/dist/PanelResizeHandle.js +71 -0
  16. package/dist/PeekPanel.d.ts +3 -0
  17. package/dist/PeekPanel.js +7 -4
  18. package/dist/PickPopup.js +15 -2
  19. package/dist/RenameCard.js +10 -5
  20. package/dist/RenderedDocs.d.ts +9 -0
  21. package/dist/RenderedDocs.js +84 -0
  22. package/dist/SplitDiffView.d.ts +9 -0
  23. package/dist/SplitDiffView.js +276 -0
  24. package/dist/SymbolPicker.d.ts +3 -0
  25. package/dist/SymbolPicker.js +4 -3
  26. package/dist/call-hierarchy-model.d.ts +52 -0
  27. package/dist/call-hierarchy-model.js +93 -0
  28. package/dist/call-hierarchy.d.ts +42 -0
  29. package/dist/call-hierarchy.js +398 -0
  30. package/dist/code-actions-model.d.ts +2 -0
  31. package/dist/code-actions-model.js +12 -10
  32. package/dist/code-actions.js +3 -1
  33. package/dist/completion-model.d.ts +14 -4
  34. package/dist/completion-model.js +40 -33
  35. package/dist/completion.d.ts +4 -0
  36. package/dist/completion.js +257 -42
  37. package/dist/diagnostics.js +8 -6
  38. package/dist/diff-expansion.d.ts +9 -0
  39. package/dist/diff-expansion.js +41 -0
  40. package/dist/diff-overlays.d.ts +15 -0
  41. package/dist/diff-overlays.js +28 -0
  42. package/dist/diff-syntax.d.ts +16 -0
  43. package/dist/diff-syntax.js +49 -0
  44. package/dist/diff-wordmarks.d.ts +5 -0
  45. package/dist/diff-wordmarks.js +26 -0
  46. package/dist/doc-comment-model.d.ts +23 -0
  47. package/dist/doc-comment-model.js +291 -0
  48. package/dist/editor-language.d.ts +13 -1
  49. package/dist/editor-language.js +15 -5
  50. package/dist/editor-react.css +79 -0
  51. package/dist/format-on-type.d.ts +14 -0
  52. package/dist/format-on-type.js +96 -0
  53. package/dist/host-types.d.ts +19 -0
  54. package/dist/hover.d.ts +4 -0
  55. package/dist/hover.js +15 -2
  56. package/dist/index.d.ts +19 -5
  57. package/dist/index.js +9 -1
  58. package/dist/inlay-hints.d.ts +47 -2
  59. package/dist/inlay-hints.js +353 -16
  60. package/dist/language-ids.js +4 -2
  61. package/dist/locales/en.json +117 -3
  62. package/dist/locales/nl.json +117 -3
  63. package/dist/models.d.ts +6 -2
  64. package/dist/models.js +5 -1
  65. package/dist/panel-size.d.ts +6 -0
  66. package/dist/panel-size.js +25 -0
  67. package/dist/patch-decorations.d.ts +27 -0
  68. package/dist/patch-decorations.js +182 -0
  69. package/dist/patch-layout.d.ts +40 -0
  70. package/dist/patch-layout.js +304 -0
  71. package/dist/peek.d.ts +3 -1
  72. package/dist/peek.js +6 -1
  73. package/dist/pick.d.ts +1 -0
  74. package/dist/pick.js +11 -1
  75. package/dist/popups.d.ts +17 -0
  76. package/dist/popups.js +1 -0
  77. package/dist/project-language.d.ts +12 -0
  78. package/dist/project-language.js +52 -2
  79. package/dist/refresher.d.ts +1 -0
  80. package/dist/refresher.js +5 -0
  81. package/dist/rename-model.d.ts +1 -1
  82. package/dist/rename-model.js +3 -7
  83. package/dist/rename.js +5 -8
  84. package/dist/rendered-docs.d.ts +64 -0
  85. package/dist/rendered-docs.js +374 -0
  86. package/dist/row-host.d.ts +2 -0
  87. package/dist/row-host.js +11 -3
  88. package/dist/semantic-delta.d.ts +2 -0
  89. package/dist/semantic-delta.js +27 -0
  90. package/dist/semantic-model.js +16 -0
  91. package/dist/semantic-tokens.d.ts +26 -1
  92. package/dist/semantic-tokens.js +174 -14
  93. package/dist/shortcut-keys.d.ts +2 -0
  94. package/dist/shortcut-keys.js +6 -2
  95. package/dist/signature.js +1 -1
  96. package/dist/snippet-field-transforms.d.ts +30 -0
  97. package/dist/snippet-field-transforms.js +116 -0
  98. package/dist/snippet-indent.d.ts +2 -0
  99. package/dist/snippet-indent.js +49 -0
  100. package/dist/snippet-range-order.d.ts +3 -0
  101. package/dist/snippet-range-order.js +19 -0
  102. package/dist/snippet-session.d.ts +42 -6
  103. package/dist/snippet-session.js +650 -53
  104. package/dist/snippet-transform-client.d.ts +19 -0
  105. package/dist/snippet-transform-client.js +66 -0
  106. package/dist/snippet-transform-format.d.ts +15 -0
  107. package/dist/snippet-transform-format.js +302 -0
  108. package/dist/snippet-transform-protocol.d.ts +45 -0
  109. package/dist/snippet-transform-protocol.js +8 -0
  110. package/dist/snippet-transform-queue.d.ts +32 -0
  111. package/dist/snippet-transform-queue.js +173 -0
  112. package/dist/snippet-transform-worker.d.ts +10 -0
  113. package/dist/snippet-transform-worker.js +41 -0
  114. package/dist/snippet-variable-names.d.ts +1 -0
  115. package/dist/snippet-variable-names.js +36 -0
  116. package/dist/snippet-variables.d.ts +7 -0
  117. package/dist/snippet-variables.js +66 -0
  118. package/dist/snippet.d.ts +30 -3
  119. package/dist/snippet.js +402 -87
  120. package/dist/split-alignment.d.ts +6 -0
  121. package/dist/split-alignment.js +38 -0
  122. package/dist/split-scroll.d.ts +14 -0
  123. package/dist/split-scroll.js +45 -0
  124. package/dist/symbols.d.ts +2 -1
  125. package/dist/symbols.js +39 -7
  126. package/dist/testing.d.ts +5 -1
  127. package/dist/testing.js +12 -0
  128. package/dist/timers.d.ts +1 -0
  129. package/dist/timers.js +4 -0
  130. package/dist/use-panel-body.d.ts +6 -0
  131. package/dist/use-panel-body.js +13 -0
  132. package/dist/workspace-edit.d.ts +25 -3
  133. package/dist/workspace-edit.js +282 -143
  134. package/package.json +10 -5
  135. package/src/CallHierarchyPanel.tsx +244 -0
  136. package/src/CompletionPopup.tsx +12 -1
  137. package/src/DiffView.tsx +262 -0
  138. package/src/EditorContextMenu.tsx +1 -0
  139. package/src/EditorView.tsx +21 -2
  140. package/src/FindReplace.tsx +51 -13
  141. package/src/HoverCard.tsx +23 -21
  142. package/src/LanguagePopups.tsx +5 -0
  143. package/src/Markdown.tsx +10 -3
  144. package/src/PanelResizeHandle.tsx +104 -0
  145. package/src/PeekPanel.tsx +11 -6
  146. package/src/PickPopup.tsx +18 -2
  147. package/src/RenameCard.tsx +18 -0
  148. package/src/RenderedDocs.tsx +155 -0
  149. package/src/SplitDiffView.tsx +337 -0
  150. package/src/SymbolPicker.tsx +4 -3
  151. package/src/call-hierarchy-model.ts +151 -0
  152. package/src/call-hierarchy.ts +440 -0
  153. package/src/code-actions-model.ts +17 -11
  154. package/src/code-actions.ts +3 -1
  155. package/src/completion-model.ts +61 -35
  156. package/src/completion.ts +302 -42
  157. package/src/diagnostics.ts +8 -6
  158. package/src/diff-expansion.ts +49 -0
  159. package/src/diff-overlays.tsx +69 -0
  160. package/src/diff-syntax.ts +67 -0
  161. package/src/diff-wordmarks.ts +31 -0
  162. package/src/doc-comment-model.ts +336 -0
  163. package/src/editor-language.ts +22 -6
  164. package/src/editor-react.css +79 -0
  165. package/src/format-on-type.ts +111 -0
  166. package/src/host-types.ts +23 -0
  167. package/src/hover.ts +18 -3
  168. package/src/index.ts +27 -4
  169. package/src/inlay-hints.ts +394 -18
  170. package/src/language-ids.ts +4 -2
  171. package/src/locales/en.json +117 -3
  172. package/src/locales/nl.json +117 -3
  173. package/src/models.ts +40 -1
  174. package/src/panel-size.ts +28 -0
  175. package/src/patch-decorations.ts +224 -0
  176. package/src/patch-layout.ts +388 -0
  177. package/src/peek.ts +6 -1
  178. package/src/pick.ts +12 -1
  179. package/src/popups.ts +24 -0
  180. package/src/project-language.ts +63 -2
  181. package/src/refresher.ts +6 -0
  182. package/src/rename-model.ts +4 -8
  183. package/src/rename.ts +5 -8
  184. package/src/rendered-docs.ts +430 -0
  185. package/src/row-host.ts +12 -3
  186. package/src/semantic-delta.ts +29 -0
  187. package/src/semantic-model.ts +17 -0
  188. package/src/semantic-tokens.ts +202 -19
  189. package/src/shortcut-keys.ts +6 -2
  190. package/src/signature.ts +1 -1
  191. package/src/snippet-field-transforms.ts +144 -0
  192. package/src/snippet-indent.ts +53 -0
  193. package/src/snippet-range-order.ts +19 -0
  194. package/src/snippet-session.ts +797 -56
  195. package/src/snippet-transform-client.ts +84 -0
  196. package/src/snippet-transform-format.ts +325 -0
  197. package/src/snippet-transform-protocol.ts +24 -0
  198. package/src/snippet-transform-queue.ts +199 -0
  199. package/src/snippet-transform-worker.ts +58 -0
  200. package/src/snippet-variable-names.ts +36 -0
  201. package/src/snippet-variables.ts +74 -0
  202. package/src/snippet.ts +452 -89
  203. package/src/split-alignment.ts +45 -0
  204. package/src/split-scroll.ts +54 -0
  205. package/src/symbols.ts +43 -7
  206. package/src/testing.ts +25 -0
  207. package/src/timers.ts +5 -0
  208. package/src/use-panel-body.ts +17 -0
  209. package/src/workspace-edit.ts +347 -169
@@ -1,36 +1,196 @@
1
+ import { StaleResultError } from '@adecore/lsp';
2
+ import { covers, withMargin } from './inlay-hints.js';
3
+ import { lineTextOf } from './line-text.js';
1
4
  import { Refresher } from './refresher.js';
5
+ import { applySemanticEdits } from './semantic-delta.js';
2
6
  import { decodeSemanticTokens } from './semantic-model.js';
3
7
  import { realTimers } from './timers.js';
4
- const METHOD = 'textDocument/semanticTokens/full';
8
+ const FULL = 'textDocument/semanticTokens/full';
9
+ const DELTA = 'textDocument/semanticTokens/full/delta';
10
+ const RANGE = 'textDocument/semanticTokens/range';
5
11
  const PAUSE_MS = 200;
12
+ const SCROLL_PAUSE_MS = 100;
13
+ /* A document with more lines or characters than this is first asked for what is in view, when the server answers ranges. */
14
+ export const DEFAULT_RANGE_LINES = 5000;
15
+ export const DEFAULT_RANGE_CHARACTERS = 500_000;
6
16
  /*
7
17
  * Colors from what the language servers know, over the grammar's guess. It asks after a pause in typing
8
18
  * and when a server's offer changed, and keeps drawing the last answer meanwhile so the colors never blink out.
19
+ * A server that offers deltas is asked for the change since its last answer; one that offers ranges is asked
20
+ * for the lines in view first when the document is large, and for them alone when it offers nothing else.
9
21
  */
10
22
  export class SemanticTokensFeature {
11
23
  refresher;
12
- constructor(language, timers = realTimers) {
13
- const { editor, project, uri } = language;
14
- this.refresher = new Refresher(async (signal) => {
15
- const { service } = project;
16
- const legend = service.providerOptions(METHOD, uri)?.legend;
17
- if (!service.supports(METHOD, uri) || legend === undefined) {
18
- return;
19
- }
20
- const tokens = await service.semanticTokens(uri, { signal });
21
- if (!signal.aborted) {
22
- editor.setSemanticTokens(tokens === null ? null : decodeSemanticTokens(tokens, legend));
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();
23
49
  }
24
- }, PAUSE_MS, timers);
25
- const edits = editor.onTextChange(() => this.refresher.later());
50
+ });
26
51
  const providers = language.onProvidersChanged(() => {
52
+ this.epoch++;
53
+ this.scrolled.cancel();
54
+ this.forget();
27
55
  this.refresher.now();
28
56
  });
29
57
  language.onDispose(() => {
30
58
  edits();
59
+ view();
31
60
  providers();
32
61
  this.refresher.dispose();
62
+ this.scrolled.dispose();
63
+ this.forget();
33
64
  editor.setSemanticTokens(null);
34
65
  });
35
66
  }
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;
72
+ }
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));
88
+ }
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
+ }));
95
+ }
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
+ }
110
+ }
111
+ const epoch = this.epoch;
112
+ const tokens = await this.askFull(signal);
113
+ if (signal.aborted || epoch !== this.epoch) {
114
+ return;
115
+ }
116
+ this.complete = tokens !== null;
117
+ this.covered = null;
118
+ this.drawn = [];
119
+ editor.setSemanticTokens(tokens === null ? null : decodeSemanticTokens(tokens, legend));
120
+ }
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;
148
+ }
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 };
156
+ }
157
+ hold(tokens) {
158
+ this.held = tokens?.resultId === undefined ? null : { resultId: tokens.resultId, data: tokens.data };
159
+ }
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
+ }
187
+ }
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);
195
+ }
36
196
  }
@@ -10,6 +10,7 @@ export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
10
10
  addCaretBelow: LanguageShortcut | null;
11
11
  addCaretPerSelectedLine: LanguageShortcut | null;
12
12
  autoIndentLines: LanguageShortcut | null;
13
+ callHierarchy: LanguageShortcut | null;
13
14
  codeActions: LanguageShortcut | null;
14
15
  collapse: LanguageShortcut | null;
15
16
  collapseAll: LanguageShortcut | null;
@@ -82,6 +83,7 @@ export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
82
83
  toggleCase: LanguageShortcut | null;
83
84
  toggleColumnMode: LanguageShortcut | null;
84
85
  toggleLineComment: LanguageShortcut | null;
86
+ toggleRenderedDocs: LanguageShortcut | null;
85
87
  triggerCompletion: LanguageShortcut | null;
86
88
  undo: LanguageShortcut | null;
87
89
  unselectOccurrence: LanguageShortcut | null;
@@ -1,4 +1,4 @@
1
- import { chordOf, KEYMAP_IDS, parseChord } from '@adecore/editor/keymap';
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
4
  const shortcuts = Object.fromEntries(KEYMAP_IDS.map((id) => {
@@ -7,6 +7,10 @@ export function shortcutsOf(keymap, apple) {
7
7
  }));
8
8
  return { ...shortcuts, rename: shortcuts.renameSymbol };
9
9
  }
10
+ /* Ctrl and Alt together that type a character, as AltGr does on some layouts, are no shortcut. */
10
11
  export function isShortcut(target, event) {
11
- return target !== null && matchesShortcut(target, event, target.apple);
12
+ if (target === null || typesInsteadOfChord(event, target.apple)) {
13
+ return false;
14
+ }
15
+ return matchesShortcut(target, event, target.apple);
12
16
  }
package/dist/signature.js CHANGED
@@ -25,7 +25,7 @@ export class SignatureFeature {
25
25
  editor.onCaret(() => this.retrigger()),
26
26
  editor.onBlur(() => this.close()),
27
27
  editor.onKeyDown((event) => {
28
- if (event.key === 'Escape' && this.isOpen) {
28
+ if (event.key === 'Escape' && this.isOpen && this.language.popups.getState().callHierarchy == null) {
29
29
  this.close();
30
30
  return true;
31
31
  }
@@ -0,0 +1,30 @@
1
+ import type { Editor, EditorContentChange, EditorRange } from '@adecore/editor';
2
+ import type { SnippetTransform } from './snippet.ts';
3
+ import type { SnippetTransformLease } from './snippet-transform-queue.ts';
4
+ export interface FieldTransformTarget {
5
+ readonly id: symbol;
6
+ range: EditorRange;
7
+ readonly value: string;
8
+ readonly transform: SnippetTransform;
9
+ readonly parents?: readonly number[];
10
+ readonly owner: object;
11
+ readonly frame: object;
12
+ readonly order: readonly number[];
13
+ readonly ancestors?: readonly symbol[];
14
+ }
15
+ export declare class SnippetFieldTransforms {
16
+ private readonly entries;
17
+ private applying;
18
+ private readonly editor;
19
+ private readonly lease;
20
+ private readonly apply;
21
+ constructor(editor: Editor, lease: SnippetTransformLease | null, apply: (targets: readonly FieldTransformTarget[], values: readonly string[]) => void);
22
+ get pending(): boolean;
23
+ get ranges(): number;
24
+ settle(): Promise<void>;
25
+ enqueue(targets: FieldTransformTarget[], current: () => boolean): void;
26
+ prune(): void;
27
+ clear(): void;
28
+ private cancel;
29
+ edited(changes: readonly EditorContentChange[], detached?: <Range extends EditorRange>(range: Range) => Range): void;
30
+ }
@@ -0,0 +1,116 @@
1
+ import { comparePositions, shiftPosition } from './diagnostics-model.js';
2
+ /* Navigation frames can pop before a worker replies; their tracked targets live independently. */
3
+ export class SnippetFieldTransforms {
4
+ entries = new Set();
5
+ applying = null;
6
+ editor;
7
+ lease;
8
+ apply;
9
+ constructor(editor, lease, apply) {
10
+ this.editor = editor;
11
+ this.lease = lease;
12
+ this.apply = apply;
13
+ }
14
+ get pending() {
15
+ return this.entries.size > 0;
16
+ }
17
+ get ranges() {
18
+ return [...this.entries].reduce((sum, entry) => sum + entry.targets.length, 0);
19
+ }
20
+ async settle() {
21
+ await Promise.all([...this.entries].map((entry) => entry.done));
22
+ }
23
+ enqueue(targets, current) {
24
+ if (targets.length === 0 || this.lease === null) {
25
+ return;
26
+ }
27
+ let finish;
28
+ const done = new Promise((resolve) => {
29
+ finish = resolve;
30
+ });
31
+ const entry = { targets, current, done, finish, cancelled: false };
32
+ this.entries.add(entry);
33
+ const jobs = [], keys = new Map();
34
+ const slots = targets.map((target) => {
35
+ const job = { ...target.transform, value: target.value }, key = JSON.stringify(job);
36
+ let slot = keys.get(key);
37
+ if (slot === undefined) {
38
+ slot = jobs.length;
39
+ keys.set(key, slot);
40
+ jobs.push(job);
41
+ }
42
+ return slot;
43
+ });
44
+ const valid = () => !entry.cancelled && entry.current() && targets.every((target) => this.editor.textInRange(target.range) === target.value);
45
+ void this.lease
46
+ .request(jobs, { kind: 'exit', current: valid })
47
+ .then((values) => {
48
+ if (!valid()) {
49
+ return;
50
+ }
51
+ this.applying = entry;
52
+ try {
53
+ this.apply(targets, slots.map((slot) => values[slot]));
54
+ }
55
+ finally {
56
+ this.applying = null;
57
+ }
58
+ })
59
+ .catch(() => {
60
+ /* Failed exits retain the raw field value without retrying. */
61
+ })
62
+ .finally(() => {
63
+ this.entries.delete(entry);
64
+ finish();
65
+ });
66
+ }
67
+ prune() {
68
+ for (const entry of this.entries) {
69
+ if (!entry.current()) {
70
+ this.cancel(entry);
71
+ }
72
+ }
73
+ }
74
+ clear() {
75
+ for (const entry of this.entries) {
76
+ this.cancel(entry);
77
+ }
78
+ }
79
+ cancel(entry) {
80
+ entry.cancelled = true;
81
+ this.entries.delete(entry);
82
+ entry.finish();
83
+ }
84
+ edited(changes, detached) {
85
+ for (const entry of this.entries) {
86
+ if (entry === this.applying) {
87
+ continue;
88
+ }
89
+ for (const target of entry.targets) {
90
+ let { start, end } = target.range;
91
+ for (const change of changes) {
92
+ const empty = comparePositions(start, end) === 0;
93
+ const insert = comparePositions(change.range.start, change.range.end) === 0;
94
+ const overlaps = insert
95
+ ? empty
96
+ ? comparePositions(start, change.range.start) === 0
97
+ : comparePositions(start, change.range.start) < 0 && comparePositions(change.range.start, end) < 0
98
+ : empty
99
+ ? comparePositions(change.range.start, start) <= 0 && comparePositions(start, change.range.end) < 0
100
+ : comparePositions(change.range.start, end) < 0 && comparePositions(start, change.range.end) < 0;
101
+ if (overlaps && !(detached !== undefined && empty && insert)) {
102
+ this.cancel(entry);
103
+ break;
104
+ }
105
+ const insertAtEnd = insert && !empty && comparePositions(end, change.range.start) === 0;
106
+ start = shiftPosition(start, change);
107
+ end = insertAtEnd ? end : shiftPosition(end, change);
108
+ }
109
+ target.range = detached === undefined ? { start, end } : detached({ ...target.range, order: target.order });
110
+ if (entry.cancelled) {
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,2 @@
1
+ import { type ParsedSnippet } from './snippet.ts';
2
+ export declare function indentSnippet(parsed: ParsedSnippet, before: string): ParsedSnippet;
@@ -0,0 +1,49 @@
1
+ import { snippetOrderOf, withSnippetOrder } from './snippet-range-order.js';
2
+ import { MAX_SNIPPET_TEXT } from './snippet.js';
3
+ /* Indentation edits map the offsets once, including child projections in copied parents. */
4
+ export function indentSnippet(parsed, before) {
5
+ const prefix = /^[\t ]*/.exec(before)?.[0] ?? '';
6
+ if (prefix === '' || !parsed.text.includes('\n')) {
7
+ return parsed;
8
+ }
9
+ const insertions = [];
10
+ for (let offset = parsed.text.indexOf('\n'); offset !== -1; offset = parsed.text.indexOf('\n', offset + 1)) {
11
+ insertions.push(offset + 1);
12
+ }
13
+ if (parsed.text.length + insertions.length * prefix.length > MAX_SNIPPET_TEXT) {
14
+ return { text: parsed.text, stops: [], limited: true };
15
+ }
16
+ const shift = (offset, after = true) => {
17
+ let low = 0;
18
+ let high = insertions.length;
19
+ while (low < high) {
20
+ const middle = (low + high) >>> 1;
21
+ if (insertions[middle] < offset || (after && insertions[middle] === offset)) {
22
+ low = middle + 1;
23
+ }
24
+ else {
25
+ high = middle;
26
+ }
27
+ }
28
+ return offset + low * prefix.length;
29
+ };
30
+ const range = (range) => withSnippetOrder({
31
+ ...range,
32
+ start: shift(range.start),
33
+ end: shift(range.end, range.start === range.end || range.tail === true)
34
+ }, snippetOrderOf(range));
35
+ return {
36
+ text: parsed.text.replace(/\n/g, '\n' + prefix),
37
+ stops: parsed.stops.map((stop) => range(stop)),
38
+ ...(parsed.transforms === undefined ? {} : { transforms: parsed.transforms.map((transform) => range(transform)) }),
39
+ ...(parsed.fields === undefined
40
+ ? {}
41
+ : {
42
+ fields: parsed.fields.map((field) => ({
43
+ ...field,
44
+ ranges: field.ranges.map((child) => range(child)),
45
+ ...(field.linked === undefined ? {} : { linked: field.linked.map((child) => range(child)) })
46
+ }))
47
+ })
48
+ };
49
+ }
@@ -0,0 +1,3 @@
1
+ export declare function snippetOrderOf(range: object): number | undefined;
2
+ export declare function withSnippetOrder<Range extends object>(range: Range, order: number | undefined): Range;
3
+ export declare function compareSnippetOrder(left: readonly number[], right: readonly number[]): number;
@@ -0,0 +1,19 @@
1
+ /* Source order disambiguates empty stops without changing the public parsed range shape. */
2
+ const orders = new WeakMap();
3
+ export function snippetOrderOf(range) {
4
+ return orders.get(range);
5
+ }
6
+ export function withSnippetOrder(range, order) {
7
+ if (order !== undefined) {
8
+ orders.set(range, order);
9
+ }
10
+ return range;
11
+ }
12
+ export function compareSnippetOrder(left, right) {
13
+ for (let index = 0; index < Math.min(left.length, right.length); index++) {
14
+ if (left[index] !== right[index]) {
15
+ return left[index] - right[index];
16
+ }
17
+ }
18
+ return left.length - right.length;
19
+ }
@@ -1,17 +1,53 @@
1
- import type { EditorPosition } from '@adecore/editor';
1
+ import { type Timers } from './timers.ts';
2
+ import { type SnippetTransformJob } from './snippet-transform-protocol.ts';
3
+ import type { EditorContentChange, EditorPosition, EditorRange } from '@adecore/editor';
2
4
  import type { EditorLanguage } from './editor-language.ts';
3
- import type { SnippetStop } from './snippet.ts';
5
+ import type { SnippetField, SnippetStop } from './snippet.ts';
4
6
  export declare function positionInInsertion(start: EditorPosition, text: string, offset: number): EditorPosition;
7
+ export interface SnippetInsertion {
8
+ readonly start: EditorPosition;
9
+ readonly text: string;
10
+ readonly stops: readonly SnippetStop[];
11
+ readonly fields?: readonly SnippetField[];
12
+ }
13
+ type SnippetHost = Pick<EditorLanguage, 'editor' | 'onDispose'> & Partial<Pick<EditorLanguage, 'popups' | 'pick' | 'project'>>;
5
14
  export declare class SnippetFeature {
6
15
  private readonly language;
7
- private stops;
8
- private current;
9
- constructor(language: EditorLanguage);
16
+ private frames;
17
+ private placed;
18
+ private visiting;
19
+ private applying;
20
+ private extras;
21
+ private generation;
22
+ private choicePrefix;
23
+ private readonly transforms;
24
+ private readonly fieldTransforms;
25
+ private exitGeneration;
26
+ private detached;
27
+ constructor(language: SnippetHost, timers?: Timers);
28
+ transformVariables(jobs: readonly SnippetTransformJob[], current?: () => boolean): Promise<readonly string[] | null>;
29
+ get hasPendingTransforms(): boolean;
30
+ settleTransforms(current: () => boolean, edited?: (changes: readonly EditorContentChange[], map: (range: EditorRange) => EditorRange) => void): Promise<boolean>;
10
31
  get isActive(): boolean;
11
- begin(start: EditorPosition, text: string, stops: readonly SnippetStop[]): void;
32
+ get token(): number;
33
+ begin(start: EditorPosition | readonly EditorPosition[], text: string, stops: readonly SnippetStop[], fields?: readonly SnippetField[]): void;
34
+ beginInstances(insertions: readonly SnippetInsertion[], options?: {
35
+ nested?: boolean;
36
+ }): void;
37
+ applyEdits(changes: readonly EditorContentChange[], token: number, extras?: readonly EditorContentChange[]): boolean;
12
38
  end(): void;
39
+ private closeChoices;
40
+ private pop;
13
41
  private visit;
42
+ private announceField;
43
+ private fieldOf;
44
+ private openChoices;
45
+ private step;
14
46
  private key;
47
+ private exitField;
48
+ private applyFieldTransforms;
49
+ private mapFrame;
15
50
  private edited;
16
51
  private moved;
17
52
  }
53
+ export {};