@adecore/editor-react 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CallHierarchyPanel.d.ts +6 -0
- package/dist/CallHierarchyPanel.js +88 -0
- package/dist/CompletionPopup.js +11 -3
- package/dist/DiffView.d.ts +22 -0
- package/dist/DiffView.js +203 -0
- package/dist/EditorContextMenu.js +1 -0
- package/dist/EditorView.js +18 -1
- package/dist/FindReplace.d.ts +1 -1
- package/dist/FindReplace.js +41 -11
- package/dist/HoverCard.js +2 -2
- package/dist/LanguagePopups.js +4 -1
- package/dist/Markdown.d.ts +2 -1
- package/dist/Markdown.js +8 -4
- package/dist/PeekPanel.d.ts +3 -0
- package/dist/PeekPanel.js +2 -3
- package/dist/PickPopup.js +15 -2
- package/dist/RenameCard.js +10 -5
- package/dist/RenderedDocs.d.ts +9 -0
- package/dist/RenderedDocs.js +84 -0
- package/dist/SplitDiffView.d.ts +9 -0
- package/dist/SplitDiffView.js +276 -0
- package/dist/SymbolPicker.d.ts +3 -0
- package/dist/SymbolPicker.js +4 -3
- package/dist/call-hierarchy-model.d.ts +52 -0
- package/dist/call-hierarchy-model.js +99 -0
- package/dist/call-hierarchy.d.ts +42 -0
- package/dist/call-hierarchy.js +399 -0
- package/dist/code-actions-model.d.ts +2 -0
- package/dist/code-actions-model.js +12 -10
- package/dist/code-actions.js +3 -1
- package/dist/completion-model.d.ts +14 -4
- package/dist/completion-model.js +40 -33
- package/dist/completion.d.ts +4 -0
- package/dist/completion.js +257 -42
- package/dist/diagnostics.js +8 -6
- package/dist/diff-expansion.d.ts +9 -0
- package/dist/diff-expansion.js +41 -0
- package/dist/diff-overlays.d.ts +15 -0
- package/dist/diff-overlays.js +28 -0
- package/dist/diff-syntax.d.ts +16 -0
- package/dist/diff-syntax.js +49 -0
- package/dist/diff-wordmarks.d.ts +5 -0
- package/dist/diff-wordmarks.js +26 -0
- package/dist/doc-comment-model.d.ts +23 -0
- package/dist/doc-comment-model.js +291 -0
- package/dist/editor-language.d.ts +13 -1
- package/dist/editor-language.js +14 -4
- package/dist/editor-react.css +79 -0
- package/dist/format-on-type.d.ts +14 -0
- package/dist/format-on-type.js +96 -0
- package/dist/host-types.d.ts +17 -0
- package/dist/hover.d.ts +4 -0
- package/dist/hover.js +15 -2
- package/dist/index.d.ts +19 -5
- package/dist/index.js +9 -1
- package/dist/inlay-hints.d.ts +47 -2
- package/dist/inlay-hints.js +353 -16
- package/dist/language-ids.js +4 -2
- package/dist/locales/en.json +114 -3
- package/dist/locales/nl.json +114 -3
- package/dist/models.d.ts +6 -2
- package/dist/models.js +5 -1
- package/dist/patch-decorations.d.ts +27 -0
- package/dist/patch-decorations.js +182 -0
- package/dist/patch-layout.d.ts +40 -0
- package/dist/patch-layout.js +304 -0
- package/dist/peek.js +1 -0
- package/dist/pick.d.ts +1 -0
- package/dist/pick.js +11 -1
- package/dist/popups.d.ts +17 -0
- package/dist/popups.js +1 -0
- package/dist/project-language.d.ts +8 -0
- package/dist/project-language.js +34 -2
- package/dist/refresher.d.ts +1 -0
- package/dist/refresher.js +5 -0
- package/dist/rename-model.d.ts +1 -1
- package/dist/rename-model.js +3 -7
- package/dist/rename.js +5 -8
- package/dist/rendered-docs.d.ts +62 -0
- package/dist/rendered-docs.js +368 -0
- package/dist/row-host.d.ts +2 -0
- package/dist/row-host.js +11 -3
- package/dist/semantic-delta.d.ts +2 -0
- package/dist/semantic-delta.js +27 -0
- package/dist/semantic-model.js +16 -0
- package/dist/semantic-tokens.d.ts +26 -1
- package/dist/semantic-tokens.js +174 -14
- package/dist/shortcut-keys.d.ts +2 -0
- package/dist/shortcut-keys.js +6 -2
- package/dist/signature.js +1 -1
- package/dist/snippet-field-transforms.d.ts +30 -0
- package/dist/snippet-field-transforms.js +116 -0
- package/dist/snippet-indent.d.ts +2 -0
- package/dist/snippet-indent.js +49 -0
- package/dist/snippet-range-order.d.ts +3 -0
- package/dist/snippet-range-order.js +19 -0
- package/dist/snippet-session.d.ts +42 -6
- package/dist/snippet-session.js +650 -53
- package/dist/snippet-transform-client.d.ts +19 -0
- package/dist/snippet-transform-client.js +66 -0
- package/dist/snippet-transform-format.d.ts +15 -0
- package/dist/snippet-transform-format.js +302 -0
- package/dist/snippet-transform-protocol.d.ts +45 -0
- package/dist/snippet-transform-protocol.js +8 -0
- package/dist/snippet-transform-queue.d.ts +32 -0
- package/dist/snippet-transform-queue.js +173 -0
- package/dist/snippet-transform-worker.d.ts +10 -0
- package/dist/snippet-transform-worker.js +41 -0
- package/dist/snippet-variable-names.d.ts +1 -0
- package/dist/snippet-variable-names.js +36 -0
- package/dist/snippet-variables.d.ts +7 -0
- package/dist/snippet-variables.js +66 -0
- package/dist/snippet.d.ts +30 -3
- package/dist/snippet.js +402 -87
- package/dist/split-alignment.d.ts +6 -0
- package/dist/split-alignment.js +38 -0
- package/dist/split-scroll.d.ts +14 -0
- package/dist/split-scroll.js +45 -0
- package/dist/symbols.d.ts +2 -1
- package/dist/symbols.js +39 -7
- package/dist/testing.d.ts +5 -1
- package/dist/testing.js +12 -0
- package/dist/workspace-edit.d.ts +25 -3
- package/dist/workspace-edit.js +282 -143
- package/package.json +10 -5
- package/src/CallHierarchyPanel.tsx +238 -0
- package/src/CompletionPopup.tsx +12 -1
- package/src/DiffView.tsx +262 -0
- package/src/EditorContextMenu.tsx +1 -0
- package/src/EditorView.tsx +21 -2
- package/src/FindReplace.tsx +51 -13
- package/src/HoverCard.tsx +23 -21
- package/src/LanguagePopups.tsx +5 -0
- package/src/Markdown.tsx +10 -3
- package/src/PeekPanel.tsx +2 -3
- package/src/PickPopup.tsx +18 -2
- package/src/RenameCard.tsx +18 -0
- package/src/RenderedDocs.tsx +155 -0
- package/src/SplitDiffView.tsx +337 -0
- package/src/SymbolPicker.tsx +4 -3
- package/src/call-hierarchy-model.ts +158 -0
- package/src/call-hierarchy.ts +441 -0
- package/src/code-actions-model.ts +17 -11
- package/src/code-actions.ts +3 -1
- package/src/completion-model.ts +61 -35
- package/src/completion.ts +302 -42
- package/src/diagnostics.ts +8 -6
- package/src/diff-expansion.ts +49 -0
- package/src/diff-overlays.tsx +69 -0
- package/src/diff-syntax.ts +67 -0
- package/src/diff-wordmarks.ts +31 -0
- package/src/doc-comment-model.ts +336 -0
- package/src/editor-language.ts +21 -5
- package/src/editor-react.css +79 -0
- package/src/format-on-type.ts +111 -0
- package/src/host-types.ts +19 -0
- package/src/hover.ts +18 -3
- package/src/index.ts +27 -4
- package/src/inlay-hints.ts +394 -18
- package/src/language-ids.ts +4 -2
- package/src/locales/en.json +114 -3
- package/src/locales/nl.json +114 -3
- package/src/models.ts +40 -1
- package/src/patch-decorations.ts +224 -0
- package/src/patch-layout.ts +388 -0
- package/src/peek.ts +1 -0
- package/src/pick.ts +12 -1
- package/src/popups.ts +24 -0
- package/src/project-language.ts +43 -2
- package/src/refresher.ts +6 -0
- package/src/rename-model.ts +4 -8
- package/src/rename.ts +5 -8
- package/src/rendered-docs.ts +422 -0
- package/src/row-host.ts +12 -3
- package/src/semantic-delta.ts +29 -0
- package/src/semantic-model.ts +17 -0
- package/src/semantic-tokens.ts +202 -19
- package/src/shortcut-keys.ts +6 -2
- package/src/signature.ts +1 -1
- package/src/snippet-field-transforms.ts +144 -0
- package/src/snippet-indent.ts +53 -0
- package/src/snippet-range-order.ts +19 -0
- package/src/snippet-session.ts +797 -56
- package/src/snippet-transform-client.ts +84 -0
- package/src/snippet-transform-format.ts +325 -0
- package/src/snippet-transform-protocol.ts +24 -0
- package/src/snippet-transform-queue.ts +199 -0
- package/src/snippet-transform-worker.ts +58 -0
- package/src/snippet-variable-names.ts +36 -0
- package/src/snippet-variables.ts +74 -0
- package/src/snippet.ts +452 -89
- package/src/split-alignment.ts +45 -0
- package/src/split-scroll.ts +54 -0
- package/src/symbols.ts +43 -7
- package/src/testing.ts +25 -0
- package/src/workspace-edit.ts +347 -169
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface DocTagEntry {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly type: string | null;
|
|
4
|
+
readonly subject: string | null;
|
|
5
|
+
readonly text: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ParsedDocComment {
|
|
8
|
+
readonly description: string;
|
|
9
|
+
readonly tags: readonly DocTagEntry[];
|
|
10
|
+
}
|
|
11
|
+
export declare const DOC_LINK_PREFIX = "#doc-link:";
|
|
12
|
+
export declare function decodeEntities(text: string): string;
|
|
13
|
+
export declare function safeAddress(address: string): string | null;
|
|
14
|
+
export declare function htmlToMarkdown(source: string): string;
|
|
15
|
+
export declare function inlineTagsToMarkdown(markdown: string): string;
|
|
16
|
+
export declare function parseDocComment(body: string, options?: {
|
|
17
|
+
php?: boolean;
|
|
18
|
+
}): ParsedDocComment;
|
|
19
|
+
export interface DocTagSection {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly tags: readonly DocTagEntry[];
|
|
22
|
+
}
|
|
23
|
+
export declare function sectionsOf(tags: readonly DocTagEntry[]): DocTagSection[];
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A documentation comment read the way a person reads it: a description and its tags. The text is
|
|
3
|
+
* Markdown by the time it leaves here. HTML is never passed on; a small, fixed subset is turned into
|
|
4
|
+
* Markdown and every other tag is dropped or left as text.
|
|
5
|
+
*/
|
|
6
|
+
/* The prefix of an address that stands for a name a `{@link}` points at, which the view resolves instead of following. */
|
|
7
|
+
export const DOC_LINK_PREFIX = '#doc-link:';
|
|
8
|
+
const ALIASES = {
|
|
9
|
+
returns: 'return',
|
|
10
|
+
exception: 'throws',
|
|
11
|
+
arg: 'param',
|
|
12
|
+
argument: 'param',
|
|
13
|
+
prop: 'property',
|
|
14
|
+
yields: 'yield',
|
|
15
|
+
link: 'see'
|
|
16
|
+
};
|
|
17
|
+
/* Tags that carry a type before their subject. */
|
|
18
|
+
const TYPED = new Set(['param', 'property', 'return', 'throws', 'var', 'type', 'yield', 'template', 'this']);
|
|
19
|
+
/* Tags whose first word is the thing they are about. */
|
|
20
|
+
const SUBJECT_FIRST = new Set(['param', 'property', 'var', 'template']);
|
|
21
|
+
const TAG_START = /^@([A-Za-z][\w-]*)(?::[\w-]+)?[ \t]*(.*)$/;
|
|
22
|
+
const FENCE = /^\s*(```|~~~)/;
|
|
23
|
+
const DASH = /^[—–-][ \t]+/;
|
|
24
|
+
const DANGEROUS = /<(script|style|iframe|object|embed|svg|math|form)\b[\s\S]*?<\/\1\s*>|<\/?(script|style|iframe|object|embed|link|meta|base|svg|math|form|img|video|audio|source|input|button|textarea|select|canvas|frame|frameset)\b[^>]*>/gi;
|
|
25
|
+
const SAFE_SCHEME = /^(https?:|mailto:)/i;
|
|
26
|
+
/* Reads a type that starts the text: in braces as JSDoc writes it, or as one word with its brackets as PHPDoc does. */
|
|
27
|
+
function takeType(text, braces) {
|
|
28
|
+
const trimmed = text.trimStart();
|
|
29
|
+
if (trimmed.startsWith('{')) {
|
|
30
|
+
let depth = 0;
|
|
31
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
32
|
+
depth += trimmed[i] === '{' ? 1 : trimmed[i] === '}' ? -1 : 0;
|
|
33
|
+
if (depth === 0) {
|
|
34
|
+
return { type: trimmed.slice(1, i).trim(), rest: trimmed.slice(i + 1).trimStart() };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { type: null, rest: trimmed };
|
|
38
|
+
}
|
|
39
|
+
if (braces || /^[$&.]/.test(trimmed)) {
|
|
40
|
+
return { type: null, rest: trimmed };
|
|
41
|
+
}
|
|
42
|
+
let depth = 0;
|
|
43
|
+
let end = 0;
|
|
44
|
+
for (; end < trimmed.length; end++) {
|
|
45
|
+
const character = trimmed[end];
|
|
46
|
+
if ('<([{'.includes(character)) {
|
|
47
|
+
depth++;
|
|
48
|
+
}
|
|
49
|
+
else if ('>)]}'.includes(character)) {
|
|
50
|
+
depth--;
|
|
51
|
+
}
|
|
52
|
+
else if (/\s/.test(character) && depth <= 0) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { type: end === 0 ? null : trimmed.slice(0, end), rest: trimmed.slice(end).trimStart() };
|
|
57
|
+
}
|
|
58
|
+
/* The first word of a tag's text: a name with its sigils and the `[name=default]` form of JSDoc. */
|
|
59
|
+
function takeSubject(text) {
|
|
60
|
+
const optional = /^\[([^\]]+)\]\s*/.exec(text);
|
|
61
|
+
if (optional !== null) {
|
|
62
|
+
return { subject: optional[1], rest: text.slice(optional[0].length) };
|
|
63
|
+
}
|
|
64
|
+
const word = /^(?:\.\.\.|&)?[$\w.\\:-]+/.exec(text);
|
|
65
|
+
if (word === null) {
|
|
66
|
+
return { subject: null, rest: text };
|
|
67
|
+
}
|
|
68
|
+
return { subject: word[0], rest: text.slice(word[0].length).trimStart() };
|
|
69
|
+
}
|
|
70
|
+
/* A comment's lines cut where a tag starts at the beginning of a line, outside a fenced block. */
|
|
71
|
+
function splitTags(body) {
|
|
72
|
+
const description = [];
|
|
73
|
+
const raw = [];
|
|
74
|
+
let fenced = false;
|
|
75
|
+
for (const line of body.split('\n')) {
|
|
76
|
+
if (FENCE.test(line)) {
|
|
77
|
+
fenced = !fenced;
|
|
78
|
+
}
|
|
79
|
+
const start = fenced ? null : TAG_START.exec(line.trimStart());
|
|
80
|
+
if (start !== null) {
|
|
81
|
+
raw.push({ name: start[1].toLowerCase(), text: start[2] });
|
|
82
|
+
}
|
|
83
|
+
else if (raw.length > 0) {
|
|
84
|
+
raw[raw.length - 1].text += `\n${line}`;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
description.push(line);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { description: description.join('\n').trim(), raw };
|
|
91
|
+
}
|
|
92
|
+
function entryOf(name, text, php) {
|
|
93
|
+
let rest = text.trim();
|
|
94
|
+
let type = null;
|
|
95
|
+
let subject = null;
|
|
96
|
+
if (TYPED.has(name)) {
|
|
97
|
+
const taken = name === 'return' && rest.startsWith('$this') ? { type: '$this', rest: rest.slice(5).trimStart() } : takeType(rest, !php);
|
|
98
|
+
type = taken.type;
|
|
99
|
+
rest = taken.rest;
|
|
100
|
+
// A type that stands alone is the whole text of a tag that has no subject.
|
|
101
|
+
if (SUBJECT_FIRST.has(name) || (name === 'throws' && type === null)) {
|
|
102
|
+
const word = takeSubject(rest);
|
|
103
|
+
subject = word.subject;
|
|
104
|
+
rest = word.rest;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (name === 'see' && !rest.startsWith('{')) {
|
|
108
|
+
const target = /^\S+/.exec(rest);
|
|
109
|
+
subject = target === null ? null : target[0];
|
|
110
|
+
rest = target === null ? rest : rest.slice(target[0].length).trimStart();
|
|
111
|
+
}
|
|
112
|
+
if (name === 'throws' && type === null && subject !== null) {
|
|
113
|
+
type = subject;
|
|
114
|
+
subject = null;
|
|
115
|
+
}
|
|
116
|
+
return { name, type, subject, text: rest.replace(DASH, '').trim() };
|
|
117
|
+
}
|
|
118
|
+
/* Text outside backtick code, which is the only text HTML and inline tags are read in. */
|
|
119
|
+
function outsideCode(markdown, convert) {
|
|
120
|
+
return markdown
|
|
121
|
+
.split(/(```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]*`)/)
|
|
122
|
+
.map((part, index) => (index % 2 === 1 ? part : convert(part)))
|
|
123
|
+
.join('');
|
|
124
|
+
}
|
|
125
|
+
const ENTITIES = { lt: '<', gt: '>', amp: '&', quot: '"', apos: "'", nbsp: ' ', ndash: '-', mdash: '-', hellip: '...' };
|
|
126
|
+
/* Named entities of the small set docblocks use, and numeric ones; anything else stays as written. */
|
|
127
|
+
export function decodeEntities(text) {
|
|
128
|
+
return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (match, code) => {
|
|
129
|
+
if (code[0] === '#') {
|
|
130
|
+
const point = code[1].toLowerCase() === 'x' ? Number.parseInt(code.slice(2), 16) : Number.parseInt(code.slice(1), 10);
|
|
131
|
+
return Number.isFinite(point) && point > 0 && point <= 0x10ffff ? String.fromCodePoint(point) : match;
|
|
132
|
+
}
|
|
133
|
+
return ENTITIES[code.toLowerCase()] ?? match;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function dedent(text) {
|
|
137
|
+
const lines = text.replace(/^\n+|\s+$/g, '').split('\n');
|
|
138
|
+
const indents = lines.filter((line) => line.trim() !== '').map((line) => line.length - line.trimStart().length);
|
|
139
|
+
const common = indents.length === 0 ? 0 : Math.min(...indents);
|
|
140
|
+
return lines.map((line) => line.slice(common)).join('\n');
|
|
141
|
+
}
|
|
142
|
+
function fenced(body) {
|
|
143
|
+
return `\n\n\`\`\`\n${dedent(decodeEntities(stripTags(body)))}\n\`\`\`\n\n`;
|
|
144
|
+
}
|
|
145
|
+
function stripTags(html) {
|
|
146
|
+
return html.replace(/<\/?[a-zA-Z][^>]*>/g, '');
|
|
147
|
+
}
|
|
148
|
+
/* The address of a link when it is one a reader may follow; null for everything else, such as a script address. */
|
|
149
|
+
export function safeAddress(address) {
|
|
150
|
+
const trimmed = decodeEntities(address.trim());
|
|
151
|
+
return SAFE_SCHEME.test(trimmed) || trimmed.startsWith(DOC_LINK_PREFIX) ? trimmed.replace(/\s/g, '%20').replace(/\(/g, '%28').replace(/\)/g, '%29') : null;
|
|
152
|
+
}
|
|
153
|
+
/*
|
|
154
|
+
* The HTML subset docblocks use, as Markdown: paragraphs and breaks, bold, italic, code, preformatted
|
|
155
|
+
* blocks, links to http, https and mailto, lists, headings and entities. Dangerous elements go with their
|
|
156
|
+
* content, attributes are never copied except a safe `href`, and a tag that is not in the subset is left
|
|
157
|
+
* as the text it was, which the view draws escaped.
|
|
158
|
+
*/
|
|
159
|
+
export function htmlToMarkdown(source) {
|
|
160
|
+
if (!source.includes('<') && !source.includes('&')) {
|
|
161
|
+
return source;
|
|
162
|
+
}
|
|
163
|
+
const lists = [];
|
|
164
|
+
const counters = [];
|
|
165
|
+
let anchor = null;
|
|
166
|
+
const blocks = source
|
|
167
|
+
.replace(/<pre\b[^>]*>([\s\S]*?)<\/pre\s*>/gi, (_match, body) => fenced(body))
|
|
168
|
+
.replace(/<code>([^<]*\n[^<]*)<\/code>/gi, (_match, body) => fenced(body));
|
|
169
|
+
const converted = outsideCode(blocks, (text) => text
|
|
170
|
+
.replace(DANGEROUS, '')
|
|
171
|
+
.replace(/<(\/?)(p|br|b|strong|i|em|u|code|tt|kbd|samp|var|a|ul|ol|li|h[1-6]|blockquote|hr|div|span|dl|dt|dd|sup|sub|small|center)\b([^>]*?)(\/?)>/gi, (_match, close, tag, attributes) => {
|
|
172
|
+
const closing = close === '/';
|
|
173
|
+
switch (tag.toLowerCase()) {
|
|
174
|
+
case 'p':
|
|
175
|
+
case 'div':
|
|
176
|
+
case 'dl':
|
|
177
|
+
return '\n\n';
|
|
178
|
+
case 'br':
|
|
179
|
+
return '\n';
|
|
180
|
+
case 'hr':
|
|
181
|
+
return '\n\n---\n\n';
|
|
182
|
+
case 'b':
|
|
183
|
+
case 'strong':
|
|
184
|
+
return '**';
|
|
185
|
+
case 'i':
|
|
186
|
+
case 'em':
|
|
187
|
+
case 'var':
|
|
188
|
+
return '*';
|
|
189
|
+
case 'code':
|
|
190
|
+
case 'tt':
|
|
191
|
+
case 'kbd':
|
|
192
|
+
case 'samp':
|
|
193
|
+
return '`';
|
|
194
|
+
case 'a': {
|
|
195
|
+
if (closing) {
|
|
196
|
+
const target = anchor;
|
|
197
|
+
anchor = null;
|
|
198
|
+
return target === null ? '' : `](${target})`;
|
|
199
|
+
}
|
|
200
|
+
const href = /\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(attributes);
|
|
201
|
+
anchor = href === null ? null : safeAddress(href[1] ?? href[2] ?? href[3] ?? '');
|
|
202
|
+
return anchor === null ? '' : '[';
|
|
203
|
+
}
|
|
204
|
+
case 'ul':
|
|
205
|
+
case 'ol':
|
|
206
|
+
if (closing) {
|
|
207
|
+
lists.pop();
|
|
208
|
+
counters.pop();
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
lists.push(tag.toLowerCase());
|
|
212
|
+
counters.push(0);
|
|
213
|
+
}
|
|
214
|
+
return '\n';
|
|
215
|
+
case 'li': {
|
|
216
|
+
if (closing) {
|
|
217
|
+
return '';
|
|
218
|
+
}
|
|
219
|
+
const depth = Math.max(0, lists.length - 1);
|
|
220
|
+
const marker = lists.at(-1) === 'ol' ? `${(counters[depth] = (counters[depth] ?? 0) + 1)}.` : '-';
|
|
221
|
+
return `\n${' '.repeat(depth)}${marker} `;
|
|
222
|
+
}
|
|
223
|
+
case 'blockquote':
|
|
224
|
+
return closing ? '\n\n' : '\n\n> ';
|
|
225
|
+
case 'dt':
|
|
226
|
+
return closing ? '**\n\n' : '\n\n**';
|
|
227
|
+
case 'dd':
|
|
228
|
+
return '\n\n';
|
|
229
|
+
default:
|
|
230
|
+
if (/^h[1-6]$/i.test(tag)) {
|
|
231
|
+
return closing ? '\n\n' : `\n\n${'#'.repeat(Number(tag[1]))} `;
|
|
232
|
+
}
|
|
233
|
+
return '';
|
|
234
|
+
}
|
|
235
|
+
}));
|
|
236
|
+
// Entities last, so `<b>` stays the text it spells and is not read as a tag above.
|
|
237
|
+
return outsideCode(converted, decodeEntities).replace(/\n{3,}/g, '\n\n');
|
|
238
|
+
}
|
|
239
|
+
/*
|
|
240
|
+
* Inline `{@link Name}`, `{@link Name label}`, `{@link Name|label}`, the `linkcode` and `linkplain` forms, `{@see Name}`
|
|
241
|
+
* and `{@code text}`. A name becomes a link the view resolves; an address becomes a link to it.
|
|
242
|
+
*/
|
|
243
|
+
export function inlineTagsToMarkdown(markdown) {
|
|
244
|
+
return outsideCode(markdown, (text) => text
|
|
245
|
+
.replace(/\{@(?:link|linkcode|linkplain|see)\s+([^}\s|]+)(?:\s*[|\s]\s*([^}]+?))?\s*\}/g, (_match, target, label) => {
|
|
246
|
+
const address = /^https?:\/\//i.test(target) ? safeAddress(target) : `${DOC_LINK_PREFIX}${encodeURIComponent(target)}`;
|
|
247
|
+
const shown = label ?? target;
|
|
248
|
+
return address === null ? `\`${shown}\`` : `[${label === undefined ? `\`${shown}\`` : shown}](${address})`;
|
|
249
|
+
})
|
|
250
|
+
.replace(/\{@code\s+([^}]*)\}/g, (_match, code) => `\`${code.trim()}\``)
|
|
251
|
+
.replace(/\{@inheritDoc\}/g, ''));
|
|
252
|
+
}
|
|
253
|
+
/* An example's lines without the caption, and without the indentation they share; the text after the tag on its own line is not indented like the rest. */
|
|
254
|
+
function exampleOf(text) {
|
|
255
|
+
const code = text.replace(/^\s*<caption>[\s\S]*?<\/caption>[ \t]*\n?/i, '');
|
|
256
|
+
const [head = '', ...tail] = code.split('\n');
|
|
257
|
+
return head.trim() === '' ? dedent(code) : [head.trim(), dedent(tail.join('\n'))].join('\n').trim();
|
|
258
|
+
}
|
|
259
|
+
function proseOf(text) {
|
|
260
|
+
return inlineTagsToMarkdown(htmlToMarkdown(text)).trim();
|
|
261
|
+
}
|
|
262
|
+
/* Reads the body of a documentation comment, as `findDocComments` returns it. `php` lets a type stand without braces. */
|
|
263
|
+
export function parseDocComment(body, options = {}) {
|
|
264
|
+
const php = options.php === true;
|
|
265
|
+
const { description, raw } = splitTags(body);
|
|
266
|
+
const tags = raw.map(({ name, text }) => {
|
|
267
|
+
const bare = name.replace(/^(?:psalm|phpstan)-/, '');
|
|
268
|
+
const canonical = ALIASES[bare] ?? bare;
|
|
269
|
+
const entry = entryOf(canonical, text, php);
|
|
270
|
+
// An example is code: it keeps its lines and gets no prose treatment, only its caption removed.
|
|
271
|
+
if (canonical === 'example') {
|
|
272
|
+
return { ...entry, text: exampleOf(text) };
|
|
273
|
+
}
|
|
274
|
+
return { ...entry, text: proseOf(entry.text) };
|
|
275
|
+
});
|
|
276
|
+
return { description: proseOf(description), tags };
|
|
277
|
+
}
|
|
278
|
+
/* Tags in the order a reader looks for them; every other tag follows in the order it was written. */
|
|
279
|
+
const ORDER = ['deprecated', 'template', 'param', 'property', 'type', 'var', 'return', 'yield', 'throws', 'see', 'example', 'since', 'version', 'author'];
|
|
280
|
+
/* The tags gathered by name, so a list of parameters is one section, in reading order. */
|
|
281
|
+
export function sectionsOf(tags) {
|
|
282
|
+
const byName = new Map();
|
|
283
|
+
for (const tag of tags) {
|
|
284
|
+
byName.set(tag.name, [...(byName.get(tag.name) ?? []), tag]);
|
|
285
|
+
}
|
|
286
|
+
const rank = (name) => {
|
|
287
|
+
const index = ORDER.indexOf(name);
|
|
288
|
+
return index === -1 ? ORDER.length : index;
|
|
289
|
+
};
|
|
290
|
+
return [...byName].map(([name, entries]) => ({ name, tags: entries })).sort((left, right) => rank(left.name) - rank(right.name));
|
|
291
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type Keymap } from '@adecore/editor/keymap';
|
|
2
2
|
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
3
3
|
import type { Location } from '@adecore/lsp';
|
|
4
|
+
import { CallHierarchyFeature } from './call-hierarchy.ts';
|
|
4
5
|
import { CodeActionsFeature } from './code-actions.ts';
|
|
5
6
|
import { CodeVisionFeature } from './code-vision.ts';
|
|
6
7
|
import { CompletionFeature } from './completion.ts';
|
|
7
8
|
import { ContextMenuFeature } from './context-menu.ts';
|
|
8
9
|
import { DefinitionLinkFeature } from './definition-link.ts';
|
|
9
10
|
import { DiagnosticsFeature } from './diagnostics.ts';
|
|
11
|
+
import { FormatOnTypeFeature } from './format-on-type.ts';
|
|
10
12
|
import { HighlightsFeature } from './highlights.ts';
|
|
11
13
|
import { HistoryFeature } from './history.ts';
|
|
12
14
|
import { HoverFeature } from './hover.ts';
|
|
@@ -15,13 +17,19 @@ import { PeekFeature } from './peek.ts';
|
|
|
15
17
|
import { PickFeature } from './pick.ts';
|
|
16
18
|
import { SymbolPickerFeature } from './symbol-picker.ts';
|
|
17
19
|
import { RenameFeature } from './rename.ts';
|
|
20
|
+
import { RenderedDocsFeature } from './rendered-docs.ts';
|
|
18
21
|
import { shortcutsOf } from './shortcut-keys.ts';
|
|
19
22
|
import { type Timers } from './timers.ts';
|
|
23
|
+
import { InlayHintsFeature } from './inlay-hints.ts';
|
|
24
|
+
import { type SemanticTokensOptions } from './semantic-tokens.ts';
|
|
20
25
|
import { SignatureFeature } from './signature.ts';
|
|
21
26
|
import { SnippetFeature } from './snippet-session.ts';
|
|
22
27
|
import { SymbolsFeature } from './symbols.ts';
|
|
23
28
|
import type { Place } from './navigation-history.ts';
|
|
24
29
|
import type { LanguageDocumentHandle, ProjectLanguage } from './project-language.ts';
|
|
30
|
+
export interface EditorLanguageOptions {
|
|
31
|
+
semanticTokens?: SemanticTokensOptions;
|
|
32
|
+
}
|
|
25
33
|
export declare class EditorLanguage {
|
|
26
34
|
readonly editor: Editor;
|
|
27
35
|
readonly apple: boolean;
|
|
@@ -33,6 +41,7 @@ export declare class EditorLanguage {
|
|
|
33
41
|
readonly popups: import("./popups.ts").PopupStore;
|
|
34
42
|
readonly diagnostics: DiagnosticsFeature;
|
|
35
43
|
readonly hover: HoverFeature;
|
|
44
|
+
readonly inlayHints: InlayHintsFeature;
|
|
36
45
|
readonly completion: CompletionFeature;
|
|
37
46
|
readonly signature: SignatureFeature;
|
|
38
47
|
readonly snippets: SnippetFeature;
|
|
@@ -42,15 +51,18 @@ export declare class EditorLanguage {
|
|
|
42
51
|
readonly rename: RenameFeature;
|
|
43
52
|
readonly navigation: NavigationFeature;
|
|
44
53
|
readonly peek: PeekFeature;
|
|
54
|
+
readonly callHierarchy: CallHierarchyFeature;
|
|
45
55
|
readonly symbolPicker: SymbolPickerFeature;
|
|
46
56
|
readonly contextMenu: ContextMenuFeature;
|
|
47
57
|
readonly history: HistoryFeature;
|
|
48
58
|
readonly definitionLink: DefinitionLinkFeature;
|
|
49
59
|
readonly symbols: SymbolsFeature;
|
|
50
60
|
readonly codeVision: CodeVisionFeature;
|
|
61
|
+
readonly formatOnType: FormatOnTypeFeature;
|
|
62
|
+
readonly renderedDocs: RenderedDocsFeature;
|
|
51
63
|
private readonly disposers;
|
|
52
64
|
private disposed;
|
|
53
|
-
constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers?: Timers);
|
|
65
|
+
constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers?: Timers, options?: EditorLanguageOptions);
|
|
54
66
|
get uri(): string;
|
|
55
67
|
get isDisposed(): boolean;
|
|
56
68
|
get place(): Place;
|
package/dist/editor-language.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { KEYMAP } from '@adecore/editor/keymap';
|
|
2
|
+
import { CallHierarchyFeature } from './call-hierarchy.js';
|
|
2
3
|
import { CodeActionsFeature } from './code-actions.js';
|
|
3
4
|
import { CodeVisionFeature } from './code-vision.js';
|
|
4
5
|
import { CompletionFeature } from './completion.js';
|
|
@@ -6,6 +7,7 @@ import { ContextMenuFeature } from './context-menu.js';
|
|
|
6
7
|
import { DefinitionLinkFeature } from './definition-link.js';
|
|
7
8
|
import { DiagnosticsFeature } from './diagnostics.js';
|
|
8
9
|
import { FoldingFeature } from './folding.js';
|
|
10
|
+
import { FormatOnTypeFeature } from './format-on-type.js';
|
|
9
11
|
import { HighlightsFeature } from './highlights.js';
|
|
10
12
|
import { HistoryFeature } from './history.js';
|
|
11
13
|
import { HoverFeature } from './hover.js';
|
|
@@ -14,6 +16,7 @@ import { PeekFeature } from './peek.js';
|
|
|
14
16
|
import { PickFeature } from './pick.js';
|
|
15
17
|
import { SymbolPickerFeature } from './symbol-picker.js';
|
|
16
18
|
import { RenameFeature } from './rename.js';
|
|
19
|
+
import { RenderedDocsFeature } from './rendered-docs.js';
|
|
17
20
|
import { shortcutsOf } from './shortcut-keys.js';
|
|
18
21
|
import { createPopupStore } from './popups.js';
|
|
19
22
|
import { realTimers } from './timers.js';
|
|
@@ -39,6 +42,7 @@ export class EditorLanguage {
|
|
|
39
42
|
popups = createPopupStore();
|
|
40
43
|
diagnostics;
|
|
41
44
|
hover;
|
|
45
|
+
inlayHints;
|
|
42
46
|
completion;
|
|
43
47
|
signature;
|
|
44
48
|
snippets;
|
|
@@ -48,15 +52,18 @@ export class EditorLanguage {
|
|
|
48
52
|
rename;
|
|
49
53
|
navigation;
|
|
50
54
|
peek;
|
|
55
|
+
callHierarchy;
|
|
51
56
|
symbolPicker;
|
|
52
57
|
contextMenu;
|
|
53
58
|
history;
|
|
54
59
|
definitionLink;
|
|
55
60
|
symbols;
|
|
56
61
|
codeVision;
|
|
62
|
+
formatOnType;
|
|
63
|
+
renderedDocs;
|
|
57
64
|
disposers = [];
|
|
58
65
|
disposed = false;
|
|
59
|
-
constructor(project, editor, uri, languageId, timers = realTimers) {
|
|
66
|
+
constructor(project, editor, uri, languageId, timers = realTimers, options = {}) {
|
|
60
67
|
this.editor = editor;
|
|
61
68
|
this.apple = project.host.apple ?? false;
|
|
62
69
|
this.keymap = project.host.keymap ?? KEYMAP;
|
|
@@ -68,22 +75,25 @@ export class EditorLanguage {
|
|
|
68
75
|
this.diagnostics = new DiagnosticsFeature(this);
|
|
69
76
|
this.hover = new HoverFeature(this, timers);
|
|
70
77
|
this.completion = new CompletionFeature(this, timers);
|
|
71
|
-
this.snippets = new SnippetFeature(this);
|
|
78
|
+
this.snippets = new SnippetFeature(this, timers);
|
|
72
79
|
this.signature = new SignatureFeature(this, timers);
|
|
73
80
|
this.highlights = new HighlightsFeature(this, timers);
|
|
74
81
|
this.symbols = new SymbolsFeature(this, timers);
|
|
75
82
|
new SelectionRangesFeature(this);
|
|
76
|
-
new SemanticTokensFeature(this, timers);
|
|
77
|
-
new InlayHintsFeature(this, timers);
|
|
83
|
+
new SemanticTokensFeature(this, timers, options.semanticTokens);
|
|
84
|
+
this.inlayHints = new InlayHintsFeature(this, timers);
|
|
78
85
|
this.codeActions = new CodeActionsFeature(this, timers);
|
|
79
86
|
this.rename = new RenameFeature(this);
|
|
80
87
|
this.navigation = new NavigationFeature(this);
|
|
81
88
|
this.peek = new PeekFeature(this);
|
|
89
|
+
this.callHierarchy = new CallHierarchyFeature(this, timers);
|
|
82
90
|
this.symbolPicker = new SymbolPickerFeature(this);
|
|
83
91
|
this.contextMenu = new ContextMenuFeature(this);
|
|
84
92
|
this.history = new HistoryFeature(this);
|
|
85
93
|
this.definitionLink = new DefinitionLinkFeature(this);
|
|
86
94
|
this.codeVision = new CodeVisionFeature(this, (listener) => this.symbols.onResult(listener), timers);
|
|
95
|
+
this.formatOnType = new FormatOnTypeFeature(this);
|
|
96
|
+
this.renderedDocs = new RenderedDocsFeature(this, timers);
|
|
87
97
|
new FoldingFeature(this, (listener) => this.symbols.onResult(listener), timers);
|
|
88
98
|
this.placeOpenedCaret(editor.getCaret());
|
|
89
99
|
this.onDispose(editor.onCaret((position) => this.placeOpenedCaret(position)));
|
package/dist/editor-react.css
CHANGED
|
@@ -17,6 +17,36 @@
|
|
|
17
17
|
color: var(--accent);
|
|
18
18
|
text-decoration: underline;
|
|
19
19
|
}
|
|
20
|
+
/* Documentation drawn in place of a comment: lists and quotes that the base layer strips of their marks. */
|
|
21
|
+
.adecore-rendered-doc ul,
|
|
22
|
+
.adecore-rendered-doc ol {
|
|
23
|
+
margin: 4px 0;
|
|
24
|
+
padding-inline-start: 20px;
|
|
25
|
+
}
|
|
26
|
+
.adecore-rendered-doc ul {
|
|
27
|
+
list-style: disc;
|
|
28
|
+
}
|
|
29
|
+
.adecore-rendered-doc ol {
|
|
30
|
+
list-style: decimal;
|
|
31
|
+
}
|
|
32
|
+
.adecore-rendered-doc blockquote {
|
|
33
|
+
margin: 4px 0;
|
|
34
|
+
padding-inline-start: 8px;
|
|
35
|
+
border-inline-start: 2px solid var(--border);
|
|
36
|
+
}
|
|
37
|
+
.adecore-rendered-doc :not(pre) > code {
|
|
38
|
+
font-family: var(--font-mono);
|
|
39
|
+
}
|
|
40
|
+
.adecore-rendered-doc h1,
|
|
41
|
+
.adecore-rendered-doc h2,
|
|
42
|
+
.adecore-rendered-doc h3,
|
|
43
|
+
.adecore-rendered-doc h4,
|
|
44
|
+
.adecore-rendered-doc h5,
|
|
45
|
+
.adecore-rendered-doc h6 {
|
|
46
|
+
margin: 4px 0;
|
|
47
|
+
font-size: inherit;
|
|
48
|
+
font-weight: 600;
|
|
49
|
+
}
|
|
20
50
|
.adecore-editor-find {
|
|
21
51
|
display: flex;
|
|
22
52
|
flex-direction: column;
|
|
@@ -41,3 +71,52 @@
|
|
|
41
71
|
.adecore-editor-find output {
|
|
42
72
|
font-variant-numeric: tabular-nums;
|
|
43
73
|
}
|
|
74
|
+
|
|
75
|
+
.adecore-editor-diff-gap {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
min-height: 32px;
|
|
79
|
+
padding: 4px 8px;
|
|
80
|
+
font-family: var(--font-sans);
|
|
81
|
+
color: var(--text-muted);
|
|
82
|
+
background: var(--surface-raised);
|
|
83
|
+
}
|
|
84
|
+
.adecore-editor-diff-no-newline {
|
|
85
|
+
padding-inline: 8px;
|
|
86
|
+
color: var(--text-muted);
|
|
87
|
+
font-family: var(--font-sans);
|
|
88
|
+
font-size: var(--text-sm);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.adecore-editor-diff .se-viewport {
|
|
92
|
+
overscroll-behavior-y: auto;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.adecore-editor-diff[data-scroll='false'] .se-viewport {
|
|
96
|
+
overflow-y: hidden;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.adecore-editor-diff-split {
|
|
100
|
+
display: grid;
|
|
101
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
102
|
+
min-height: 0;
|
|
103
|
+
}
|
|
104
|
+
.adecore-editor-diff-side {
|
|
105
|
+
position: relative;
|
|
106
|
+
min-width: 0;
|
|
107
|
+
min-height: 0;
|
|
108
|
+
}
|
|
109
|
+
.adecore-editor-diff-side + .adecore-editor-diff-side {
|
|
110
|
+
border-inline-start: 1px solid var(--border);
|
|
111
|
+
}
|
|
112
|
+
.adecore-editor-diff-surface {
|
|
113
|
+
position: absolute;
|
|
114
|
+
inset: 0;
|
|
115
|
+
}
|
|
116
|
+
.adecore-editor-diff-split .se-viewport {
|
|
117
|
+
scrollbar-gutter: stable;
|
|
118
|
+
}
|
|
119
|
+
.adecore-editor-diff-filler {
|
|
120
|
+
background: repeating-linear-gradient(135deg, transparent 0 6px, var(--editor-hatch) 6px 7px);
|
|
121
|
+
pointer-events: none;
|
|
122
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
2
|
+
export declare class FormatOnTypeFeature {
|
|
3
|
+
private readonly language;
|
|
4
|
+
private enabled;
|
|
5
|
+
private revision;
|
|
6
|
+
private controller;
|
|
7
|
+
constructor(language: EditorLanguage);
|
|
8
|
+
get isEnabled(): boolean;
|
|
9
|
+
get supported(): boolean;
|
|
10
|
+
configure(enabled: boolean): void;
|
|
11
|
+
triggerCharacters(): readonly string[];
|
|
12
|
+
private typed;
|
|
13
|
+
private ask;
|
|
14
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { comparePositions } from './diagnostics-model.js';
|
|
2
|
+
const METHOD = 'textDocument/onTypeFormatting';
|
|
3
|
+
/* A line break with the indentation the editor put after it, which is how Enter reaches the server. */
|
|
4
|
+
const LINE_BREAK = /^\r?\n[ \t]*(\r?\n[ \t]*)?$/;
|
|
5
|
+
/* A closer with the whitespace the editor replaced in front of it when it moved the closer back to its opener's indentation. */
|
|
6
|
+
const DEDENTED = /^[ \t]+(\S)$/u;
|
|
7
|
+
/* The overlapping edits of a server answer have no single meaning, so an answer that has them is not applied. */
|
|
8
|
+
function overlaps(edits) {
|
|
9
|
+
const ordered = [...edits].sort((left, right) => comparePositions(left.range.start, right.range.start));
|
|
10
|
+
return ordered.some((edit, index) => index > 0 && comparePositions(ordered[index - 1].range.end, edit.range.start) > 0);
|
|
11
|
+
}
|
|
12
|
+
/*
|
|
13
|
+
* Formatting while typing: after a person types one of the characters the server named in
|
|
14
|
+
* `firstTriggerCharacter` and `moreTriggerCharacter`, the server is asked what to change around the caret, and its
|
|
15
|
+
* edits are applied.
|
|
16
|
+
*
|
|
17
|
+
* Only typing counts: one change that is the trigger character alone, the closer the editor moved back to its
|
|
18
|
+
* opener's indentation, or a line break with its indentation for `\n`, which the editor reports as typed though
|
|
19
|
+
* Enter runs as a command. A character the editor types over an existing closer changes no text and asks nothing. A paste, undo, a completion or an external change does not ask, and neither does a
|
|
20
|
+
* second caret, a read-only editor or a snippet session, whose tab stops an edit could move. An answer is applied
|
|
21
|
+
* only while the text is exactly what it was asked about, and the caret where it was.
|
|
22
|
+
*
|
|
23
|
+
* The edits join the undo step of the typed character, so one undo takes both back. On by default where the server offers it; `configure(false)`
|
|
24
|
+
* turns it off for a host that has a setting for it.
|
|
25
|
+
*/
|
|
26
|
+
export class FormatOnTypeFeature {
|
|
27
|
+
language;
|
|
28
|
+
enabled = true;
|
|
29
|
+
/* Raised by every change of the text, so an answer for an older text is told from the one for the text that is. */
|
|
30
|
+
revision = 0;
|
|
31
|
+
controller = new AbortController();
|
|
32
|
+
constructor(language) {
|
|
33
|
+
this.language = language;
|
|
34
|
+
const off = language.editor.onTextChange((change) => this.typed(change));
|
|
35
|
+
language.onDispose(() => {
|
|
36
|
+
off();
|
|
37
|
+
this.controller.abort();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
get isEnabled() {
|
|
41
|
+
return this.enabled;
|
|
42
|
+
}
|
|
43
|
+
/* Whether the server of this document formats on type and the service can ask it. */
|
|
44
|
+
get supported() {
|
|
45
|
+
const { project, uri } = this.language;
|
|
46
|
+
return project.service.onTypeFormatting !== undefined && project.service.supports(METHOD, uri);
|
|
47
|
+
}
|
|
48
|
+
configure(enabled) {
|
|
49
|
+
this.enabled = enabled;
|
|
50
|
+
if (!enabled) {
|
|
51
|
+
this.controller.abort();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/* The characters that ask the server, the first one and the more. */
|
|
55
|
+
triggerCharacters() {
|
|
56
|
+
const options = this.language.project.service.providerOptions(METHOD, this.language.uri);
|
|
57
|
+
return options === undefined
|
|
58
|
+
? []
|
|
59
|
+
: [...(options.firstTriggerCharacter === undefined ? [] : [options.firstTriggerCharacter]), ...(options.moreTriggerCharacter ?? [])];
|
|
60
|
+
}
|
|
61
|
+
typed(change) {
|
|
62
|
+
const revision = ++this.revision;
|
|
63
|
+
this.controller.abort();
|
|
64
|
+
if (!this.enabled || (change.source !== 'input' && change.typed !== true) || change.changes.length !== 1 || !this.supported) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const { editor, snippets } = this.language;
|
|
68
|
+
const { text } = change.changes[0];
|
|
69
|
+
const typed = LINE_BREAK.test(text) ? '\n' : Array.from(text).length === 1 ? text : (DEDENTED.exec(text)?.[1] ?? null);
|
|
70
|
+
if (typed === null || !this.triggerCharacters().includes(typed)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (snippets.isActive || editor.isReadOnly?.() === true || editor.getSelections().length !== 1) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.controller = new AbortController();
|
|
77
|
+
void this.ask(revision, typed, editor.getCaret(), this.controller.signal);
|
|
78
|
+
}
|
|
79
|
+
async ask(revision, typed, caret, signal) {
|
|
80
|
+
const { editor, project, uri, document } = this.language;
|
|
81
|
+
try {
|
|
82
|
+
// The change of the typed character reaches the server before this question does.
|
|
83
|
+
await document.ready;
|
|
84
|
+
const edits = await project.service.onTypeFormatting(uri, caret, typed, { ...editor.getIndentation() }, { signal });
|
|
85
|
+
const here = editor.getCaret();
|
|
86
|
+
const stale = revision !== this.revision || this.language.isDisposed || comparePositions(here, caret) !== 0;
|
|
87
|
+
if (stale || edits === null || edits.length === 0 || overlaps(edits)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
editor.applyEdits(edits.map((edit) => ({ range: edit.range, text: edit.newText })), { mergeUndo: true });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// A refusal or a failing server is not worth a notice per keystroke; the next trigger character asks again.
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|