@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
@@ -0,0 +1,197 @@
1
+ import { diffLines } from '@adecore/merge';
2
+ import { replacedWords } from './word-diff.js';
3
+ export function lineSpanOf(range) {
4
+ const endsOnBreak = range.end.character === 0 && range.end.line > range.start.line;
5
+ return { startLine: range.start.line + 1, endLine: endsOnBreak ? range.end.line : range.end.line + 1 };
6
+ }
7
+ export function isEmptyRange(range) {
8
+ return range.start.line === range.end.line && range.start.character === range.end.character;
9
+ }
10
+ /* The selection, or the whole line under the caret while nothing is selected. */
11
+ export function inlineRangeOf(selection, lineLength) {
12
+ if (!isEmptyRange(selection)) {
13
+ return selection;
14
+ }
15
+ const { line } = selection.start;
16
+ return { start: { line, character: 0 }, end: { line, character: lineLength(line) } };
17
+ }
18
+ export function problemsOnLines(diagnostics, span, describe) {
19
+ return diagnostics
20
+ .filter((diagnostic) => diagnostic.range.start.line + 1 <= span.endLine && diagnostic.range.end.line + 1 >= span.startLine)
21
+ .map((diagnostic) => ({ line: Math.max(diagnostic.range.start.line + 1, span.startLine), ...describe(diagnostic) }))
22
+ .sort((left, right) => left.line - right.line);
23
+ }
24
+ const OPENING = /^( {0,3})(`{3,}|~{3,})(.*)$/;
25
+ function openingOf(line) {
26
+ const match = OPENING.exec(line);
27
+ if (match === null) {
28
+ return null;
29
+ }
30
+ const [, indent = '', marks = '', info = ''] = match;
31
+ // The info string of a backtick fence holds no backtick, which is what keeps a line of inline code from opening one.
32
+ if (marks.startsWith('`') && info.includes('`')) {
33
+ return null;
34
+ }
35
+ return { char: marks[0], length: marks.length, indent: indent.length, info: info.trim() };
36
+ }
37
+ function closes(line, start) {
38
+ const match = /^ {0,3}(`+|~+)\s*$/.exec(line);
39
+ return match !== null && match[1][0] === start.char && match[1].length >= start.length;
40
+ }
41
+ function isReplacement(info) {
42
+ return info
43
+ .toLowerCase()
44
+ .split(/[\s,:=]+/)
45
+ .includes('replacement');
46
+ }
47
+ /*
48
+ * Reads the answer the way a markdown reader does: a fence closes at a line of at least as many marks,
49
+ * so a fence inside the code stays code. A block that never closes is a turn that was cut off, and
50
+ * applying half of it would be worse than showing the answer as text, so it counts as no block.
51
+ */
52
+ export function parseAnswer(text) {
53
+ const lines = text.replace(/\r\n/g, '\n').split('\n');
54
+ let open = null;
55
+ let found = null;
56
+ for (const [index, line] of lines.entries()) {
57
+ if (open === null) {
58
+ const start = openingOf(line);
59
+ if (start !== null) {
60
+ open = { start, from: index, body: [] };
61
+ }
62
+ continue;
63
+ }
64
+ if (closes(line, open.start)) {
65
+ if (isReplacement(open.start.info)) {
66
+ found = { from: open.from, to: index, body: open.body };
67
+ }
68
+ open = null;
69
+ continue;
70
+ }
71
+ open.body.push(line.replace(new RegExp(`^ {0,${open.start.indent}}`), ''));
72
+ }
73
+ if (found === null) {
74
+ return { replacement: null, rest: text.trim() };
75
+ }
76
+ return {
77
+ replacement: found.body.join('\n'),
78
+ rest: [...lines.slice(0, found.from), ...lines.slice(found.to + 1)]
79
+ .join('\n')
80
+ .replace(/\n{3,}/g, '\n\n')
81
+ .trim()
82
+ };
83
+ }
84
+ /* The proposal in the shape of the text it replaces: its line breaks and whether it ends on one. */
85
+ export function fitReplacement(selected, replacement) {
86
+ const breaks = selected.includes('\r\n') ? '\r\n' : '\n';
87
+ let fitted = replacement.replace(/\r\n/g, '\n');
88
+ const endsOnBreak = selected.endsWith('\n');
89
+ if (fitted !== '' && endsOnBreak && !fitted.endsWith('\n')) {
90
+ fitted += '\n';
91
+ }
92
+ else if (!endsOnBreak && fitted.endsWith('\n')) {
93
+ fitted = fitted.slice(0, -1);
94
+ }
95
+ return breaks === '\n' ? fitted : fitted.replace(/\n/g, breaks);
96
+ }
97
+ /* Where the caret stands after `text` was typed at `start`. */
98
+ export function endOfInsertion(start, text) {
99
+ const lines = text.split('\n');
100
+ if (lines.length === 1) {
101
+ return { line: start.line, character: start.character + text.length };
102
+ }
103
+ return { line: start.line + lines.length - 1, character: lines.at(-1).length };
104
+ }
105
+ function linesOf(text) {
106
+ const lines = text.replace(/\r\n/g, '\n').split('\n');
107
+ return lines.length > 1 && lines.at(-1) === '' ? lines.slice(0, -1) : lines;
108
+ }
109
+ /*
110
+ * The proposal against the selection, line by line. Lines the proposal changes show as added, since
111
+ * the old ones are the selected text above the card; lines it drops without a replacement are the
112
+ * only removed ones, because nothing in the proposal would show them gone.
113
+ */
114
+ export function diffSegments(selected, proposal, startLine) {
115
+ const base = linesOf(selected);
116
+ const other = linesOf(proposal);
117
+ const segments = [];
118
+ let cursor = 0;
119
+ const same = (until) => {
120
+ if (until > cursor) {
121
+ segments.push({ kind: 'same', text: other.slice(cursor, until).join('\n'), firstLine: startLine + cursor });
122
+ }
123
+ };
124
+ for (const change of diffLines(base, other)) {
125
+ same(change.otherStart);
126
+ if (change.otherEnd > change.otherStart) {
127
+ segments.push({
128
+ kind: 'added',
129
+ text: other.slice(change.otherStart, change.otherEnd).join('\n'),
130
+ firstLine: startLine + change.otherStart,
131
+ ...(change.baseEnd > change.baseStart ? { replaces: base.slice(change.baseStart, change.baseEnd).join('\n') } : {})
132
+ });
133
+ }
134
+ else {
135
+ segments.push({ kind: 'removed', text: base.slice(change.baseStart, change.baseEnd).join('\n') });
136
+ }
137
+ cursor = change.otherEnd;
138
+ }
139
+ same(other.length);
140
+ return segments;
141
+ }
142
+ /* Per line of an added stretch, the character ranges of the words that differ from the line it replaces, for the stronger tint; undefined when none do. */
143
+ export function emphasisOf(segment) {
144
+ if (segment.kind !== 'added' || segment.replaces === undefined) {
145
+ return undefined;
146
+ }
147
+ const ranges = replacedWords(linesOf(segment.text), linesOf(segment.replaces));
148
+ return ranges.some((line) => line.length > 0) ? ranges : undefined;
149
+ }
150
+ /* The offset of a position in a text whose lines end in `\n` or `\r\n`; null when the text is not that long. */
151
+ export function offsetOf(text, position) {
152
+ let offset = 0;
153
+ for (let line = 0; line < position.line; line++) {
154
+ const end = text.indexOf('\n', offset);
155
+ if (end === -1) {
156
+ return null;
157
+ }
158
+ offset = end + 1;
159
+ }
160
+ const lineEnd = text.indexOf('\n', offset);
161
+ const length = (lineEnd === -1 ? text.length : lineEnd) - offset;
162
+ return position.character > length ? null : offset + position.character;
163
+ }
164
+ /* The text in a range of `text`, or null when the range is not in it. */
165
+ export function textInRange(text, range) {
166
+ const from = offsetOf(text, range.start);
167
+ const to = offsetOf(text, range.end);
168
+ return from === null || to === null || to < from ? null : text.slice(from, to);
169
+ }
170
+ /* `text` with the range replaced; null when the range is not in it. */
171
+ export function replaceRange(text, range, replacement) {
172
+ const from = offsetOf(text, range.start);
173
+ const to = offsetOf(text, range.end);
174
+ return from === null || to === null || to < from ? null : `${text.slice(0, from)}${replacement}${text.slice(to)}`;
175
+ }
176
+ /*
177
+ * Where `selected` stands in `text` now: its own range while it still holds, and otherwise the one
178
+ * place it occurs when lines moved. Null when it is gone or ambiguous, which is a selection that changed.
179
+ */
180
+ export function locateSelection(text, range, selected) {
181
+ if (textInRange(text, range) === selected) {
182
+ return range;
183
+ }
184
+ if (selected === '') {
185
+ return null;
186
+ }
187
+ const first = text.indexOf(selected);
188
+ if (first === -1 || text.indexOf(selected, first + 1) !== -1) {
189
+ return null;
190
+ }
191
+ return { start: positionAt(text, first), end: positionAt(text, first + selected.length) };
192
+ }
193
+ function positionAt(text, offset) {
194
+ const before = text.slice(0, offset);
195
+ const line = before.split('\n').length - 1;
196
+ return { line, character: offset - (before.lastIndexOf('\n') + 1) };
197
+ }
@@ -0,0 +1,10 @@
1
+ import type { CompletionItem } from '@adecore/lsp';
2
+ export declare class RecentChoices {
3
+ private readonly order;
4
+ private tick;
5
+ static keyOf(item: CompletionItem): string;
6
+ record(item: CompletionItem): void;
7
+ recency(item: CompletionItem): number;
8
+ }
9
+ export declare function recentChoicesOf(languageId: string): RecentChoices;
10
+ export declare function forgetRecentChoices(): void;
@@ -0,0 +1,39 @@
1
+ import { qualifierOf } from './completion-model.js';
2
+ const LIMIT = 200;
3
+ /*
4
+ * The suggestions a person took lately, so one they use again comes first among equal matches. It lives in
5
+ * memory only, one per language, and forgets the oldest choice past a few hundred.
6
+ */
7
+ export class RecentChoices {
8
+ order = new Map();
9
+ tick = 0;
10
+ /* An item is told by its label, its kind and where it comes from, so two `Property` of two namespaces are two choices. */
11
+ static keyOf(item) {
12
+ return `${item.label}\0${item.kind ?? ''}\0${qualifierOf(item)}`;
13
+ }
14
+ record(item) {
15
+ const key = RecentChoices.keyOf(item);
16
+ this.order.delete(key);
17
+ this.order.set(key, ++this.tick);
18
+ if (this.order.size > LIMIT) {
19
+ this.order.delete(this.order.keys().next().value);
20
+ }
21
+ }
22
+ /* Higher for a later choice, 0 for an item never chosen. */
23
+ recency(item) {
24
+ return this.order.get(RecentChoices.keyOf(item)) ?? 0;
25
+ }
26
+ }
27
+ const stores = new Map();
28
+ export function recentChoicesOf(languageId) {
29
+ let store = stores.get(languageId);
30
+ if (store === undefined) {
31
+ store = new RecentChoices();
32
+ stores.set(languageId, store);
33
+ }
34
+ return store;
35
+ }
36
+ /* For a test that must not hear the choices an earlier one made. */
37
+ export function forgetRecentChoices() {
38
+ stores.clear();
39
+ }
@@ -0,0 +1,13 @@
1
+ import { type Timers } from './timers.ts';
2
+ export declare class Refresher {
3
+ private timer;
4
+ private controller;
5
+ private disposed;
6
+ private readonly timers;
7
+ private readonly delay;
8
+ private readonly run;
9
+ constructor(run: (signal: AbortSignal) => Promise<void>, delay: number, timers?: Timers);
10
+ later(): void;
11
+ now(): void;
12
+ dispose(): void;
13
+ }
@@ -0,0 +1,50 @@
1
+ import { StaleResultError } from '@adecore/lsp';
2
+ import { realTimers } from './timers.js';
3
+ /*
4
+ * Asks again for something a document's text decides, such as its colors or its hints: after a pause
5
+ * in typing, when a server says what it offers changed, and never twice at once. A newer ask aborts the
6
+ * one before it, and an answer for a text that has moved on is dropped, since another ask is on its way.
7
+ */
8
+ export class Refresher {
9
+ timer;
10
+ controller = null;
11
+ disposed = false;
12
+ timers;
13
+ delay;
14
+ run;
15
+ constructor(run, delay, timers = realTimers) {
16
+ this.run = run;
17
+ this.delay = delay;
18
+ this.timers = timers;
19
+ }
20
+ /* After a pause: every call starts the wait again. */
21
+ later() {
22
+ this.timers.clear(this.timer);
23
+ this.timer = this.timers.set(() => this.now(), this.delay);
24
+ }
25
+ now() {
26
+ if (this.disposed) {
27
+ return;
28
+ }
29
+ this.timers.clear(this.timer);
30
+ this.controller?.abort();
31
+ const controller = new AbortController();
32
+ this.controller = controller;
33
+ this.run(controller.signal).catch((error) => {
34
+ // A server that cannot answer yet is asked again when it says it can; a stale text is asked again by its own change.
35
+ if (!(error instanceof StaleResultError) && !controller.signal.aborted && !isQuiet(error)) {
36
+ console.warn('Language request failed', error);
37
+ }
38
+ });
39
+ }
40
+ dispose() {
41
+ this.disposed = true;
42
+ this.timers.clear(this.timer);
43
+ this.controller?.abort();
44
+ }
45
+ }
46
+ /* The failures of a server that is not up, does not offer the feature or was asked to stop, which are not worth a line in the console. */
47
+ function isQuiet(error) {
48
+ const code = error?.code;
49
+ return code === -32002 || code === -32601 || code === -32800 || code === -32801;
50
+ }
@@ -0,0 +1 @@
1
+ export declare function relativeTime(at: number, now: number): string;
@@ -0,0 +1,9 @@
1
+ import { formatAgo, formatDay, formatDayWithYear } from '@adecore/ui/format';
2
+ export function relativeTime(at, now) {
3
+ const seconds = Math.max(0, now - at);
4
+ if (seconds < 7 * 24 * 60 * 60) {
5
+ return formatAgo(seconds * 1000);
6
+ }
7
+ const date = new Date(at * 1000);
8
+ return date.getFullYear() === new Date(now * 1000).getFullYear() ? formatDay(date) : formatDayWithYear(date);
9
+ }
@@ -0,0 +1,25 @@
1
+ import type { EditorPosition, EditorRange } from '@adecore/editor';
2
+ import type { Location, PrepareRenameResult, TextEdit, WorkspaceEdit } from '@adecore/lsp';
3
+ export declare function wordRangeAt(lineText: string, position: EditorPosition): EditorRange | null;
4
+ export interface RenameTarget {
5
+ readonly range: EditorRange;
6
+ readonly placeholder: string;
7
+ }
8
+ export declare function renameTargetOf(result: PrepareRenameResult, textOf: (range: EditorRange) => string, wordAtCaret: () => EditorRange | null): RenameTarget | null;
9
+ export interface Occurrences {
10
+ readonly inFile: readonly EditorRange[];
11
+ readonly count: number;
12
+ readonly files: number;
13
+ }
14
+ export declare function occurrencesOf(locations: readonly Location[], uri: string): Occurrences;
15
+ export interface RenameRow {
16
+ readonly line: number;
17
+ readonly before: string;
18
+ readonly after: string;
19
+ }
20
+ export interface RenameFile {
21
+ readonly uri: string;
22
+ readonly rows: readonly RenameRow[];
23
+ }
24
+ export declare function renameRowsOf(text: string, edits: readonly TextEdit[]): RenameRow[];
25
+ export declare function renamePreviewOf(edit: WorkspaceEdit, textOf: (uri: string) => Promise<string | null>): Promise<RenameFile[] | null>;
@@ -0,0 +1,93 @@
1
+ import { isIdentifierCharacter } from './completion-model.js';
2
+ import { entriesOf } from './workspace-edit.js';
3
+ /* The identifier the caret is in or touches; null where there is none. */
4
+ export function wordRangeAt(lineText, position) {
5
+ let start = Math.min(position.character, lineText.length);
6
+ let end = start;
7
+ while (start > 0 && isIdentifierCharacter(lineText[start - 1])) {
8
+ start--;
9
+ }
10
+ while (end < lineText.length && isIdentifierCharacter(lineText[end])) {
11
+ end++;
12
+ }
13
+ return start === end ? null : { start: { line: position.line, character: start }, end: { line: position.line, character: end } };
14
+ }
15
+ /*
16
+ * The target of a prepare answer. A server that answers with a bare range or the default behavior leaves
17
+ * the name to the text, and one that answers null says nothing here can be renamed.
18
+ */
19
+ export function renameTargetOf(result, textOf, wordAtCaret) {
20
+ if (result === null) {
21
+ return null;
22
+ }
23
+ if ('placeholder' in result) {
24
+ return { range: result.range, placeholder: result.placeholder };
25
+ }
26
+ if ('start' in result) {
27
+ return { range: result, placeholder: textOf(result) };
28
+ }
29
+ const word = wordAtCaret();
30
+ return word === null ? null : { range: word, placeholder: textOf(word) };
31
+ }
32
+ export function occurrencesOf(locations, uri) {
33
+ return {
34
+ inFile: locations.filter((location) => location.uri === uri).map((location) => location.range),
35
+ count: locations.length,
36
+ files: new Set(locations.map((location) => location.uri)).size
37
+ };
38
+ }
39
+ function lineStarts(text) {
40
+ const starts = [0];
41
+ for (let index = 0; index < text.length; index++) {
42
+ const code = text.charCodeAt(index);
43
+ if (code === 13 && text.charCodeAt(index + 1) === 10) {
44
+ index++;
45
+ starts.push(index + 1);
46
+ }
47
+ else if (code === 10 || code === 13) {
48
+ starts.push(index + 1);
49
+ }
50
+ }
51
+ return starts;
52
+ }
53
+ /* The lines of a file an edit touches, each once, as they read before and after. An edit across lines is shown from its first line. */
54
+ export function renameRowsOf(text, edits) {
55
+ const starts = lineStarts(text);
56
+ const lineAt = (line) => {
57
+ const from = starts[line] ?? text.length;
58
+ let to = (starts[line + 1] ?? text.length + 1) - 1;
59
+ while (to > from && (text[to - 1] === '\n' || text[to - 1] === '\r')) {
60
+ to--;
61
+ }
62
+ return { from, to: Math.min(to, text.length) };
63
+ };
64
+ const byLine = new Map();
65
+ for (const edit of edits) {
66
+ byLine.set(edit.range.start.line, [...(byLine.get(edit.range.start.line) ?? []), edit]);
67
+ }
68
+ return [...byLine]
69
+ .sort((left, right) => left[0] - right[0])
70
+ .map(([line, own]) => {
71
+ const { from, to } = lineAt(line);
72
+ const before = text.slice(from, to);
73
+ let after = before;
74
+ for (const edit of [...own].sort((left, right) => right.range.start.character - left.range.start.character)) {
75
+ const end = edit.range.end.line === line ? edit.range.end.character : before.length;
76
+ after = after.slice(0, edit.range.start.character) + edit.newText + after.slice(end);
77
+ }
78
+ return { line: line + 1, before, after };
79
+ });
80
+ }
81
+ /* Resource renames are omitted; create/delete operations refuse the text-only preview with null. */
82
+ export async function renamePreviewOf(edit, textOf) {
83
+ const entries = entriesOf(edit);
84
+ if (entries === null) {
85
+ return null;
86
+ }
87
+ const files = [];
88
+ for (const [uri, list] of entries) {
89
+ const text = await textOf(uri);
90
+ files.push({ uri, rows: text === null ? [] : renameRowsOf(text, list.flat()) });
91
+ }
92
+ return files;
93
+ }
@@ -0,0 +1,21 @@
1
+ import type { EditorLanguage } from './editor-language.ts';
2
+ export declare class RenameFeature {
3
+ private tell;
4
+ private readonly language;
5
+ private session;
6
+ constructor(language: EditorLanguage);
7
+ get isOpen(): boolean;
8
+ start(): Promise<void>;
9
+ submit(name: string, preview: boolean): Promise<void>;
10
+ confirm(): Promise<void>;
11
+ cancel(): void;
12
+ private apply;
13
+ private fail;
14
+ edited(): void;
15
+ private finish;
16
+ private light;
17
+ private suggest;
18
+ private linesOf;
19
+ private publish;
20
+ private say;
21
+ }