@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,79 @@
1
+ import { diffLines, splitLines } from '@adecore/merge';
2
+ import { formatNumber } from '@adecore/ui/format';
3
+ /* What a line of the text in the editor is: the index of the commit that wrote it, or this for a line no commit holds yet. */
4
+ export const UNCOMMITTED = -1;
5
+ /*
6
+ * The commit of every line of the text in the editor. Blame was run for the text on disk, so a line
7
+ * the editor still has from there keeps its commit and a line that was typed since, replaced or
8
+ * pasted has none, which reads as uncommitted. Null when the blame is not of that text.
9
+ */
10
+ export function mapBlame(blame, base, current) {
11
+ const baseLines = splitLines(base);
12
+ if (blame.lines.length !== baseLines.length) {
13
+ return null;
14
+ }
15
+ const mapped = new Int32Array(current.length).fill(UNCOMMITTED);
16
+ let basePosition = 0;
17
+ let position = 0;
18
+ const carry = (until) => {
19
+ for (; position < until; position++, basePosition++) {
20
+ mapped[position] = blame.lines[basePosition];
21
+ }
22
+ };
23
+ for (const change of diffLines(baseLines, current)) {
24
+ carry(change.otherStart);
25
+ position = change.otherEnd;
26
+ basePosition = change.baseEnd;
27
+ }
28
+ carry(current.length);
29
+ return mapped;
30
+ }
31
+ /* A person's name as git has it, with runs of space made one. */
32
+ export function shortName(name) {
33
+ return name.replace(/\s+/g, ' ').trim();
34
+ }
35
+ /*
36
+ * Who wrote the lines `from` through `to`. Blank lines count for no one and a line that is not
37
+ * committed for no author, so a method that is only edited keeps its author and says it was edited.
38
+ */
39
+ export function authorshipOf(commits, mapped, lines, from, to) {
40
+ const byName = new Map();
41
+ let uncommittedLines = 0;
42
+ let modified = false;
43
+ let latest = null;
44
+ for (let line = from; line <= to && line < mapped.length; line++) {
45
+ const index = mapped[line];
46
+ if (index === UNCOMMITTED) {
47
+ modified = true;
48
+ }
49
+ if (lines[line].trim() === '') {
50
+ continue;
51
+ }
52
+ if (index === UNCOMMITTED) {
53
+ uncommittedLines++;
54
+ continue;
55
+ }
56
+ const commit = commits[index];
57
+ const name = shortName(commit.author);
58
+ if (name === '') {
59
+ continue;
60
+ }
61
+ const author = byName.get(name) ?? { name, email: commit.email, lines: 0 };
62
+ author.lines++;
63
+ byName.set(name, author);
64
+ if (latest === null || commit.at > latest.at) {
65
+ latest = commit;
66
+ }
67
+ }
68
+ const authors = [...byName.values()].sort((left, right) => right.lines - left.lines || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
69
+ return { authors, uncommittedLines, modified, latest };
70
+ }
71
+ /* The row's words: the author with most lines, how many others wrote some, and a star when the range is edited since the commit. */
72
+ export function authorsText(authorship, i18n) {
73
+ const main = authorship.authors[0];
74
+ if (main === undefined) {
75
+ return i18n.t('editor:language.codeVision.newCode');
76
+ }
77
+ const others = authorship.authors.length - 1;
78
+ return `${main.name}${others > 0 ? ` +${formatNumber(others)}` : ''}${authorship.modified ? ' *' : ''}`;
79
+ }
@@ -0,0 +1,21 @@
1
+ import type { DocumentSymbolResult, Position } from '@adecore/lsp';
2
+ import type { i18n } from 'i18next';
3
+ export declare const MAX_DECLARATIONS = 3000;
4
+ export interface CodeVisionDeclaration {
5
+ readonly id: string;
6
+ readonly name: string;
7
+ readonly position: Position;
8
+ readonly line: number;
9
+ readonly usages: boolean;
10
+ readonly authors: boolean;
11
+ readonly authorFrom: number;
12
+ readonly authorTo: number;
13
+ }
14
+ export interface DeclarationSource {
15
+ readonly lineCount: number;
16
+ lineAt(line: number): string;
17
+ readonly languageId: string;
18
+ }
19
+ export declare function startAfterComments(source: DeclarationSource, from: Position, to: Position): Position | null;
20
+ export declare function declarationsOf(result: DocumentSymbolResult, source: DeclarationSource): CodeVisionDeclaration[];
21
+ export declare function usagesText(count: number, i18n: i18n): string;
@@ -0,0 +1,183 @@
1
+ import { formatNumber } from '@adecore/ui/format';
2
+ /* More declarations than this in one file and the rows are left out, which is the file that is too big for them to be quiet. */
3
+ export const MAX_DECLARATIONS = 3000;
4
+ /* LSP's SymbolKind: the kinds a row is drawn for and the ones whose children can be declarations too. */
5
+ const CLASS = 5;
6
+ const METHOD = 6;
7
+ const PROPERTY = 7;
8
+ const FIELD = 8;
9
+ const CONSTRUCTOR = 9;
10
+ const ENUM = 10;
11
+ const INTERFACE = 11;
12
+ const FUNCTION = 12;
13
+ const CONSTANT = 14;
14
+ const STRUCT = 23;
15
+ const CONTAINERS = new Set([1, 2, 3, 4, CLASS, ENUM, INTERFACE, STRUCT]);
16
+ const TYPES = new Set([CLASS, ENUM, INTERFACE, STRUCT]);
17
+ const CALLABLES = new Set([METHOD, FUNCTION]);
18
+ /* Languages whose `#` starts a comment, so a docblock written with it is passed like any other. */
19
+ const HASH_COMMENTS = new Set(['php', 'python', 'shellscript', 'ruby', 'yaml', 'dockerfile']);
20
+ function isLocalName(name) {
21
+ return name === '' || name.startsWith('<') || name === 'default';
22
+ }
23
+ function isUsageKind(kind, parentKind) {
24
+ if (TYPES.has(kind) || CALLABLES.has(kind)) {
25
+ return true;
26
+ }
27
+ return (kind === PROPERTY || kind === FIELD || kind === CONSTANT) && (parentKind === CLASS || parentKind === STRUCT);
28
+ }
29
+ function isAuthorKind(kind) {
30
+ return TYPES.has(kind) || CALLABLES.has(kind) || kind === CONSTRUCTOR;
31
+ }
32
+ function walk(symbols, path, parentKind, out) {
33
+ for (const symbol of symbols) {
34
+ out.push({
35
+ name: symbol.name,
36
+ kind: symbol.kind,
37
+ start: symbol.range.start,
38
+ end: symbol.range.end,
39
+ namePosition: symbol.selectionRange.start,
40
+ path,
41
+ parentKind
42
+ });
43
+ // What a function or a method holds is local to it, and a row for a callback is noise.
44
+ if (CONTAINERS.has(symbol.kind)) {
45
+ walk(symbol.children ?? [], [...path, symbol.name], symbol.kind, out);
46
+ }
47
+ }
48
+ }
49
+ function candidatesOf(result) {
50
+ const out = [];
51
+ if (result === null || result.length === 0) {
52
+ return out;
53
+ }
54
+ if ('range' in result[0]) {
55
+ walk(result, [], null, out);
56
+ return out;
57
+ }
58
+ const flat = result;
59
+ const containers = new Map(flat.filter((symbol) => CONTAINERS.has(symbol.kind)).map((symbol) => [symbol.name, symbol.kind]));
60
+ for (const symbol of flat) {
61
+ const container = symbol.containerName ?? '';
62
+ if (container !== '' && !containers.has(container)) {
63
+ continue;
64
+ }
65
+ out.push({
66
+ name: symbol.name,
67
+ kind: symbol.kind,
68
+ start: symbol.location.range.start,
69
+ end: symbol.location.range.end,
70
+ namePosition: null,
71
+ path: container === '' ? [] : [container],
72
+ parentKind: container === '' ? null : (containers.get(container) ?? null)
73
+ });
74
+ }
75
+ return out;
76
+ }
77
+ /*
78
+ * Where a declaration really starts: past the comments and the blank lines a server counts into its
79
+ * range. Null when its range is nothing but comments.
80
+ */
81
+ export function startAfterComments(source, from, to) {
82
+ const hash = HASH_COMMENTS.has(source.languageId);
83
+ let line = from.line;
84
+ let character = from.character;
85
+ while (line <= to.line && line < source.lineCount) {
86
+ const text = source.lineAt(line);
87
+ let at = character;
88
+ while (at < text.length && (text[at] === ' ' || text[at] === '\t')) {
89
+ at++;
90
+ }
91
+ if (at >= text.length) {
92
+ line++;
93
+ character = 0;
94
+ continue;
95
+ }
96
+ if (text.startsWith('/*', at)) {
97
+ let end = text.indexOf('*/', at + 2);
98
+ let scan = line;
99
+ while (end === -1 && scan + 1 <= to.line && scan + 1 < source.lineCount) {
100
+ scan++;
101
+ end = source.lineAt(scan).indexOf('*/');
102
+ }
103
+ if (end === -1) {
104
+ return null;
105
+ }
106
+ line = scan;
107
+ character = end + 2;
108
+ continue;
109
+ }
110
+ if (text.startsWith('//', at) || (hash && text[at] === '#' && text[at + 1] !== '[')) {
111
+ line++;
112
+ character = 0;
113
+ continue;
114
+ }
115
+ return line > to.line ? null : { line, character: at };
116
+ }
117
+ return null;
118
+ }
119
+ const CLOSERS = /^\s*[})\]]*[;,]?\s*$/;
120
+ /* The last line of a declaration that holds more than the brackets that close it or nothing, which is what its authors are read from. */
121
+ function lastLineWithCode(source, from, to) {
122
+ for (let line = Math.min(to, source.lineCount - 1); line > from; line--) {
123
+ if (!CLOSERS.test(source.lineAt(line))) {
124
+ return line;
125
+ }
126
+ }
127
+ return from;
128
+ }
129
+ /* Where the name starts when a server gave only a range: its first occurrence on the line the declaration starts on. */
130
+ function nameIn(source, name, start) {
131
+ const at = source.lineAt(start.line).indexOf(name, start.character);
132
+ return at === -1 ? null : { line: start.line, character: at };
133
+ }
134
+ /*
135
+ * The declarations of a file a row is drawn above: classes, interfaces, enums, structs, functions and
136
+ * methods, and the properties and constants of a class, wherever they stand outside the body of a
137
+ * function. Only one that starts its line gets a row, since a row sits above the line.
138
+ */
139
+ export function declarationsOf(result, source) {
140
+ const declarations = [];
141
+ const seen = new Map();
142
+ for (const candidate of candidatesOf(result)) {
143
+ if (isLocalName(candidate.name)) {
144
+ continue;
145
+ }
146
+ const usages = isUsageKind(candidate.kind, candidate.parentKind) && !candidate.name.startsWith('__');
147
+ const authors = isAuthorKind(candidate.kind);
148
+ if (!usages && !authors) {
149
+ continue;
150
+ }
151
+ const start = startAfterComments(source, candidate.start, candidate.end);
152
+ if (start === null || source.lineAt(start.line).slice(0, start.character).trim() !== '') {
153
+ continue;
154
+ }
155
+ const position = candidate.namePosition ?? nameIn(source, candidate.name, start);
156
+ if (position === null) {
157
+ continue;
158
+ }
159
+ const key = `${candidate.path.join('>')}>${candidate.name}#${candidate.kind}`;
160
+ const index = seen.get(key) ?? 0;
161
+ seen.set(key, index + 1);
162
+ declarations.push({
163
+ id: `${key}#${index}`,
164
+ name: candidate.name,
165
+ position,
166
+ line: start.line,
167
+ usages,
168
+ authors,
169
+ authorFrom: position.line,
170
+ authorTo: lastLineWithCode(source, position.line, candidate.end.line)
171
+ });
172
+ if (declarations.length > MAX_DECLARATIONS) {
173
+ return [];
174
+ }
175
+ }
176
+ return declarations;
177
+ }
178
+ /* "No usages", "1 usage" and "N usages". */
179
+ export function usagesText(count, i18n) {
180
+ return count === 0
181
+ ? i18n.t('editor:language.codeVision.usagesNone')
182
+ : i18n.t('editor:language.codeVision.usages', { count, formatted: formatNumber(count) });
183
+ }
@@ -0,0 +1,48 @@
1
+ import type { GitBlameResult } from './host-types.ts';
2
+ import type { DocumentSymbolResult } from '@adecore/lsp';
3
+ import type { EditorLanguage } from './editor-language.ts';
4
+ import { type Timers } from './timers.ts';
5
+ export declare const MAX_LINES = 20000;
6
+ export interface CodeVisionSettings {
7
+ readonly usages: boolean;
8
+ readonly authors: boolean;
9
+ }
10
+ export type BlameSource = {
11
+ readonly kind: 'pending';
12
+ } | {
13
+ readonly kind: 'ready';
14
+ readonly blame: GitBlameResult;
15
+ readonly base: string;
16
+ openCommit?(hash: string): void;
17
+ };
18
+ export declare class CodeVisionFeature {
19
+ private readonly language;
20
+ private readonly timers;
21
+ private settings;
22
+ private declarations;
23
+ private epoch;
24
+ private edited;
25
+ private readonly counts;
26
+ private queue;
27
+ private readonly running;
28
+ private failed;
29
+ private blame;
30
+ private lines;
31
+ private mapped;
32
+ private readonly authorships;
33
+ private scrollTimer;
34
+ private publishing;
35
+ private disposed;
36
+ constructor(language: EditorLanguage, onSymbols: (listener: (result: DocumentSymbolResult) => void) => () => void, timers?: Timers);
37
+ configure(settings: CodeVisionSettings): void;
38
+ setBlame(blame: BlameSource | null): void;
39
+ private read;
40
+ private get canCount();
41
+ private plan;
42
+ private count;
43
+ private publish;
44
+ private rows;
45
+ private mapOfBlame;
46
+ private showAuthors;
47
+ closeAuthors(restoreFocus?: boolean): void;
48
+ }
@@ -0,0 +1,236 @@
1
+ import { splitLines } from '@adecore/merge';
2
+ import { authorsText, authorshipOf, mapBlame } from './code-authors.js';
3
+ import { declarationsOf, usagesText } from './code-vision-model.js';
4
+ import { realTimers } from './timers.js';
5
+ const REFERENCES = 'textDocument/references';
6
+ /* Lines asked about above and below what is in view, so a little scrolling stays inside what is counted. */
7
+ const MARGIN_LINES = 40;
8
+ /* References are the slowest question a server is asked here, so only this many are in flight at once. */
9
+ const MAX_PARALLEL = 3;
10
+ const SCROLL_PAUSE_MS = 150;
11
+ /* Past this many lines a file gets no rows at all, which is also what keeps blame and the walk of symbols cheap. */
12
+ export const MAX_LINES = 20_000;
13
+ /*
14
+ * The rows above declarations: how often each is used and, once git has been asked, who wrote it. The
15
+ * declarations come from the symbols the servers know, and a row holds its height from the first
16
+ * answer on, empty until its entries are in, so the text never moves when they arrive. Counts are asked
17
+ * lazily, for the declarations in view and a margin around them, a few at a time, and only after the
18
+ * text has settled; an old count stays on its row until the new one is in.
19
+ */
20
+ export class CodeVisionFeature {
21
+ language;
22
+ timers;
23
+ settings = { usages: false, authors: false };
24
+ declarations = [];
25
+ /* What the declarations were read from, so a count never outlives the text it was asked for. */
26
+ epoch = 0;
27
+ edited = false;
28
+ counts = new Map();
29
+ queue = [];
30
+ /* Scrolling changes the pending queue but keeps requests already in flight. */
31
+ running = new Map();
32
+ /* Declarations a server could not count for this text, which are not asked again until it says it can. */
33
+ failed = new Set();
34
+ blame = null;
35
+ /* The text the declarations were read from, which the lines of their authors are counted in. */
36
+ lines = [];
37
+ mapped = null;
38
+ authorships = new Map();
39
+ scrollTimer;
40
+ publishing = false;
41
+ disposed = false;
42
+ constructor(language, onSymbols, timers = realTimers) {
43
+ this.language = language;
44
+ this.timers = timers;
45
+ const { editor } = language;
46
+ const offs = [
47
+ onSymbols((result) => this.read(result)),
48
+ editor.onTextChange(() => {
49
+ this.edited = true;
50
+ this.closeAuthors();
51
+ }),
52
+ editor.onViewChange(() => {
53
+ this.timers.clear(this.scrollTimer);
54
+ this.scrollTimer = this.timers.set(() => this.plan(), SCROLL_PAUSE_MS);
55
+ })
56
+ ];
57
+ const providers = language.onProvidersChanged(() => {
58
+ this.failed.clear();
59
+ this.publish();
60
+ this.plan();
61
+ });
62
+ language.onDispose(() => {
63
+ this.disposed = true;
64
+ for (const off of offs) {
65
+ off();
66
+ }
67
+ providers();
68
+ this.timers.clear(this.scrollTimer);
69
+ for (const controller of this.running.values()) {
70
+ controller.abort();
71
+ }
72
+ this.closeAuthors();
73
+ editor.setCodeVision([]);
74
+ });
75
+ }
76
+ /* Which rows to draw, from the settings of the client. */
77
+ configure(settings) {
78
+ if (settings.usages === this.settings.usages && settings.authors === this.settings.authors) {
79
+ return;
80
+ }
81
+ this.settings = settings;
82
+ this.publish();
83
+ this.plan();
84
+ }
85
+ /* What git says about the file, from the client that asked it. */
86
+ setBlame(blame) {
87
+ this.blame = blame;
88
+ this.mapped = null;
89
+ this.authorships.clear();
90
+ this.publish();
91
+ }
92
+ read(result) {
93
+ const { editor, languageId } = this.language;
94
+ const lines = splitLines(editor.getText());
95
+ this.lines = lines;
96
+ this.mapped = null;
97
+ this.authorships.clear();
98
+ this.edited = false;
99
+ this.declarations =
100
+ lines.length > MAX_LINES ? [] : declarationsOf(result, { lineCount: lines.length, lineAt: (line) => lines[line] ?? '', languageId });
101
+ this.epoch++;
102
+ this.failed.clear();
103
+ this.publish();
104
+ this.plan();
105
+ }
106
+ get canCount() {
107
+ return this.settings.usages && this.language.project.service.supports(REFERENCES, this.language.uri);
108
+ }
109
+ /* Queues the declarations in view whose count is not from the current text, nearest the top of the view first. */
110
+ plan() {
111
+ if (this.disposed || this.edited || !this.canCount) {
112
+ return;
113
+ }
114
+ const view = this.language.editor.getVisibleRange();
115
+ const first = view.start.line - MARGIN_LINES;
116
+ const last = view.end.line + MARGIN_LINES;
117
+ this.queue = this.declarations
118
+ .filter((declaration) => declaration.usages &&
119
+ declaration.line >= first &&
120
+ declaration.line <= last &&
121
+ this.counts.get(declaration.id)?.epoch !== this.epoch &&
122
+ !this.running.has(declaration.id) &&
123
+ !this.failed.has(declaration.id))
124
+ .sort((left, right) => Math.abs(left.line - view.start.line) - Math.abs(right.line - view.start.line));
125
+ while (this.running.size < MAX_PARALLEL && this.queue.length > 0) {
126
+ void this.count(this.queue.shift());
127
+ }
128
+ }
129
+ async count(declaration) {
130
+ const { project, uri } = this.language;
131
+ const controller = new AbortController();
132
+ this.running.set(declaration.id, controller);
133
+ const epoch = this.epoch;
134
+ try {
135
+ const locations = await project.service.references(uri, declaration.position, false, { signal: controller.signal, parallel: true });
136
+ if (!this.disposed && epoch === this.epoch) {
137
+ this.counts.set(declaration.id, { count: distinctPlaces(locations ?? []), epoch });
138
+ this.publish();
139
+ }
140
+ }
141
+ catch {
142
+ // A text that moved on is counted again from its own symbols; for a server that is not up the declaration waits for it to say so.
143
+ if (epoch === this.epoch) {
144
+ this.failed.add(declaration.id);
145
+ }
146
+ }
147
+ finally {
148
+ this.running.delete(declaration.id);
149
+ this.plan();
150
+ }
151
+ }
152
+ /* Said once for every change that lands in the same turn, since each is a pass over the rows of the layout. */
153
+ publish() {
154
+ if (this.publishing) {
155
+ return;
156
+ }
157
+ this.publishing = true;
158
+ queueMicrotask(() => {
159
+ this.publishing = false;
160
+ if (!this.disposed) {
161
+ this.language.editor.setCodeVision(this.rows());
162
+ }
163
+ });
164
+ }
165
+ rows() {
166
+ const rows = [];
167
+ const taken = new Set();
168
+ const counted = this.canCount;
169
+ const withAuthors = this.settings.authors && this.blame !== null;
170
+ const commits = this.blame?.kind === 'ready' ? this.blame.blame.commits : [];
171
+ const mapped = this.blame?.kind === 'ready' && this.settings.authors ? this.mapOfBlame(this.blame) : null;
172
+ for (const declaration of this.declarations) {
173
+ const reserved = (counted && declaration.usages) || (withAuthors && declaration.authors);
174
+ if (!reserved || taken.has(declaration.line)) {
175
+ continue;
176
+ }
177
+ taken.add(declaration.line);
178
+ const entries = [];
179
+ const known = this.counts.get(declaration.id);
180
+ if (counted && declaration.usages && known !== undefined) {
181
+ entries.push({
182
+ id: 'usages',
183
+ text: usagesText(known.count, this.language.project.i18n),
184
+ activate: () => void this.language.peek.open(declaration.position)
185
+ });
186
+ }
187
+ if (mapped !== null && declaration.authors) {
188
+ let authorship = this.authorships.get(declaration.id);
189
+ if (authorship === undefined) {
190
+ authorship = authorshipOf(commits, mapped, this.lines, declaration.authorFrom, declaration.authorTo);
191
+ this.authorships.set(declaration.id, authorship);
192
+ }
193
+ const shown = authorship;
194
+ entries.push({
195
+ id: 'authors',
196
+ text: authorsText(shown, this.language.project.i18n),
197
+ ...(shown.authors.length > 0 ? { icon: shown.authors.length > 1 ? 'users' : 'user' } : {}),
198
+ activate: (anchor) => this.showAuthors(declaration, shown, anchor)
199
+ });
200
+ }
201
+ rows.push({ id: declaration.id, line: declaration.line, entries });
202
+ }
203
+ return rows;
204
+ }
205
+ mapOfBlame(source) {
206
+ if (this.mapped?.blame !== source.blame) {
207
+ this.mapped = { blame: source.blame, lines: mapBlame(source.blame, source.base, this.lines) };
208
+ }
209
+ return this.mapped.lines;
210
+ }
211
+ showAuthors(declaration, authorship, anchor) {
212
+ const source = this.blame;
213
+ const openCommit = source?.kind === 'ready' ? source.openCommit : undefined;
214
+ this.language.popups.setState({
215
+ authors: {
216
+ anchor,
217
+ name: declaration.name,
218
+ authorship,
219
+ openCommit: openCommit === undefined || authorship.latest === null ? null : () => openCommit(authorship.latest.hash)
220
+ }
221
+ });
222
+ }
223
+ /* Closes the card of the authors; `restoreFocus` is for a close that came from the keyboard, which gives the editor its keys back. */
224
+ closeAuthors(restoreFocus = false) {
225
+ if (this.language.popups.getState().authors !== null) {
226
+ this.language.popups.setState({ authors: null });
227
+ if (restoreFocus) {
228
+ this.language.editor.focus();
229
+ }
230
+ }
231
+ }
232
+ }
233
+ /* The places a server named, each counted once however many ways it was reached. */
234
+ function distinctPlaces(locations) {
235
+ return new Set(locations.map((location) => `${location.uri}\0${location.range.start.line}\0${location.range.start.character}`)).size;
236
+ }
@@ -0,0 +1,24 @@
1
+ import type { CompletionItem } from '@adecore/lsp';
2
+ import { type Insertion } from './completion-model.ts';
3
+ export declare const PARAMETER_HINTS_COMMAND = "editor.action.triggerParameterHints";
4
+ export type CallPlan = {
5
+ readonly action: 'none';
6
+ } | {
7
+ readonly action: 'add';
8
+ readonly inside: boolean;
9
+ } | {
10
+ readonly action: 'enter';
11
+ readonly shift: number;
12
+ };
13
+ export interface CallSite {
14
+ readonly item: CompletionItem;
15
+ readonly languageId: string;
16
+ readonly insertion: Insertion;
17
+ readonly before: string;
18
+ readonly rest: string;
19
+ readonly replace: boolean;
20
+ }
21
+ export declare function isCallItem(item: CompletionItem, lineBefore: string): boolean;
22
+ export declare function hasParameters(item: CompletionItem): boolean | null;
23
+ export declare function planCall(site: CallSite): CallPlan;
24
+ export declare function withParentheses(insertion: Insertion, inside: boolean): Insertion;