@adecore/editor-react 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/CallHierarchyPanel.d.ts +6 -0
  2. package/dist/CallHierarchyPanel.js +88 -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/PeekPanel.d.ts +3 -0
  15. package/dist/PeekPanel.js +2 -3
  16. package/dist/PickPopup.js +15 -2
  17. package/dist/RenameCard.js +10 -5
  18. package/dist/RenderedDocs.d.ts +9 -0
  19. package/dist/RenderedDocs.js +84 -0
  20. package/dist/SplitDiffView.d.ts +9 -0
  21. package/dist/SplitDiffView.js +276 -0
  22. package/dist/SymbolPicker.d.ts +3 -0
  23. package/dist/SymbolPicker.js +4 -3
  24. package/dist/call-hierarchy-model.d.ts +52 -0
  25. package/dist/call-hierarchy-model.js +99 -0
  26. package/dist/call-hierarchy.d.ts +42 -0
  27. package/dist/call-hierarchy.js +399 -0
  28. package/dist/code-actions-model.d.ts +2 -0
  29. package/dist/code-actions-model.js +12 -10
  30. package/dist/code-actions.js +3 -1
  31. package/dist/completion-model.d.ts +14 -4
  32. package/dist/completion-model.js +40 -33
  33. package/dist/completion.d.ts +4 -0
  34. package/dist/completion.js +257 -42
  35. package/dist/diagnostics.js +8 -6
  36. package/dist/diff-expansion.d.ts +9 -0
  37. package/dist/diff-expansion.js +41 -0
  38. package/dist/diff-overlays.d.ts +15 -0
  39. package/dist/diff-overlays.js +28 -0
  40. package/dist/diff-syntax.d.ts +16 -0
  41. package/dist/diff-syntax.js +49 -0
  42. package/dist/diff-wordmarks.d.ts +5 -0
  43. package/dist/diff-wordmarks.js +26 -0
  44. package/dist/doc-comment-model.d.ts +23 -0
  45. package/dist/doc-comment-model.js +291 -0
  46. package/dist/editor-language.d.ts +13 -1
  47. package/dist/editor-language.js +14 -4
  48. package/dist/editor-react.css +79 -0
  49. package/dist/format-on-type.d.ts +14 -0
  50. package/dist/format-on-type.js +96 -0
  51. package/dist/host-types.d.ts +17 -0
  52. package/dist/hover.d.ts +4 -0
  53. package/dist/hover.js +15 -2
  54. package/dist/index.d.ts +19 -5
  55. package/dist/index.js +9 -1
  56. package/dist/inlay-hints.d.ts +47 -2
  57. package/dist/inlay-hints.js +353 -16
  58. package/dist/language-ids.js +4 -2
  59. package/dist/locales/en.json +114 -3
  60. package/dist/locales/nl.json +114 -3
  61. package/dist/models.d.ts +6 -2
  62. package/dist/models.js +5 -1
  63. package/dist/patch-decorations.d.ts +27 -0
  64. package/dist/patch-decorations.js +182 -0
  65. package/dist/patch-layout.d.ts +40 -0
  66. package/dist/patch-layout.js +304 -0
  67. package/dist/peek.js +1 -0
  68. package/dist/pick.d.ts +1 -0
  69. package/dist/pick.js +11 -1
  70. package/dist/popups.d.ts +17 -0
  71. package/dist/popups.js +1 -0
  72. package/dist/project-language.d.ts +8 -0
  73. package/dist/project-language.js +34 -2
  74. package/dist/refresher.d.ts +1 -0
  75. package/dist/refresher.js +5 -0
  76. package/dist/rename-model.d.ts +1 -1
  77. package/dist/rename-model.js +3 -7
  78. package/dist/rename.js +5 -8
  79. package/dist/rendered-docs.d.ts +62 -0
  80. package/dist/rendered-docs.js +368 -0
  81. package/dist/row-host.d.ts +2 -0
  82. package/dist/row-host.js +11 -3
  83. package/dist/semantic-delta.d.ts +2 -0
  84. package/dist/semantic-delta.js +27 -0
  85. package/dist/semantic-model.js +16 -0
  86. package/dist/semantic-tokens.d.ts +26 -1
  87. package/dist/semantic-tokens.js +174 -14
  88. package/dist/shortcut-keys.d.ts +2 -0
  89. package/dist/shortcut-keys.js +6 -2
  90. package/dist/signature.js +1 -1
  91. package/dist/snippet-field-transforms.d.ts +30 -0
  92. package/dist/snippet-field-transforms.js +116 -0
  93. package/dist/snippet-indent.d.ts +2 -0
  94. package/dist/snippet-indent.js +49 -0
  95. package/dist/snippet-range-order.d.ts +3 -0
  96. package/dist/snippet-range-order.js +19 -0
  97. package/dist/snippet-session.d.ts +42 -6
  98. package/dist/snippet-session.js +650 -53
  99. package/dist/snippet-transform-client.d.ts +19 -0
  100. package/dist/snippet-transform-client.js +66 -0
  101. package/dist/snippet-transform-format.d.ts +15 -0
  102. package/dist/snippet-transform-format.js +302 -0
  103. package/dist/snippet-transform-protocol.d.ts +45 -0
  104. package/dist/snippet-transform-protocol.js +8 -0
  105. package/dist/snippet-transform-queue.d.ts +32 -0
  106. package/dist/snippet-transform-queue.js +173 -0
  107. package/dist/snippet-transform-worker.d.ts +10 -0
  108. package/dist/snippet-transform-worker.js +41 -0
  109. package/dist/snippet-variable-names.d.ts +1 -0
  110. package/dist/snippet-variable-names.js +36 -0
  111. package/dist/snippet-variables.d.ts +7 -0
  112. package/dist/snippet-variables.js +66 -0
  113. package/dist/snippet.d.ts +30 -3
  114. package/dist/snippet.js +402 -87
  115. package/dist/split-alignment.d.ts +6 -0
  116. package/dist/split-alignment.js +38 -0
  117. package/dist/split-scroll.d.ts +14 -0
  118. package/dist/split-scroll.js +45 -0
  119. package/dist/symbols.d.ts +2 -1
  120. package/dist/symbols.js +39 -7
  121. package/dist/testing.d.ts +5 -1
  122. package/dist/testing.js +12 -0
  123. package/dist/workspace-edit.d.ts +25 -3
  124. package/dist/workspace-edit.js +282 -143
  125. package/package.json +10 -5
  126. package/src/CallHierarchyPanel.tsx +238 -0
  127. package/src/CompletionPopup.tsx +12 -1
  128. package/src/DiffView.tsx +262 -0
  129. package/src/EditorContextMenu.tsx +1 -0
  130. package/src/EditorView.tsx +21 -2
  131. package/src/FindReplace.tsx +51 -13
  132. package/src/HoverCard.tsx +23 -21
  133. package/src/LanguagePopups.tsx +5 -0
  134. package/src/Markdown.tsx +10 -3
  135. package/src/PeekPanel.tsx +2 -3
  136. package/src/PickPopup.tsx +18 -2
  137. package/src/RenameCard.tsx +18 -0
  138. package/src/RenderedDocs.tsx +155 -0
  139. package/src/SplitDiffView.tsx +337 -0
  140. package/src/SymbolPicker.tsx +4 -3
  141. package/src/call-hierarchy-model.ts +158 -0
  142. package/src/call-hierarchy.ts +441 -0
  143. package/src/code-actions-model.ts +17 -11
  144. package/src/code-actions.ts +3 -1
  145. package/src/completion-model.ts +61 -35
  146. package/src/completion.ts +302 -42
  147. package/src/diagnostics.ts +8 -6
  148. package/src/diff-expansion.ts +49 -0
  149. package/src/diff-overlays.tsx +69 -0
  150. package/src/diff-syntax.ts +67 -0
  151. package/src/diff-wordmarks.ts +31 -0
  152. package/src/doc-comment-model.ts +336 -0
  153. package/src/editor-language.ts +21 -5
  154. package/src/editor-react.css +79 -0
  155. package/src/format-on-type.ts +111 -0
  156. package/src/host-types.ts +19 -0
  157. package/src/hover.ts +18 -3
  158. package/src/index.ts +27 -4
  159. package/src/inlay-hints.ts +394 -18
  160. package/src/language-ids.ts +4 -2
  161. package/src/locales/en.json +114 -3
  162. package/src/locales/nl.json +114 -3
  163. package/src/models.ts +40 -1
  164. package/src/patch-decorations.ts +224 -0
  165. package/src/patch-layout.ts +388 -0
  166. package/src/peek.ts +1 -0
  167. package/src/pick.ts +12 -1
  168. package/src/popups.ts +24 -0
  169. package/src/project-language.ts +43 -2
  170. package/src/refresher.ts +6 -0
  171. package/src/rename-model.ts +4 -8
  172. package/src/rename.ts +5 -8
  173. package/src/rendered-docs.ts +422 -0
  174. package/src/row-host.ts +12 -3
  175. package/src/semantic-delta.ts +29 -0
  176. package/src/semantic-model.ts +17 -0
  177. package/src/semantic-tokens.ts +202 -19
  178. package/src/shortcut-keys.ts +6 -2
  179. package/src/signature.ts +1 -1
  180. package/src/snippet-field-transforms.ts +144 -0
  181. package/src/snippet-indent.ts +53 -0
  182. package/src/snippet-range-order.ts +19 -0
  183. package/src/snippet-session.ts +797 -56
  184. package/src/snippet-transform-client.ts +84 -0
  185. package/src/snippet-transform-format.ts +325 -0
  186. package/src/snippet-transform-protocol.ts +24 -0
  187. package/src/snippet-transform-queue.ts +199 -0
  188. package/src/snippet-transform-worker.ts +58 -0
  189. package/src/snippet-variable-names.ts +36 -0
  190. package/src/snippet-variables.ts +74 -0
  191. package/src/snippet.ts +452 -89
  192. package/src/split-alignment.ts +45 -0
  193. package/src/split-scroll.ts +54 -0
  194. package/src/symbols.ts +43 -7
  195. package/src/testing.ts +25 -0
  196. package/src/workspace-edit.ts +347 -169
@@ -9,6 +9,9 @@
9
9
  "line": "Regel {{line}}",
10
10
  "none": "Geen informatie voor deze plek"
11
11
  },
12
+ "inlay": {
13
+ "failed": "De actie van de hint is mislukt: {{message}}"
14
+ },
12
15
  "edit": {
13
16
  "staged_one": "{{count}} bestand gewijzigd en nog niet opgeslagen",
14
17
  "staged_other": "{{count}} bestanden gewijzigd en nog niet opgeslagen",
@@ -30,6 +33,10 @@
30
33
  "otherFiles_other": "en nog {{count}} bestanden",
31
34
  "moves_one": "verplaatst {{count}} bestand",
32
35
  "moves_other": "verplaatst {{count}} bestanden",
36
+ "creates_one": "maakt {{count}} bestand aan",
37
+ "creates_other": "maakt {{count}} bestanden aan",
38
+ "deletes_one": "verwijdert {{count}} bestand",
39
+ "deletes_other": "verwijdert {{count}} bestanden",
33
40
  "groups": {
34
41
  "quickfix": "Quick fix",
35
42
  "rewrite": "Herschrijven",
@@ -56,7 +63,12 @@
56
63
  "cancel": "Annuleer",
57
64
  "apply": "Hernoem",
58
65
  "suggestions": "Voorgestelde namen",
59
- "useSuggestion": "Suggestie gebruiken"
66
+ "useSuggestion": "Suggestie gebruiken",
67
+ "operations": {
68
+ "create": "Maakt aan",
69
+ "rename": "Verplaatst",
70
+ "delete": "Verwijdert"
71
+ }
60
72
  },
61
73
  "navigation": {
62
74
  "unavailable": "Geen language server die dat hier beantwoordt",
@@ -84,6 +96,33 @@
84
96
  "noDefinition": "Geen definitie gevonden",
85
97
  "unavailableDefinition": "Geen language server vindt definities in dit bestand"
86
98
  },
99
+ "callHierarchy": {
100
+ "unavailable": "Geen language server vindt aanroepen in dit bestand",
101
+ "none": "Hier is geen symbool met aanroepen gevonden",
102
+ "failed": "Aanroepen opzoeken mislukt: {{message}}",
103
+ "label": "Aanroephiërarchie",
104
+ "direction": "Richting",
105
+ "incoming": "Aanroepers",
106
+ "outgoing": "Aangeroepen",
107
+ "close": "Sluit",
108
+ "loading": "Laden…",
109
+ "noIncoming": "Geen aanroepers",
110
+ "noOutgoing": "Roept niets aan",
111
+ "retry": "Aanroepen laden mislukt: {{message}}. Druk om opnieuw te proberen",
112
+ "more_one": "Toon nog {{formatted}} aanroep ({{hidden}} over)",
113
+ "more_other": "Toon nog {{formatted}} aanroepen ({{hidden}} over)",
114
+ "sites_one": "{{formatted}} aanroep",
115
+ "sites_other": "{{formatted}} aanroepen",
116
+ "site": "Aanroep {{current}} van {{total}}",
117
+ "previousSite": "Vorige aanroep",
118
+ "nextSite": "Volgende aanroep",
119
+ "announce": {
120
+ "incoming_one": "{{count}} aanroeper van {{name}}",
121
+ "incoming_other": "{{count}} aanroepers van {{name}}",
122
+ "outgoing_one": "{{count}} aangeroepen door {{name}}",
123
+ "outgoing_other": "{{count}} aangeroepen door {{name}}"
124
+ }
125
+ },
87
126
  "codeVision": {
88
127
  "usagesNone": "Niet gebruikt",
89
128
  "usages_one": "{{formatted}} keer gebruikt",
@@ -126,20 +165,65 @@
126
165
  "copy": "Kopieer",
127
166
  "paste": "Plak"
128
167
  },
168
+ "snippet": {
169
+ "choices": "Snippetkeuze"
170
+ },
129
171
  "completion": {
172
+ "tooLarge": "Deze aanvulling is te groot om in te voegen",
130
173
  "insert": "Invoegen",
131
174
  "replace": "Vervangen",
132
175
  "details": "Details",
133
- "none": "Geen suggesties"
176
+ "none": "Geen suggesties",
177
+ "transformBusy": "Snippettransformaties zijn bezig; probeer het opnieuw",
178
+ "transformFailed": "Snippettransformatie kon niet worden toegepast"
134
179
  },
135
180
  "history": {
136
181
  "recent": "Recente plekken"
182
+ },
183
+ "announce": {
184
+ "problem": "{{severity}}: {{message}}",
185
+ "severity": {
186
+ "error": "Fout",
187
+ "warning": "Waarschuwing",
188
+ "info": "Informatie",
189
+ "hint": "Hint"
190
+ },
191
+ "field": "Veld {{current}} van {{total}}",
192
+ "fieldChoices_one": "Veld {{current}} van {{total}}, {{count}} keuze",
193
+ "fieldChoices_other": "Veld {{current}} van {{total}}, {{count}} keuzes"
194
+ },
195
+ "fold": {
196
+ "fold": "Regels {{start}} tot {{end}} invouwen",
197
+ "unfold": "Regels {{start}} tot {{end}} uitvouwen"
198
+ },
199
+ "docs": {
200
+ "label": "Weergegeven documentatie",
201
+ "labelNamed": "Weergegeven documentatie: {{- summary}}",
202
+ "render": "Toon weergegeven documentatie",
203
+ "showSource": "Toon bron van documentatie",
204
+ "tags": {
205
+ "deprecated": "Verouderd",
206
+ "template": "Typeparameters",
207
+ "param": "Parameters",
208
+ "property": "Eigenschappen",
209
+ "type": "Type",
210
+ "var": "Variabele",
211
+ "return": "Geeft terug",
212
+ "yield": "Levert op",
213
+ "throws": "Gooit",
214
+ "see": "Zie ook",
215
+ "example": "Voorbeeld",
216
+ "since": "Sinds",
217
+ "version": "Versie",
218
+ "author": "Auteur"
219
+ }
137
220
  }
138
221
  },
139
222
  "commands": {
140
223
  "goToDefinition": "Ga naar definitie",
141
224
  "goToTypeDefinition": "Ga naar typedefinitie",
142
225
  "goToImplementation": "Ga naar implementatie",
226
+ "callHierarchy": "Aanroephiërarchie",
143
227
  "peekReferences": "Bekijk referenties",
144
228
  "renameSymbol": "Symbool hernoemen",
145
229
  "codeActions": "Codeacties",
@@ -148,6 +232,7 @@
148
232
  "find": {
149
233
  "label": "Zoeken",
150
234
  "count": "{{current}} / {{total}}",
235
+ "countLimited": "{{current}} / {{total}}+",
151
236
  "none": "Geen resultaten",
152
237
  "invalid": "Ongeldige reguliere expressie",
153
238
  "caseSensitive": "Hoofdlettergevoelig",
@@ -166,7 +251,33 @@
166
251
  "allLabel": "Alles vervangen",
167
252
  "preserveCase": "Hoofdletters behouden",
168
253
  "toggle": "Vervangen tonen",
169
- "hide": "Vervangen verbergen"
254
+ "hide": "Vervangen verbergen",
255
+ "failed": {
256
+ "limit": "Te veel om ineens te vervangen ({{edits}} edits of {{characters}} uitvoertekens)",
257
+ "timeout": "Vervangen duurde te lang; de tekst is niet gewijzigd",
258
+ "unavailable": "Vervangen vereist de zoekworker",
259
+ "error": "Vervangen is niet gelukt; de tekst is niet gewijzigd"
260
+ }
261
+ },
262
+ "countMore": "{{total}}+ resultaten",
263
+ "searching": "Zoeken…",
264
+ "large": "Groot document: druk op Enter om te zoeken",
265
+ "replaced": "{{count}} resultaten vervangen",
266
+ "failed": {
267
+ "timeout": "Zoeken duurde te lang; druk op Enter om opnieuw te proberen",
268
+ "unavailable": "Zoeken met regex is niet beschikbaar",
269
+ "invalid": "Ongeldige reguliere expressie",
270
+ "error": "Zoeken kon niet worden voltooid"
170
271
  }
272
+ },
273
+ "diff": {
274
+ "hidden": "Toon {{count}} verborgen regels",
275
+ "contextLimit": "Limiet voor context bereikt",
276
+ "oldSide": "Voor de wijziging",
277
+ "newSide": "Na de wijziging",
278
+ "noNewline": "Geen afsluitende nieuwe regel",
279
+ "added": "Toegevoegd, nieuwe regel {{number}}",
280
+ "removed": "Verwijderd, oude regel {{number}}",
281
+ "unchanged": "Oude regel {{old}}, nieuwe regel {{new}}"
171
282
  }
172
283
  }
package/src/models.ts CHANGED
@@ -19,6 +19,7 @@ export {
19
19
  CLASS_KIND,
20
20
  SNIPPET_FORMAT,
21
21
  type Insertion,
22
+ type SnippetInsertionOptions,
22
23
  insertionOf,
23
24
  mirroredInsertions,
24
25
  documentationText,
@@ -84,6 +85,20 @@ export {
84
85
  distinguishingFolders,
85
86
  visualColumnOf
86
87
  } from './peek-model.ts';
88
+ export {
89
+ type CallDirection,
90
+ type CallLoad,
91
+ type CallNode,
92
+ type CallRow,
93
+ CALL_LEVEL_LIMIT,
94
+ rootsOf,
95
+ childrenOf,
96
+ rowsOf,
97
+ stepsOf,
98
+ targetOf,
99
+ statusIdOf,
100
+ moreIdOf
101
+ } from './call-hierarchy-model.ts';
87
102
  export {
88
103
  type SymbolGroup,
89
104
  SYMBOL_GROUPS,
@@ -104,6 +119,7 @@ export {
104
119
  } from './symbol-picker-model.ts';
105
120
  export { SYMBOL_LINK_CLASS, isTypeName, linkTypeNames, declaredNameOf, placesOfName } from './symbol-links.ts';
106
121
  export { scopesOf, decodeSemanticTokens } from './semantic-model.ts';
122
+ export { applySemanticEdits } from './semantic-delta.ts';
107
123
  export {
108
124
  type ActionGroup,
109
125
  ACTION_GROUPS,
@@ -118,9 +134,21 @@ export {
118
134
  type EditPreview,
119
135
  previewOf
120
136
  } from './code-actions-model.ts';
137
+ export { resourceOperationsOf, type ResourceOperationView } from './workspace-edit.ts';
121
138
  export { MAX_DECLARATIONS, type CodeVisionDeclaration, type DeclarationSource, startAfterComments, declarationsOf, usagesText } from './code-vision-model.ts';
122
139
  export { UNCOMMITTED, mapBlame, type CodeAuthor, type CodeAuthorship, shortName, authorshipOf, authorsText } from './code-authors.ts';
123
- export { type SnippetStop, type ParsedSnippet, parseSnippet, tabOrder } from './snippet.ts';
140
+ export {
141
+ type SnippetTransform,
142
+ type SnippetStop,
143
+ type SnippetRange,
144
+ type SnippetField,
145
+ type SnippetVariableResolver,
146
+ type SnippetTemplate,
147
+ compileSnippet,
148
+ type ParsedSnippet,
149
+ parseSnippet,
150
+ tabOrder
151
+ } from './snippet.ts';
124
152
  export {
125
153
  type LineSpan,
126
154
  lineSpanOf,
@@ -155,3 +183,14 @@ export {
155
183
  } from './find-query.ts';
156
184
  export { type PopupSize, type PopupPlacement, type PlaceOptions, placePopup, placeBeside } from './popup-placement.ts';
157
185
  export { planConflict, type ConflictStretch, type ConflictPlan } from './conflict-model.ts';
186
+ export {
187
+ patchLayoutOf,
188
+ DEFAULT_PATCH_LIMITS,
189
+ type PatchLayout,
190
+ type PatchLayoutLine,
191
+ type PatchLineRole,
192
+ type PatchFileLayout,
193
+ type PatchFileStatus,
194
+ type PatchHunkLayout,
195
+ type PatchLimits
196
+ } from './patch-layout.ts';
@@ -0,0 +1,224 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import type { Editor, EditorChangeMark, EditorLineHighlight } from '@adecore/editor';
4
+ import { patchLayoutOf, type PatchLayout, type PatchLayoutLine, type PatchLimits } from './patch-layout.ts';
5
+
6
+ /* The sentences a screen reader reads with a line of the patch, in the host's words. */
7
+ export interface PatchLineLabels {
8
+ added(number: number): string;
9
+ removed(number: number): string;
10
+ unchanged(old: number, fresh: number): string;
11
+ }
12
+
13
+ export interface PatchDecorationOptions {
14
+ /* One number per line, the old one for a removed line and the new one otherwise, or both side by side. Defaults to `single`. */
15
+ numbers?: 'single' | 'both';
16
+ labels?: PatchLineLabels;
17
+ limits?: PatchLimits;
18
+ /* Called with the layout after the first read and after `refresh()`, and once the edits have rested for `delay` ms, so a file list is not rebuilt per keystroke. */
19
+ onLayout?(layout: PatchLayout): void;
20
+ /* Milliseconds of rest before `onLayout` follows edits, and before a patch of `immediateBelow` characters or more is decorated again. Defaults to 150. */
21
+ delay?: number;
22
+ /* Up to this many characters (of the last read) the decorations follow an edit on the same microtask. Defaults to 100000. */
23
+ immediateBelow?: number;
24
+ /* Replaces `setTimeout` and `clearTimeout`, for a test that drives the clock. */
25
+ timer?: PatchTimer;
26
+ }
27
+
28
+ export interface PatchTimer {
29
+ set(callback: () => void, delay: number): unknown;
30
+ clear(handle: unknown): void;
31
+ }
32
+
33
+ const DEFAULT_TIMER: PatchTimer = {
34
+ set: (callback, delay) => setTimeout(callback, delay),
35
+ clear: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>)
36
+ };
37
+
38
+ export interface PatchDecoration {
39
+ readonly layout: PatchLayout;
40
+ /* Reads the text again, such as after the options changed. */
41
+ refresh(): void;
42
+ /* Takes the decorations off the editor and stops following it. */
43
+ dispose(): void;
44
+ }
45
+
46
+ const CHANGE_LAYER = 'patch';
47
+
48
+ function numberOf(line: PatchLayoutLine, numbers: 'single' | 'both', width: number): string | null {
49
+ if (line.old === null && line.new === null) {
50
+ return null;
51
+ }
52
+ if (numbers === 'single') {
53
+ return String(line.new ?? line.old);
54
+ }
55
+ const pad = (value: number | null): string => (value === null ? '' : String(value)).padStart(width, ' ');
56
+ return `${pad(line.old)} ${pad(line.new)}`;
57
+ }
58
+
59
+ /* Runs of one role become one highlight, so the bar of consecutive changed lines is one stripe. */
60
+ function highlightsOf(layout: PatchLayout): { highlights: EditorLineHighlight[]; marks: EditorChangeMark[] } {
61
+ const highlights: EditorLineHighlight[] = [];
62
+ const marks: EditorChangeMark[] = [];
63
+ const lines = layout.lines;
64
+ for (let index = 0; index < lines.length; index++) {
65
+ const role = lines[index]!.role;
66
+ if (role === 'file-header') {
67
+ highlights.push({ startLine: index + 1, endLine: index + 1, color: '--text-muted', fill: '--surface-raised' });
68
+ } else if (role === 'hunk-header') {
69
+ highlights.push({ startLine: index + 1, endLine: index + 1, color: '--accent' });
70
+ } else if (role === 'added' || role === 'removed') {
71
+ let end = index;
72
+ while (end + 1 < lines.length && lines[end + 1]!.role === role) {
73
+ end++;
74
+ }
75
+ const removed = role === 'removed';
76
+ highlights.push({
77
+ startLine: index + 1,
78
+ endLine: end + 1,
79
+ color: removed ? '--editor-deleted' : '--editor-added',
80
+ bar: removed ? 'hatched' : 'solid'
81
+ });
82
+ marks.push({ startLine: index + 1, endLine: end + 1, kind: removed ? 'deleted' : 'added' });
83
+ index = end;
84
+ }
85
+ }
86
+ return { highlights, marks };
87
+ }
88
+
89
+ /*
90
+ * Draws a patch in the editor the way the diff views draw a change: tinted lines, numbers from the hunk
91
+ * headers in the gutter and styled file and hunk headers. The editor stays editable. A small patch is read
92
+ * again on the microtask after an edit, so its decorations are right within a frame; a large one waits for
93
+ * the edits to rest, and `onLayout` always does. Typing costs one pass over the text (bounded by the limits).
94
+ */
95
+ export function decoratePatch(editor: Editor, options: PatchDecorationOptions = {}): PatchDecoration {
96
+ let layout = patchLayoutOf('', options.limits);
97
+ let pending = false;
98
+ let disposed = false;
99
+ let length = 0;
100
+ let wait: unknown = null;
101
+ const timer = options.timer ?? DEFAULT_TIMER;
102
+ const delay = options.delay ?? 150;
103
+ const immediateBelow = options.immediateBelow ?? 100_000;
104
+ const clearWait = (): void => {
105
+ if (wait !== null) {
106
+ timer.clear(wait);
107
+ wait = null;
108
+ }
109
+ };
110
+ const after = (callback: () => void): void => {
111
+ clearWait();
112
+ wait = timer.set(() => {
113
+ wait = null;
114
+ callback();
115
+ }, delay);
116
+ };
117
+ const apply = (): void => {
118
+ const numbers = options.numbers ?? 'single';
119
+ const lines = layout.lines;
120
+ const width = lines.reduce((digits, line) => Math.max(digits, String(Math.max(line.old ?? 0, line.new ?? 0)).length), 1);
121
+ editor.setLineNumbers((line) => {
122
+ const entry = lines[line];
123
+ return entry === undefined ? null : numberOf(entry, numbers, width);
124
+ });
125
+ const labels = options.labels;
126
+ editor.setLineLabels(
127
+ labels === undefined
128
+ ? null
129
+ : (line) => {
130
+ const entry = lines[line];
131
+ if (entry?.role === 'removed') {
132
+ return labels.removed(entry.old!);
133
+ }
134
+ if (entry?.role === 'added') {
135
+ return labels.added(entry.new!);
136
+ }
137
+ return entry?.role === 'context' ? labels.unchanged(entry.old!, entry.new!) : null;
138
+ }
139
+ );
140
+ const { highlights, marks } = highlightsOf(layout);
141
+ editor.setLineHighlights(highlights);
142
+ editor.setChangeMarks(marks, CHANGE_LAYER);
143
+ };
144
+ const decorate = (): void => {
145
+ pending = false;
146
+ if (disposed) {
147
+ return;
148
+ }
149
+ const text = editor.getText();
150
+ length = text.length;
151
+ layout = patchLayoutOf(text, options.limits);
152
+ apply();
153
+ };
154
+ const announce = (): void => {
155
+ if (!disposed) {
156
+ options.onLayout?.(layout);
157
+ }
158
+ };
159
+ const read = (): void => {
160
+ clearWait();
161
+ decorate();
162
+ announce();
163
+ };
164
+ const schedule = (): void => {
165
+ if (length < immediateBelow) {
166
+ if (pending) {
167
+ return;
168
+ }
169
+ pending = true;
170
+ queueMicrotask(() => {
171
+ decorate();
172
+ after(announce);
173
+ });
174
+ return;
175
+ }
176
+ pending = true;
177
+ after(() => {
178
+ decorate();
179
+ announce();
180
+ });
181
+ };
182
+ const stop = editor.onTextChange(schedule);
183
+ read();
184
+ return {
185
+ get layout() {
186
+ return layout;
187
+ },
188
+ refresh: read,
189
+ dispose() {
190
+ disposed = true;
191
+ clearWait();
192
+ stop();
193
+ editor.setLineNumbers(null);
194
+ editor.setLineLabels(null);
195
+ editor.setLineHighlights([]);
196
+ editor.setChangeMarks([], CHANGE_LAYER);
197
+ }
198
+ };
199
+ }
200
+
201
+ /* Decorates a mounted editor as a patch for as long as it stands, in the editor namespace's words, and returns the layout for a file tree. */
202
+ export function usePatchDecorations(editor: Editor | null, options: Omit<PatchDecorationOptions, 'labels' | 'onLayout'> = {}): PatchLayout | null {
203
+ const { t } = useTranslation('editor');
204
+ const [state, setState] = useState<{ editor: Editor; layout: PatchLayout } | null>(null);
205
+ const { numbers } = options;
206
+ const { maxLines, maxCharacters } = options.limits ?? {};
207
+ useEffect(() => {
208
+ if (editor === null) {
209
+ return;
210
+ }
211
+ const decoration = decoratePatch(editor, {
212
+ ...(numbers === undefined ? {} : { numbers }),
213
+ limits: { ...(maxLines === undefined ? {} : { maxLines }), ...(maxCharacters === undefined ? {} : { maxCharacters }) },
214
+ labels: {
215
+ added: (number) => t('diff.added', { number }),
216
+ removed: (number) => t('diff.removed', { number }),
217
+ unchanged: (old, fresh) => t('diff.unchanged', { old, new: fresh })
218
+ },
219
+ onLayout: (layout) => setState({ editor, layout })
220
+ });
221
+ return () => decoration.dispose();
222
+ }, [editor, numbers, maxLines, maxCharacters, t]);
223
+ return state !== null && state.editor === editor ? state.layout : null;
224
+ }