@adecore/editor-react 0.0.1 → 0.17.0-beta.2

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 (264) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +60 -0
  3. package/dist/AnchoredPopup.d.ts +12 -0
  4. package/dist/AnchoredPopup.js +27 -0
  5. package/dist/AttributionCard.d.ts +14 -0
  6. package/dist/AttributionCard.js +5 -0
  7. package/dist/BaselineStatus.d.ts +4 -0
  8. package/dist/BaselineStatus.js +13 -0
  9. package/dist/ChangeReview.d.ts +12 -0
  10. package/dist/ChangeReview.js +27 -0
  11. package/dist/CodeAuthorsCard.d.ts +6 -0
  12. package/dist/CodeAuthorsCard.js +22 -0
  13. package/dist/CompletionPopup.d.ts +8 -0
  14. package/dist/CompletionPopup.js +103 -0
  15. package/dist/EditorContextMenu.d.ts +8 -0
  16. package/dist/EditorContextMenu.js +23 -0
  17. package/dist/EditorView.d.ts +16 -0
  18. package/dist/EditorView.js +27 -0
  19. package/dist/FindReplace.d.ts +6 -0
  20. package/dist/FindReplace.js +44 -0
  21. package/dist/HoverCard.d.ts +11 -0
  22. package/dist/HoverCard.js +38 -0
  23. package/dist/HoverSections.d.ts +21 -0
  24. package/dist/HoverSections.js +82 -0
  25. package/dist/LanguagePopups.d.ts +6 -0
  26. package/dist/LanguagePopups.js +38 -0
  27. package/dist/Markdown.d.ts +3 -0
  28. package/dist/Markdown.js +7 -0
  29. package/dist/PathText.d.ts +4 -0
  30. package/dist/PathText.js +12 -0
  31. package/dist/PeekPanel.d.ts +6 -0
  32. package/dist/PeekPanel.js +111 -0
  33. package/dist/PickPopup.d.ts +8 -0
  34. package/dist/PickPopup.js +33 -0
  35. package/dist/RenameCard.d.ts +9 -0
  36. package/dist/RenameCard.js +108 -0
  37. package/dist/SignatureCard.d.ts +4 -0
  38. package/dist/SignatureCard.js +8 -0
  39. package/dist/SymbolPicker.d.ts +6 -0
  40. package/dist/SymbolPicker.js +108 -0
  41. package/dist/code-actions-model.d.ts +23 -0
  42. package/dist/code-actions-model.js +114 -0
  43. package/dist/code-actions.d.ts +30 -0
  44. package/dist/code-actions.js +326 -0
  45. package/dist/code-authors.d.ts +18 -0
  46. package/dist/code-authors.js +79 -0
  47. package/dist/code-vision-model.d.ts +21 -0
  48. package/dist/code-vision-model.js +183 -0
  49. package/dist/code-vision.d.ts +48 -0
  50. package/dist/code-vision.js +236 -0
  51. package/dist/completion-call.d.ts +24 -0
  52. package/dist/completion-call.js +110 -0
  53. package/dist/completion-model.d.ts +51 -0
  54. package/dist/completion-model.js +353 -0
  55. package/dist/completion.d.ts +43 -0
  56. package/dist/completion.js +463 -0
  57. package/dist/conflict-model.d.ts +15 -0
  58. package/dist/conflict-model.js +36 -0
  59. package/dist/context-menu.d.ts +10 -0
  60. package/dist/context-menu.js +55 -0
  61. package/dist/definition-link.d.ts +14 -0
  62. package/dist/definition-link.js +99 -0
  63. package/dist/diagnostics-model.d.ts +15 -0
  64. package/dist/diagnostics-model.js +83 -0
  65. package/dist/diagnostics.d.ts +23 -0
  66. package/dist/diagnostics.js +95 -0
  67. package/dist/editor-language.d.ts +65 -0
  68. package/dist/editor-language.js +170 -0
  69. package/dist/editor-react.css +43 -0
  70. package/dist/find-query.d.ts +26 -0
  71. package/dist/find-query.js +51 -0
  72. package/dist/folding.d.ts +16 -0
  73. package/dist/folding.js +95 -0
  74. package/dist/highlight-layers.d.ts +10 -0
  75. package/dist/highlight-layers.js +30 -0
  76. package/dist/highlights.d.ts +16 -0
  77. package/dist/highlights.js +102 -0
  78. package/dist/history.d.ts +8 -0
  79. package/dist/history.js +46 -0
  80. package/dist/holder.d.ts +5 -0
  81. package/dist/holder.js +19 -0
  82. package/dist/host-types.d.ts +45 -0
  83. package/dist/host-types.js +1 -0
  84. package/dist/hover-content.d.ts +45 -0
  85. package/dist/hover-content.js +198 -0
  86. package/dist/hover.d.ts +32 -0
  87. package/dist/hover.js +260 -0
  88. package/dist/index.d.ts +44 -0
  89. package/dist/index.js +28 -0
  90. package/dist/inlay-hints.d.ts +13 -0
  91. package/dist/inlay-hints.js +71 -0
  92. package/dist/language-ids.d.ts +3 -0
  93. package/dist/language-ids.js +49 -0
  94. package/dist/line-edits.d.ts +8 -0
  95. package/dist/line-edits.js +39 -0
  96. package/dist/locales/en.json +171 -0
  97. package/dist/locales/nl.json +171 -0
  98. package/dist/models.d.ts +20 -0
  99. package/dist/models.js +20 -0
  100. package/dist/navigation-history.d.ts +16 -0
  101. package/dist/navigation-history.js +53 -0
  102. package/dist/navigation.d.ts +23 -0
  103. package/dist/navigation.js +155 -0
  104. package/dist/paths.d.ts +1 -0
  105. package/dist/paths.js +3 -0
  106. package/dist/peek-model.d.ts +28 -0
  107. package/dist/peek-model.js +104 -0
  108. package/dist/peek.d.ts +30 -0
  109. package/dist/peek.js +215 -0
  110. package/dist/pick.d.ts +34 -0
  111. package/dist/pick.js +166 -0
  112. package/dist/popup-placement.d.ts +23 -0
  113. package/dist/popup-placement.js +34 -0
  114. package/dist/popups.d.ts +133 -0
  115. package/dist/popups.js +14 -0
  116. package/dist/project-language.d.ts +36 -0
  117. package/dist/project-language.js +190 -0
  118. package/dist/project-problems.d.ts +24 -0
  119. package/dist/project-problems.js +63 -0
  120. package/dist/proposal-model.d.ts +36 -0
  121. package/dist/proposal-model.js +197 -0
  122. package/dist/recent-choices.d.ts +10 -0
  123. package/dist/recent-choices.js +39 -0
  124. package/dist/refresher.d.ts +13 -0
  125. package/dist/refresher.js +50 -0
  126. package/dist/relative-time.d.ts +1 -0
  127. package/dist/relative-time.js +9 -0
  128. package/dist/rename-model.d.ts +25 -0
  129. package/dist/rename-model.js +93 -0
  130. package/dist/rename.d.ts +21 -0
  131. package/dist/rename.js +257 -0
  132. package/dist/rendering-context.d.ts +6 -0
  133. package/dist/rendering-context.js +5 -0
  134. package/dist/rendering.d.ts +6 -0
  135. package/dist/rendering.js +5 -0
  136. package/dist/review-group.d.ts +16 -0
  137. package/dist/review-group.js +54 -0
  138. package/dist/row-host.d.ts +42 -0
  139. package/dist/row-host.js +120 -0
  140. package/dist/selection-ranges.d.ts +7 -0
  141. package/dist/selection-ranges.js +26 -0
  142. package/dist/semantic-model.d.ts +4 -0
  143. package/dist/semantic-model.js +76 -0
  144. package/dist/semantic-tokens.d.ts +6 -0
  145. package/dist/semantic-tokens.js +38 -0
  146. package/dist/shortcut-keys.d.ts +91 -0
  147. package/dist/shortcut-keys.js +12 -0
  148. package/dist/signature-model.d.ts +18 -0
  149. package/dist/signature-model.js +33 -0
  150. package/dist/signature.d.ts +22 -0
  151. package/dist/signature.js +150 -0
  152. package/dist/snippet-session.d.ts +17 -0
  153. package/dist/snippet-session.js +110 -0
  154. package/dist/snippet.d.ts +11 -0
  155. package/dist/snippet.js +134 -0
  156. package/dist/symbol-links.d.ts +6 -0
  157. package/dist/symbol-links.js +50 -0
  158. package/dist/symbol-picker-model.d.ts +44 -0
  159. package/dist/symbol-picker-model.js +146 -0
  160. package/dist/symbol-picker.d.ts +12 -0
  161. package/dist/symbol-picker.js +76 -0
  162. package/dist/symbols.d.ts +11 -0
  163. package/dist/symbols.js +95 -0
  164. package/dist/testing.d.ts +57 -0
  165. package/dist/testing.js +156 -0
  166. package/dist/timers.d.ts +14 -0
  167. package/dist/timers.js +34 -0
  168. package/dist/use-problem-counts.d.ts +3 -0
  169. package/dist/use-problem-counts.js +20 -0
  170. package/dist/view-state.d.ts +21 -0
  171. package/dist/view-state.js +63 -0
  172. package/dist/word-diff.d.ts +1 -0
  173. package/dist/word-diff.js +32 -0
  174. package/dist/workspace-edit.d.ts +21 -0
  175. package/dist/workspace-edit.js +147 -0
  176. package/package.json +74 -2
  177. package/src/AnchoredPopup.tsx +51 -0
  178. package/src/AttributionCard.tsx +36 -0
  179. package/src/BaselineStatus.tsx +20 -0
  180. package/src/ChangeReview.tsx +43 -0
  181. package/src/CodeAuthorsCard.tsx +81 -0
  182. package/src/CompletionPopup.tsx +196 -0
  183. package/src/EditorContextMenu.tsx +67 -0
  184. package/src/EditorView.tsx +47 -0
  185. package/src/FindReplace.tsx +89 -0
  186. package/src/HoverCard.tsx +121 -0
  187. package/src/HoverSections.tsx +146 -0
  188. package/src/LanguagePopups.tsx +65 -0
  189. package/src/Markdown.tsx +11 -0
  190. package/src/PathText.tsx +18 -0
  191. package/src/PeekPanel.tsx +222 -0
  192. package/src/PickPopup.tsx +102 -0
  193. package/src/RenameCard.tsx +219 -0
  194. package/src/SignatureCard.tsx +42 -0
  195. package/src/SymbolPicker.tsx +219 -0
  196. package/src/code-actions-model.ts +146 -0
  197. package/src/code-actions.ts +384 -0
  198. package/src/code-authors.ts +104 -0
  199. package/src/code-vision-model.ts +230 -0
  200. package/src/code-vision.ts +269 -0
  201. package/src/completion-call.ts +139 -0
  202. package/src/completion-model.ts +440 -0
  203. package/src/completion.ts +500 -0
  204. package/src/conflict-model.ts +56 -0
  205. package/src/context-menu.ts +63 -0
  206. package/src/definition-link.ts +106 -0
  207. package/src/diagnostics-model.ts +102 -0
  208. package/src/diagnostics.ts +117 -0
  209. package/src/editor-language.ts +189 -0
  210. package/src/editor-react.css +43 -0
  211. package/src/find-query.ts +74 -0
  212. package/src/folding.ts +110 -0
  213. package/src/highlight-layers.ts +37 -0
  214. package/src/highlights.ts +123 -0
  215. package/src/history.ts +56 -0
  216. package/src/holder.ts +19 -0
  217. package/src/host-types.ts +52 -0
  218. package/src/hover-content.ts +264 -0
  219. package/src/hover.ts +280 -0
  220. package/src/index.ts +63 -0
  221. package/src/inlay-hints.ts +85 -0
  222. package/src/language-ids.ts +54 -0
  223. package/src/line-edits.ts +48 -0
  224. package/src/locales/en.json +171 -0
  225. package/src/locales/nl.json +171 -0
  226. package/src/models.ts +157 -0
  227. package/src/navigation-history.ts +69 -0
  228. package/src/navigation.ts +168 -0
  229. package/src/paths.ts +3 -0
  230. package/src/peek-model.ts +147 -0
  231. package/src/peek.ts +247 -0
  232. package/src/pick.ts +197 -0
  233. package/src/popup-placement.ts +64 -0
  234. package/src/popups.ts +204 -0
  235. package/src/project-language.ts +223 -0
  236. package/src/project-problems.ts +87 -0
  237. package/src/proposal-model.ts +259 -0
  238. package/src/recent-choices.ts +48 -0
  239. package/src/refresher.ts +56 -0
  240. package/src/relative-time.ts +10 -0
  241. package/src/rename-model.ts +130 -0
  242. package/src/rename.ts +290 -0
  243. package/src/rendering-context.ts +12 -0
  244. package/src/rendering.tsx +6 -0
  245. package/src/review-group.ts +70 -0
  246. package/src/row-host.ts +158 -0
  247. package/src/selection-ranges.ts +32 -0
  248. package/src/semantic-model.ts +80 -0
  249. package/src/semantic-tokens.ts +46 -0
  250. package/src/shortcut-keys.ts +21 -0
  251. package/src/signature-model.ts +49 -0
  252. package/src/signature.ts +164 -0
  253. package/src/snippet-session.ts +122 -0
  254. package/src/snippet.ts +147 -0
  255. package/src/symbol-links.ts +72 -0
  256. package/src/symbol-picker-model.ts +198 -0
  257. package/src/symbol-picker.ts +85 -0
  258. package/src/symbols.ts +110 -0
  259. package/src/testing.ts +240 -0
  260. package/src/timers.ts +45 -0
  261. package/src/use-problem-counts.ts +25 -0
  262. package/src/view-state.ts +88 -0
  263. package/src/word-diff.ts +35 -0
  264. package/src/workspace-edit.ts +194 -0
package/dist/rename.js ADDED
@@ -0,0 +1,257 @@
1
+ import { StaleResultError } from '@adecore/lsp';
2
+ import { occurrencesOf, renamePreviewOf, renameTargetOf, wordRangeAt } from './rename-model.js';
3
+ import { isShortcut } from './shortcut-keys.js';
4
+ const METHOD = 'textDocument/rename';
5
+ const TOAST_ID = 'language-rename';
6
+ /* How many names are offered, and how many other lines and files are read to say how the symbol is used. */
7
+ const SAMPLE_LOCATIONS = 8;
8
+ const SAMPLE_OTHER_FILES = 3;
9
+ const CONTEXT_LINES = 3;
10
+ /*
11
+ * Renaming a symbol. The input sits on the symbol itself, every place the name has lights up while the
12
+ * new one is typed, Enter applies the server's edit at once and Shift+Enter lists what it would change first.
13
+ * Edits to other files land as unsaved drafts (`applyWorkspaceEdit`), so nothing is written to disk here.
14
+ */
15
+ export class RenameFeature {
16
+ tell(kind, title) {
17
+ this.language.project.host.notify?.({ id: TOAST_ID, kind, title });
18
+ }
19
+ language;
20
+ session = null;
21
+ constructor(language) {
22
+ this.language = language;
23
+ const { editor } = language;
24
+ const offs = [
25
+ editor.onKeyDown((event) => {
26
+ if (!isShortcut(this.language.shortcuts.rename, event)) {
27
+ return false;
28
+ }
29
+ void this.start();
30
+ return true;
31
+ }),
32
+ editor.onTextChange(() => this.cancel())
33
+ ];
34
+ language.onDispose(() => {
35
+ for (const off of offs) {
36
+ off();
37
+ }
38
+ this.cancel();
39
+ });
40
+ }
41
+ get isOpen() {
42
+ return this.session !== null;
43
+ }
44
+ async start() {
45
+ const { editor, project, uri } = this.language;
46
+ if (!project.service.supports(METHOD, uri)) {
47
+ this.tell('error', this.say('unavailable'));
48
+ return;
49
+ }
50
+ this.cancel();
51
+ const caret = editor.getCaret();
52
+ const prepare = project.service.providerOptions(METHOD, uri)?.prepareProvider === true;
53
+ let answer;
54
+ try {
55
+ answer = prepare ? await project.service.prepareRename(uri, caret) : { defaultBehavior: true };
56
+ }
57
+ catch (error) {
58
+ if (!(error instanceof StaleResultError)) {
59
+ this.tell('error', this.say('nothing'));
60
+ }
61
+ return;
62
+ }
63
+ const target = renameTargetOf(answer, (range) => editor.textInRange(range), () => wordRangeAt(editor.textInRange({ start: { line: caret.line, character: 0 }, end: { line: caret.line, character: Number.MAX_SAFE_INTEGER } }), caret));
64
+ if (target === null) {
65
+ this.tell('error', this.say('nothing'));
66
+ return;
67
+ }
68
+ const session = { target, original: editor.textInRange(target.range), edit: null, names: new AbortController() };
69
+ this.session = session;
70
+ this.publish({ phase: 'input', busy: false, name: target.placeholder, files: [], occurrences: null, suggestions: [] });
71
+ void this.light(session).then((locations) => this.suggest(session, locations));
72
+ }
73
+ /* The name was typed: Enter applies it, Shift+Enter lists what it would change first. */
74
+ async submit(name, preview) {
75
+ const session = this.session;
76
+ const next = name.trim();
77
+ if (session === null) {
78
+ return;
79
+ }
80
+ if (next === '' || next === session.original) {
81
+ this.finish();
82
+ return;
83
+ }
84
+ this.publish({ phase: 'input', busy: true, name: next, error: null });
85
+ const { project, uri } = this.language;
86
+ try {
87
+ const edit = await project.service.rename(uri, session.target.range.start, next);
88
+ if (this.session !== session) {
89
+ return;
90
+ }
91
+ if (edit === null) {
92
+ this.fail(this.say('refused'));
93
+ return;
94
+ }
95
+ session.edit = edit;
96
+ if (!preview) {
97
+ await this.apply(session);
98
+ return;
99
+ }
100
+ const files = await renamePreviewOf(edit, (target) => project.readText(target));
101
+ if (this.session !== session) {
102
+ return;
103
+ }
104
+ if (files === null) {
105
+ // An edit that renames files too has no list of lines to show, so it is applied or refused as a whole.
106
+ await this.apply(session);
107
+ return;
108
+ }
109
+ this.publish({ phase: 'preview', busy: false, name: next, files });
110
+ }
111
+ catch (error) {
112
+ if (this.session === session) {
113
+ this.fail(error instanceof StaleResultError ? this.say('stale') : error instanceof Error ? error.message : String(error));
114
+ }
115
+ }
116
+ }
117
+ /* Applies the edit the preview showed. */
118
+ async confirm() {
119
+ const session = this.session;
120
+ if (session?.edit) {
121
+ await this.apply(session);
122
+ }
123
+ }
124
+ cancel() {
125
+ if (this.session === null) {
126
+ return;
127
+ }
128
+ this.finish();
129
+ }
130
+ async apply(session) {
131
+ const result = await this.language.project.applyWorkspaceEdit(session.edit);
132
+ if (this.session !== session) {
133
+ return;
134
+ }
135
+ if (result.applied) {
136
+ this.finish();
137
+ }
138
+ else {
139
+ this.fail(result.failureReason ?? this.say('refused'));
140
+ }
141
+ }
142
+ /* The input stays, with the reason under it, so the name can be changed and tried again. */
143
+ fail(message) {
144
+ if (this.session !== null) {
145
+ this.session.edit = null;
146
+ this.publish({ phase: 'input', busy: false, files: [], error: this.say('failed', { message }) });
147
+ }
148
+ }
149
+ /* The name was changed, which answers the failure under it. */
150
+ edited() {
151
+ if (this.language.popups.getState().rename?.error != null) {
152
+ this.publish({ error: null });
153
+ }
154
+ }
155
+ finish() {
156
+ this.session?.names.abort();
157
+ this.session = null;
158
+ const { editor, popups } = this.language;
159
+ popups.setState({ rename: null });
160
+ editor.setHighlights([]);
161
+ editor.focus();
162
+ }
163
+ /* Lights up the places the name has in this file, and counts them all; what the servers listed, or null when they did not. */
164
+ async light(session) {
165
+ const { editor, project, uri } = this.language;
166
+ if (!project.service.supports('textDocument/references', uri)) {
167
+ return null;
168
+ }
169
+ try {
170
+ const locations = await project.service.references(uri, session.target.range.start, true);
171
+ if (this.session !== session || locations === null) {
172
+ return null;
173
+ }
174
+ const found = occurrencesOf(locations, uri);
175
+ editor.setHighlights(found.inFile.map((range) => ({ range, kind: 'write' })));
176
+ this.publish({ occurrences: { count: found.count, files: found.files } });
177
+ return locations;
178
+ }
179
+ catch {
180
+ // The count is a courtesy; renaming does not wait for it.
181
+ return null;
182
+ }
183
+ }
184
+ /*
185
+ * Names from the host suggestion provider, using the lines around the symbol and a sample of the lines that
186
+ * use it. They only appear under the input: the name that is typed stays the one that is applied.
187
+ */
188
+ async suggest(session, locations) {
189
+ const suggestNames = this.language.project.host.suggestNames;
190
+ if (suggestNames === undefined || this.session !== session) {
191
+ return;
192
+ }
193
+ const { editor, languageId, uri } = this.language;
194
+ try {
195
+ const line = session.target.range.start.line;
196
+ const context = editor.textInRange({
197
+ start: { line: Math.max(0, line - CONTEXT_LINES), character: 0 },
198
+ end: { line: line + CONTEXT_LINES, character: Number.MAX_SAFE_INTEGER }
199
+ });
200
+ const uses = await this.linesOf(locations ?? []);
201
+ const names = await suggestNames({ uri, languageId, name: session.original, context, uses }, session.names.signal);
202
+ if (this.session === session && names.length > 0) {
203
+ this.publish({ suggestions: names });
204
+ }
205
+ }
206
+ catch {
207
+ // Suggestions do not prevent a manual rename.
208
+ }
209
+ }
210
+ /* The text of the first lines that name the symbol, in the order the servers listed them, reading at most a few other files. */
211
+ async linesOf(locations) {
212
+ const { project } = this.language;
213
+ const texts = new Map();
214
+ const lines = [];
215
+ let others = 0;
216
+ for (const location of locations.slice(0, SAMPLE_LOCATIONS)) {
217
+ if (!texts.has(location.uri)) {
218
+ if (location.uri !== this.language.uri && others++ >= SAMPLE_OTHER_FILES) {
219
+ continue;
220
+ }
221
+ texts.set(location.uri, (await project.readText(location.uri))?.split('\n') ?? null);
222
+ }
223
+ const text = texts.get(location.uri)?.[location.range.start.line];
224
+ if (text !== undefined && text.trim() !== '' && !lines.includes(text)) {
225
+ lines.push(text);
226
+ }
227
+ }
228
+ return lines;
229
+ }
230
+ publish(patch) {
231
+ const session = this.session;
232
+ if (session === null) {
233
+ return;
234
+ }
235
+ const { popups } = this.language;
236
+ const current = popups.getState().rename;
237
+ popups.setState({
238
+ rename: {
239
+ phase: 'input',
240
+ range: session.target.range,
241
+ original: session.original,
242
+ placeholder: session.target.placeholder,
243
+ occurrences: null,
244
+ busy: false,
245
+ error: null,
246
+ name: session.target.placeholder,
247
+ files: [],
248
+ suggestions: [],
249
+ ...current,
250
+ ...patch
251
+ }
252
+ });
253
+ }
254
+ say(key, options) {
255
+ return this.language.project.i18n.t(`editor:language.rename.${key}`, options);
256
+ }
257
+ }
@@ -0,0 +1,6 @@
1
+ export interface EditorRendering {
2
+ readonly theme: string;
3
+ highlight?(code: string, language: string, theme: string): Promise<string>;
4
+ }
5
+ export declare const RenderingContext: import("react").Context<EditorRendering>;
6
+ export declare function useEditorRendering(): EditorRendering;
@@ -0,0 +1,5 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const RenderingContext = createContext({ theme: 'light' });
3
+ export function useEditorRendering() {
4
+ return useContext(RenderingContext);
5
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type EditorRendering } from './rendering-context.ts';
3
+ export declare function EditorRenderingProvider({ value, children }: {
4
+ value: EditorRendering;
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { RenderingContext } from './rendering-context.js';
3
+ export function EditorRenderingProvider({ value, children }) {
4
+ return _jsx(RenderingContext, { value: value, children: children });
5
+ }
@@ -0,0 +1,16 @@
1
+ export interface ReviewMember {
2
+ hide(runIds: readonly string[]): void;
3
+ show(runIds: readonly string[]): void;
4
+ refresh(): void;
5
+ }
6
+ export declare class ReviewGroup {
7
+ private readonly members;
8
+ join(member: ReviewMember): () => void;
9
+ hide(runIds: readonly string[]): void;
10
+ show(runIds: readonly string[]): void;
11
+ refresh(): void;
12
+ }
13
+ export declare function joinReviewGroup(key: string): {
14
+ group: ReviewGroup;
15
+ leave(): void;
16
+ };
@@ -0,0 +1,54 @@
1
+ /*
2
+ * The editors of one file in this window. An answer in one of them is an answer for all: the rows of
3
+ * the others go with it at once and every one of them reads the runs again, so no editor keeps a
4
+ * change in front of a person that the other already took.
5
+ */
6
+ export class ReviewGroup {
7
+ members = new Set();
8
+ join(member) {
9
+ this.members.add(member);
10
+ return () => {
11
+ this.members.delete(member);
12
+ };
13
+ }
14
+ hide(runIds) {
15
+ for (const member of [...this.members]) {
16
+ member.hide(runIds);
17
+ }
18
+ }
19
+ show(runIds) {
20
+ for (const member of [...this.members]) {
21
+ member.show(runIds);
22
+ }
23
+ }
24
+ refresh() {
25
+ for (const member of [...this.members]) {
26
+ member.refresh();
27
+ }
28
+ }
29
+ }
30
+ const groups = new Map();
31
+ /* The host supplies a shared file key; the group is released with its last editor. */
32
+ export function joinReviewGroup(key) {
33
+ let entry = groups.get(key);
34
+ if (entry === undefined) {
35
+ entry = { group: new ReviewGroup(), holders: 0 };
36
+ groups.set(key, entry);
37
+ }
38
+ entry.holders++;
39
+ const held = entry;
40
+ let left = false;
41
+ return {
42
+ group: held.group,
43
+ leave: () => {
44
+ if (left) {
45
+ return;
46
+ }
47
+ left = true;
48
+ held.holders--;
49
+ if (held.holders === 0 && groups.get(key) === held) {
50
+ groups.delete(key);
51
+ }
52
+ }
53
+ };
54
+ }
@@ -0,0 +1,42 @@
1
+ import type { Editor } from '@adecore/editor';
2
+ export interface HostedRow {
3
+ id: string;
4
+ line: number;
5
+ placement: 'above' | 'below';
6
+ height: number;
7
+ }
8
+ export declare class RowHost {
9
+ private readonly editor;
10
+ private readonly owner;
11
+ private readonly containers;
12
+ private readonly listeners;
13
+ private version;
14
+ private signature;
15
+ constructor(editor: Editor, owner: string);
16
+ getVersion: () => number;
17
+ subscribe: (listener: () => void) => (() => void);
18
+ container(id: string): HTMLElement | undefined;
19
+ set(rows: readonly HostedRow[]): void;
20
+ clear(): void;
21
+ private mounted;
22
+ private bump;
23
+ }
24
+ export interface HostedAction {
25
+ id: string;
26
+ line: number;
27
+ }
28
+ export declare class LineActionHost {
29
+ private readonly editor;
30
+ private readonly owner;
31
+ private readonly containers;
32
+ private readonly listeners;
33
+ private version;
34
+ private signature;
35
+ constructor(editor: Editor, owner: string);
36
+ getVersion: () => number;
37
+ subscribe: (listener: () => void) => (() => void);
38
+ container(id: string): HTMLElement | undefined;
39
+ set(actions: readonly HostedAction[]): void;
40
+ clear(): void;
41
+ private bump;
42
+ }
@@ -0,0 +1,120 @@
1
+ /*
2
+ * The rows of one owner in an editor, and the elements the editor draws them in, so React can portal
3
+ * into them. The editor makes an element again whenever its row scrolls back into view, which is why a
4
+ * row's content never keeps state of its own.
5
+ */
6
+ export class RowHost {
7
+ editor;
8
+ owner;
9
+ containers = new Map();
10
+ listeners = new Set();
11
+ version = 0;
12
+ signature = '';
13
+ constructor(editor, owner) {
14
+ this.editor = editor;
15
+ this.owner = owner;
16
+ }
17
+ getVersion = () => this.version;
18
+ subscribe = (listener) => {
19
+ this.listeners.add(listener);
20
+ return () => {
21
+ this.listeners.delete(listener);
22
+ };
23
+ };
24
+ container(id) {
25
+ return this.containers.get(id);
26
+ }
27
+ /* Sets the owner's rows; the same rows again are left alone, so an editor does not measure them twice. */
28
+ set(rows) {
29
+ const signature = rows.map((row) => `${row.id}@${row.line}${row.placement}`).join('|');
30
+ if (signature === this.signature) {
31
+ return;
32
+ }
33
+ this.signature = signature;
34
+ const kept = new Set(rows.map((row) => row.id));
35
+ for (const id of [...this.containers.keys()]) {
36
+ if (!kept.has(id)) {
37
+ this.containers.delete(id);
38
+ }
39
+ }
40
+ this.editor.setWidgets(rows.map((row) => ({
41
+ id: row.id,
42
+ line: row.line,
43
+ placement: row.placement,
44
+ height: row.height,
45
+ render: (container) => this.mounted(row.id, container)
46
+ })), this.owner);
47
+ this.bump();
48
+ }
49
+ clear() {
50
+ this.set([]);
51
+ }
52
+ mounted(id, container) {
53
+ this.containers.set(id, container);
54
+ this.bump();
55
+ }
56
+ bump() {
57
+ this.version++;
58
+ for (const listener of [...this.listeners]) {
59
+ listener();
60
+ }
61
+ }
62
+ }
63
+ /*
64
+ * The actions of one owner after the end of lines in an editor, and the elements the editor makes for
65
+ * them. An element lives as long as its action does, unlike a row's, so what React draws into it keeps
66
+ * its state.
67
+ */
68
+ export class LineActionHost {
69
+ editor;
70
+ owner;
71
+ containers = new Map();
72
+ listeners = new Set();
73
+ version = 0;
74
+ signature = '';
75
+ constructor(editor, owner) {
76
+ this.editor = editor;
77
+ this.owner = owner;
78
+ }
79
+ getVersion = () => this.version;
80
+ subscribe = (listener) => {
81
+ this.listeners.add(listener);
82
+ return () => {
83
+ this.listeners.delete(listener);
84
+ };
85
+ };
86
+ container(id) {
87
+ return this.containers.get(id);
88
+ }
89
+ set(actions) {
90
+ const signature = actions.map((action) => `${action.id}@${action.line}`).join('|');
91
+ if (signature === this.signature) {
92
+ return;
93
+ }
94
+ this.signature = signature;
95
+ const kept = new Set(actions.map((action) => action.id));
96
+ for (const id of [...this.containers.keys()]) {
97
+ if (!kept.has(id)) {
98
+ this.containers.delete(id);
99
+ }
100
+ }
101
+ this.editor.setLineActions(actions.map((action) => ({
102
+ id: action.id,
103
+ line: action.line,
104
+ render: (container) => {
105
+ this.containers.set(action.id, container);
106
+ this.bump();
107
+ }
108
+ })), this.owner);
109
+ this.bump();
110
+ }
111
+ clear() {
112
+ this.set([]);
113
+ }
114
+ bump() {
115
+ this.version++;
116
+ for (const listener of [...this.listeners]) {
117
+ listener();
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,7 @@
1
+ import type { EditorRange } from '@adecore/editor';
2
+ import type { SelectionRange } from '@adecore/lsp';
3
+ import type { EditorLanguage } from './editor-language.ts';
4
+ export declare function selectionChainOf(selection: SelectionRange): EditorRange[];
5
+ export declare class SelectionRangesFeature {
6
+ constructor(language: EditorLanguage);
7
+ }
@@ -0,0 +1,26 @@
1
+ const METHOD = 'textDocument/selectionRange';
2
+ /* The links of one answer, the smallest range first. */
3
+ export function selectionChainOf(selection) {
4
+ const chain = [];
5
+ for (let link = selection; link; link = link.parent) {
6
+ chain.push(link.range);
7
+ }
8
+ return chain;
9
+ }
10
+ /*
11
+ * Extend and Shrink Selection grow through the ranges the language server knows around the caret, which
12
+ * follow the syntax of the file. Where no server offers them the editor grows by its own rule.
13
+ */
14
+ export class SelectionRangesFeature {
15
+ constructor(language) {
16
+ const { editor, project, uri } = language;
17
+ editor.setSelectionRanges(async (positions) => {
18
+ if (!project.service.supports(METHOD, uri)) {
19
+ return null;
20
+ }
21
+ const chains = await project.service.selectionRanges(uri, positions);
22
+ return chains === null || chains.length !== positions.length ? null : chains.map(selectionChainOf);
23
+ });
24
+ language.onDispose(() => editor.setSelectionRanges(null));
25
+ }
26
+ }
@@ -0,0 +1,4 @@
1
+ import type { EditorSemanticToken } from '@adecore/editor';
2
+ import type { SemanticTokens, SemanticTokensLegend } from '@adecore/lsp';
3
+ export declare function scopesOf(type: string, modifiers: ReadonlySet<string>, scoped?: boolean): string[] | null;
4
+ export declare function decodeSemanticTokens(tokens: SemanticTokens, legend: SemanticTokensLegend): EditorSemanticToken[];
@@ -0,0 +1,76 @@
1
+ /*
2
+ * The TextMate scopes a classification stands for, so the editor's theme colors it the way its own
3
+ * grammar colors the same thing. Null where the grammar already did better: a keyword, a string, a
4
+ * local variable. A function being called, a method, a static member and a declaration are scopes of
5
+ * their own, as the platform's color scheme tells them apart. `scoped` says the server marks what is
6
+ * local to a function, so a variable it leaves unmarked lives at the top of a file or comes from a
7
+ * library, which the scheme draws as a global.
8
+ */
9
+ export function scopesOf(type, modifiers, scoped = false) {
10
+ switch (type) {
11
+ case 'namespace':
12
+ return ['entity.name.namespace'];
13
+ case 'class':
14
+ return ['entity.name.type.class'];
15
+ case 'enum':
16
+ return ['entity.name.type.enum'];
17
+ case 'interface':
18
+ return ['entity.name.type.interface'];
19
+ case 'struct':
20
+ return ['entity.name.type.struct'];
21
+ case 'type':
22
+ return ['entity.name.type'];
23
+ case 'typeParameter':
24
+ return ['entity.name.type.parameter'];
25
+ case 'parameter':
26
+ return ['variable.parameter'];
27
+ case 'property':
28
+ return [modifiers.has('static') ? 'variable.other.property.static' : 'variable.other.property'];
29
+ case 'enumMember':
30
+ return ['variable.other.enummember'];
31
+ case 'function':
32
+ case 'method':
33
+ if (modifiers.has('static')) {
34
+ return ['entity.name.function.static'];
35
+ }
36
+ if (modifiers.has('declaration')) {
37
+ return ['entity.name.function'];
38
+ }
39
+ return [type === 'method' ? 'entity.name.function.method' : 'entity.name.function.call'];
40
+ case 'decorator':
41
+ return ['meta.decorator'];
42
+ case 'macro':
43
+ return ['entity.name.function.macro'];
44
+ case 'variable':
45
+ if (scoped) {
46
+ return modifiers.has('local') ? null : ['variable.other.constant'];
47
+ }
48
+ return modifiers.has('readonly') ? ['variable.other.constant'] : null;
49
+ default:
50
+ return null;
51
+ }
52
+ }
53
+ /* The encoded data of `textDocument/semanticTokens` as one token per classified range, the ones the theme has nothing to say about left out. */
54
+ export function decodeSemanticTokens(tokens, legend) {
55
+ const result = [];
56
+ const { data } = tokens;
57
+ const scoped = legend.tokenModifiers.includes('local');
58
+ let line = 0;
59
+ let character = 0;
60
+ for (let at = 0; at + 4 < data.length; at += 5) {
61
+ const deltaLine = data[at];
62
+ line += deltaLine;
63
+ character = deltaLine === 0 ? character + data[at + 1] : data[at + 1];
64
+ const type = legend.tokenTypes[data[at + 3]];
65
+ if (type === undefined) {
66
+ continue;
67
+ }
68
+ const bits = data[at + 4];
69
+ const modifiers = new Set(legend.tokenModifiers.filter((_, index) => (bits & (1 << index)) !== 0));
70
+ const scopes = scopesOf(type, modifiers, scoped);
71
+ if (scopes !== null) {
72
+ result.push({ line, character, length: data[at + 2], scopes });
73
+ }
74
+ }
75
+ return result;
76
+ }
@@ -0,0 +1,6 @@
1
+ import type { EditorLanguage } from './editor-language.ts';
2
+ import { type Timers } from './timers.ts';
3
+ export declare class SemanticTokensFeature {
4
+ private readonly refresher;
5
+ constructor(language: EditorLanguage, timers?: Timers);
6
+ }
@@ -0,0 +1,38 @@
1
+ import { Refresher } from './refresher.js';
2
+ import { decodeSemanticTokens } from './semantic-model.js';
3
+ import { realTimers } from './timers.js';
4
+ const METHOD = 'textDocument/semanticTokens/full';
5
+ const PAUSE_MS = 200;
6
+ /*
7
+ * Colors the editor draws from what the language servers know, over what the grammar made of the text:
8
+ * a class is a class and a call is a call where the grammar could only guess. It asks after a pause in
9
+ * typing and when a server says its offer changed, and keeps drawing the last answer, which follows its
10
+ * text through edits, in between, so the colors never blink out while the next one is on its way.
11
+ */
12
+ export class SemanticTokensFeature {
13
+ refresher;
14
+ constructor(language, timers = realTimers) {
15
+ const { editor, project, uri } = language;
16
+ this.refresher = new Refresher(async (signal) => {
17
+ const { service } = project;
18
+ const legend = service.providerOptions(METHOD, uri)?.legend;
19
+ if (!service.supports(METHOD, uri) || legend === undefined) {
20
+ return;
21
+ }
22
+ const tokens = await service.semanticTokens(uri, { signal });
23
+ if (!signal.aborted) {
24
+ editor.setSemanticTokens(tokens === null ? null : decodeSemanticTokens(tokens, legend));
25
+ }
26
+ }, PAUSE_MS, timers);
27
+ const edits = editor.onTextChange(() => this.refresher.later());
28
+ const providers = language.onProvidersChanged(() => {
29
+ this.refresher.now();
30
+ });
31
+ language.onDispose(() => {
32
+ edits();
33
+ providers();
34
+ this.refresher.dispose();
35
+ editor.setSemanticTokens(null);
36
+ });
37
+ }
38
+ }