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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +55 -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 +15 -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 +4 -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 +29 -0
  44. package/dist/code-actions.js +329 -0
  45. package/dist/code-authors.d.ts +17 -0
  46. package/dist/code-authors.js +80 -0
  47. package/dist/code-vision-model.d.ts +20 -0
  48. package/dist/code-vision-model.js +184 -0
  49. package/dist/code-vision.d.ts +48 -0
  50. package/dist/code-vision.js +234 -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 +464 -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 +64 -0
  68. package/dist/editor-language.js +148 -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 +97 -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 +47 -0
  80. package/dist/holder.d.ts +5 -0
  81. package/dist/holder.js +19 -0
  82. package/dist/host-types.d.ts +43 -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 +261 -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 +73 -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 +22 -0
  103. package/dist/navigation.js +156 -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 +29 -0
  109. package/dist/peek.js +216 -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 +34 -0
  117. package/dist/project-language.js +187 -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 +20 -0
  131. package/dist/rename.js +258 -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 +40 -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 +77 -0
  162. package/dist/symbols.d.ts +11 -0
  163. package/dist/symbols.js +97 -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 +45 -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 +61 -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 +387 -0
  198. package/src/code-authors.ts +104 -0
  199. package/src/code-vision-model.ts +230 -0
  200. package/src/code-vision.ts +267 -0
  201. package/src/completion-call.ts +139 -0
  202. package/src/completion-model.ts +440 -0
  203. package/src/completion.ts +501 -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 +163 -0
  210. package/src/editor-react.css +43 -0
  211. package/src/find-query.ts +74 -0
  212. package/src/folding.ts +112 -0
  213. package/src/highlight-layers.ts +37 -0
  214. package/src/highlights.ts +123 -0
  215. package/src/history.ts +57 -0
  216. package/src/holder.ts +19 -0
  217. package/src/host-types.ts +49 -0
  218. package/src/hover-content.ts +264 -0
  219. package/src/hover.ts +281 -0
  220. package/src/index.ts +63 -0
  221. package/src/inlay-hints.ts +87 -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 +169 -0
  229. package/src/paths.ts +3 -0
  230. package/src/peek-model.ts +147 -0
  231. package/src/peek.ts +248 -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 +220 -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 +291 -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 +48 -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 +86 -0
  258. package/src/symbols.ts +112 -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,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,20 @@
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
+ }
package/dist/rename.js ADDED
@@ -0,0 +1,258 @@
1
+ import i18next from 'i18next';
2
+ import { StaleResultError } from '@adecore/lsp';
3
+ import { occurrencesOf, renamePreviewOf, renameTargetOf, wordRangeAt } from './rename-model.js';
4
+ import { isShortcut } from './shortcut-keys.js';
5
+ const METHOD = 'textDocument/rename';
6
+ const TOAST_ID = 'language-rename';
7
+ /* How many names are offered, and how many other lines and files are read to say how the symbol is used. */
8
+ const SAMPLE_LOCATIONS = 8;
9
+ const SAMPLE_OTHER_FILES = 3;
10
+ const CONTEXT_LINES = 3;
11
+ function say(key, options) {
12
+ return i18next.t(`editor:language.rename.${key}`, options);
13
+ }
14
+ /*
15
+ * Renaming a symbol. The input sits on the symbol itself, every place the name has lights up while the
16
+ * new one is typed, Enter applies the server's edit at once and Shift+Enter lists what it would change first.
17
+ * Edits to other files land as unsaved drafts (`applyWorkspaceEdit`), so nothing is written to disk here.
18
+ */
19
+ export class RenameFeature {
20
+ tell(kind, title) {
21
+ this.language.project.host.notify?.({ id: TOAST_ID, kind, title });
22
+ }
23
+ language;
24
+ session = null;
25
+ constructor(language) {
26
+ this.language = language;
27
+ const { editor } = language;
28
+ const offs = [
29
+ editor.onKeyDown((event) => {
30
+ if (!isShortcut(this.language.shortcuts.rename, event)) {
31
+ return false;
32
+ }
33
+ void this.start();
34
+ return true;
35
+ }),
36
+ editor.onTextChange(() => this.cancel())
37
+ ];
38
+ language.onDispose(() => {
39
+ for (const off of offs) {
40
+ off();
41
+ }
42
+ this.cancel();
43
+ });
44
+ }
45
+ get isOpen() {
46
+ return this.session !== null;
47
+ }
48
+ async start() {
49
+ const { editor, project, uri } = this.language;
50
+ if (!project.service.supports(METHOD, uri)) {
51
+ this.tell('error', say('unavailable'));
52
+ return;
53
+ }
54
+ this.cancel();
55
+ const caret = editor.getCaret();
56
+ const prepare = project.service.providerOptions(METHOD, uri)?.prepareProvider === true;
57
+ let answer;
58
+ try {
59
+ answer = prepare ? await project.service.prepareRename(uri, caret) : { defaultBehavior: true };
60
+ }
61
+ catch (error) {
62
+ if (!(error instanceof StaleResultError)) {
63
+ this.tell('error', say('nothing'));
64
+ }
65
+ return;
66
+ }
67
+ 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));
68
+ if (target === null) {
69
+ this.tell('error', say('nothing'));
70
+ return;
71
+ }
72
+ const session = { target, original: editor.textInRange(target.range), edit: null, names: new AbortController() };
73
+ this.session = session;
74
+ this.publish({ phase: 'input', busy: false, name: target.placeholder, files: [], occurrences: null, suggestions: [] });
75
+ void this.light(session).then((locations) => this.suggest(session, locations));
76
+ }
77
+ /* The name was typed: Enter applies it, Shift+Enter lists what it would change first. */
78
+ async submit(name, preview) {
79
+ const session = this.session;
80
+ const next = name.trim();
81
+ if (session === null) {
82
+ return;
83
+ }
84
+ if (next === '' || next === session.original) {
85
+ this.finish();
86
+ return;
87
+ }
88
+ this.publish({ phase: 'input', busy: true, name: next, error: null });
89
+ const { project, uri } = this.language;
90
+ try {
91
+ const edit = await project.service.rename(uri, session.target.range.start, next);
92
+ if (this.session !== session) {
93
+ return;
94
+ }
95
+ if (edit === null) {
96
+ this.fail(say('refused'));
97
+ return;
98
+ }
99
+ session.edit = edit;
100
+ if (!preview) {
101
+ await this.apply(session);
102
+ return;
103
+ }
104
+ const files = await renamePreviewOf(edit, (target) => project.readText(target));
105
+ if (this.session !== session) {
106
+ return;
107
+ }
108
+ if (files === null) {
109
+ // An edit that renames files too has no list of lines to show, so it is applied or refused as a whole.
110
+ await this.apply(session);
111
+ return;
112
+ }
113
+ this.publish({ phase: 'preview', busy: false, name: next, files });
114
+ }
115
+ catch (error) {
116
+ if (this.session === session) {
117
+ this.fail(error instanceof StaleResultError ? say('stale') : error instanceof Error ? error.message : String(error));
118
+ }
119
+ }
120
+ }
121
+ /* Applies the edit the preview showed. */
122
+ async confirm() {
123
+ const session = this.session;
124
+ if (session?.edit) {
125
+ await this.apply(session);
126
+ }
127
+ }
128
+ cancel() {
129
+ if (this.session === null) {
130
+ return;
131
+ }
132
+ this.finish();
133
+ }
134
+ async apply(session) {
135
+ const result = await this.language.project.applyWorkspaceEdit(session.edit);
136
+ if (this.session !== session) {
137
+ return;
138
+ }
139
+ if (result.applied) {
140
+ this.finish();
141
+ }
142
+ else {
143
+ this.fail(result.failureReason ?? say('refused'));
144
+ }
145
+ }
146
+ /* The input stays, with the reason under it, so the name can be changed and tried again. */
147
+ fail(message) {
148
+ if (this.session !== null) {
149
+ this.session.edit = null;
150
+ this.publish({ phase: 'input', busy: false, files: [], error: say('failed', { message }) });
151
+ }
152
+ }
153
+ /* The name was changed, which answers the failure under it. */
154
+ edited() {
155
+ if (this.language.popups.getState().rename?.error != null) {
156
+ this.publish({ error: null });
157
+ }
158
+ }
159
+ finish() {
160
+ this.session?.names.abort();
161
+ this.session = null;
162
+ const { editor, popups } = this.language;
163
+ popups.setState({ rename: null });
164
+ editor.setHighlights([]);
165
+ editor.focus();
166
+ }
167
+ /* Lights up the places the name has in this file, and counts them all; what the servers listed, or null when they did not. */
168
+ async light(session) {
169
+ const { editor, project, uri } = this.language;
170
+ if (!project.service.supports('textDocument/references', uri)) {
171
+ return null;
172
+ }
173
+ try {
174
+ const locations = await project.service.references(uri, session.target.range.start, true);
175
+ if (this.session !== session || locations === null) {
176
+ return null;
177
+ }
178
+ const found = occurrencesOf(locations, uri);
179
+ editor.setHighlights(found.inFile.map((range) => ({ range, kind: 'write' })));
180
+ this.publish({ occurrences: { count: found.count, files: found.files } });
181
+ return locations;
182
+ }
183
+ catch {
184
+ // The count is a courtesy; renaming does not wait for it.
185
+ return null;
186
+ }
187
+ }
188
+ /*
189
+ * Names from the host suggestion provider, using the lines around the symbol and a sample of the lines that
190
+ * use it. They only appear under the input: the name that is typed stays the one that is applied.
191
+ */
192
+ async suggest(session, locations) {
193
+ const suggestNames = this.language.project.host.suggestNames;
194
+ if (suggestNames === undefined || this.session !== session) {
195
+ return;
196
+ }
197
+ const { editor, languageId, uri } = this.language;
198
+ try {
199
+ const line = session.target.range.start.line;
200
+ const context = editor.textInRange({
201
+ start: { line: Math.max(0, line - CONTEXT_LINES), character: 0 },
202
+ end: { line: line + CONTEXT_LINES, character: Number.MAX_SAFE_INTEGER }
203
+ });
204
+ const uses = await this.linesOf(locations ?? []);
205
+ const names = await suggestNames({ uri, languageId, name: session.original, context, uses }, session.names.signal);
206
+ if (this.session === session && names.length > 0) {
207
+ this.publish({ suggestions: names });
208
+ }
209
+ }
210
+ catch {
211
+ // Suggestions do not prevent a manual rename.
212
+ }
213
+ }
214
+ /* The text of the first lines that name the symbol, in the order the servers listed them, reading at most a few other files. */
215
+ async linesOf(locations) {
216
+ const { project } = this.language;
217
+ const texts = new Map();
218
+ const lines = [];
219
+ let others = 0;
220
+ for (const location of locations.slice(0, SAMPLE_LOCATIONS)) {
221
+ if (!texts.has(location.uri)) {
222
+ if (location.uri !== this.language.uri && others++ >= SAMPLE_OTHER_FILES) {
223
+ continue;
224
+ }
225
+ texts.set(location.uri, (await project.readText(location.uri))?.split('\n') ?? null);
226
+ }
227
+ const text = texts.get(location.uri)?.[location.range.start.line];
228
+ if (text !== undefined && text.trim() !== '' && !lines.includes(text)) {
229
+ lines.push(text);
230
+ }
231
+ }
232
+ return lines;
233
+ }
234
+ publish(patch) {
235
+ const session = this.session;
236
+ if (session === null) {
237
+ return;
238
+ }
239
+ const { popups } = this.language;
240
+ const current = popups.getState().rename;
241
+ popups.setState({
242
+ rename: {
243
+ phase: 'input',
244
+ range: session.target.range,
245
+ original: session.original,
246
+ placeholder: session.target.placeholder,
247
+ occurrences: null,
248
+ busy: false,
249
+ error: null,
250
+ name: session.target.placeholder,
251
+ files: [],
252
+ suggestions: [],
253
+ ...current,
254
+ ...patch
255
+ }
256
+ });
257
+ }
258
+ }
@@ -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
+ }