@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,67 @@
|
|
|
1
|
+
import type { LineToken } from '@adecore/editor';
|
|
2
|
+
import type { DiffSyntaxDocument } from '@adecore/editor/shiki-worker';
|
|
3
|
+
import type { DiffDocument } from '@adecore/merge';
|
|
4
|
+
|
|
5
|
+
export type DiffSourceSide = 'old' | 'new';
|
|
6
|
+
|
|
7
|
+
export interface DiffSourceTokens {
|
|
8
|
+
/* Keep the same array while this source line is unchanged. Undefined means pending; null means plain. */
|
|
9
|
+
get(side: DiffSourceSide, index: number): readonly LineToken[] | null | undefined;
|
|
10
|
+
subscribe(listener: () => void): () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DiffSyntaxSources {
|
|
14
|
+
readonly documents: Readonly<Record<DiffSourceSide, DiffSyntaxDocument>>;
|
|
15
|
+
locate(side: DiffSourceSide, index: number): { readonly block: number; readonly line: number } | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function canTokenize(source: readonly string[]): boolean {
|
|
19
|
+
if (source.length > 20000) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
let characters = Math.max(0, source.length - 1);
|
|
23
|
+
for (const line of source) {
|
|
24
|
+
characters += line.length;
|
|
25
|
+
if (line.length > 10000 || characters > 2000000) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function diffSyntaxDocument(document: DiffDocument, language: string, theme: string): DiffSyntaxSources {
|
|
33
|
+
const locations: Record<DiffSourceSide, Array<{ block: number; line: number }>> = { old: [], new: [] };
|
|
34
|
+
const build = (side: DiffSourceSide): DiffSyntaxDocument => {
|
|
35
|
+
const source = document.sides[side];
|
|
36
|
+
// Skipping a long hidden line loses grammar state; keep the entire side plain instead.
|
|
37
|
+
if (!canTokenize(source)) {
|
|
38
|
+
return { language, theme, blocks: [], resets: [] };
|
|
39
|
+
}
|
|
40
|
+
const blocks: string[] = [];
|
|
41
|
+
const resets: number[] = [];
|
|
42
|
+
const ranges = document.snapshots
|
|
43
|
+
? [{ from: 0, to: source.length }]
|
|
44
|
+
: document.offsets.map((offset, index) => ({ from: offset[side], to: document.offsets[index + 1]?.[side] ?? source.length }));
|
|
45
|
+
for (const range of ranges) {
|
|
46
|
+
if (!document.snapshots && range.from < range.to) {
|
|
47
|
+
resets.push(blocks.length);
|
|
48
|
+
}
|
|
49
|
+
for (let from = range.from; from < range.to; from += 200) {
|
|
50
|
+
const to = Math.min(from + 200, range.to);
|
|
51
|
+
const block = blocks.length;
|
|
52
|
+
blocks.push(source.slice(from, to).join('\n'));
|
|
53
|
+
for (let index = from; index < to; index++) {
|
|
54
|
+
locations[side][index] = { block, line: index - from };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { language, theme, blocks, resets };
|
|
59
|
+
};
|
|
60
|
+
const documents = { old: build('old'), new: build('new') };
|
|
61
|
+
return {
|
|
62
|
+
documents,
|
|
63
|
+
locate(side, index) {
|
|
64
|
+
return Number.isInteger(index) && index >= 0 ? locations[side][index] : undefined;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { diffRows, wordChanges, type DiffDocument, type DiffWordRange } from '@adecore/merge';
|
|
2
|
+
|
|
3
|
+
export function wordMarks(document: DiffDocument): {
|
|
4
|
+
old: Map<number, readonly (readonly [number, number])[]>;
|
|
5
|
+
new: Map<number, readonly (readonly [number, number])[]>;
|
|
6
|
+
} {
|
|
7
|
+
const marks = { old: new Map<number, readonly (readonly [number, number])[]>(), new: new Map<number, readonly (readonly [number, number])[]>() };
|
|
8
|
+
let pairs = 0;
|
|
9
|
+
let characters = 0;
|
|
10
|
+
for (const row of diffRows(document, 'split')) {
|
|
11
|
+
if (row.kind !== 'line' || row.change !== 'pair' || document.hunks[row.hunk!]!.coarse) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const old = document.sides.old[row.old!.index]!;
|
|
15
|
+
const fresh = document.sides.new[row.new!.index]!;
|
|
16
|
+
characters += old.length + fresh.length;
|
|
17
|
+
// Per-pair limits alone still allow thousands of expensive word diffs during one mount.
|
|
18
|
+
if (++pairs > 1000 || characters > 100000) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const words = wordChanges(old, fresh);
|
|
22
|
+
if (words === null) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const ranges = (text: string, ranges: readonly DiffWordRange[]): Array<readonly [number, number]> =>
|
|
26
|
+
ranges.filter((range) => !document.ignoreWhitespace || /\S/.test(text.slice(range.from, range.to))).map((range) => [range.from, range.to]);
|
|
27
|
+
marks.old.set(row.old!.index, ranges(old, words.old));
|
|
28
|
+
marks.new.set(row.new!.index, ranges(fresh, words.new));
|
|
29
|
+
}
|
|
30
|
+
return marks;
|
|
31
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
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
|
+
|
|
7
|
+
export interface DocTagEntry {
|
|
8
|
+
/* Canonical: `returns` is `return`, `exception` is `throws`, `arg` and `argument` are `param`. */
|
|
9
|
+
readonly name: string;
|
|
10
|
+
/* The type in the tag, as written, without braces. */
|
|
11
|
+
readonly type: string | null;
|
|
12
|
+
/* The parameter, variable or class the tag is about. */
|
|
13
|
+
readonly subject: string | null;
|
|
14
|
+
/* Markdown. For `example` the code itself. */
|
|
15
|
+
readonly text: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ParsedDocComment {
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly tags: readonly DocTagEntry[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* The prefix of an address that stands for a name a `{@link}` points at, which the view resolves instead of following. */
|
|
24
|
+
export const DOC_LINK_PREFIX = '#doc-link:';
|
|
25
|
+
|
|
26
|
+
const ALIASES: Readonly<Record<string, string>> = {
|
|
27
|
+
returns: 'return',
|
|
28
|
+
exception: 'throws',
|
|
29
|
+
arg: 'param',
|
|
30
|
+
argument: 'param',
|
|
31
|
+
prop: 'property',
|
|
32
|
+
yields: 'yield',
|
|
33
|
+
link: 'see'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/* Tags that carry a type before their subject. */
|
|
37
|
+
const TYPED = new Set(['param', 'property', 'return', 'throws', 'var', 'type', 'yield', 'template', 'this']);
|
|
38
|
+
/* Tags whose first word is the thing they are about. */
|
|
39
|
+
const SUBJECT_FIRST = new Set(['param', 'property', 'var', 'template']);
|
|
40
|
+
|
|
41
|
+
const TAG_START = /^@([A-Za-z][\w-]*)(?::[\w-]+)?[ \t]*(.*)$/;
|
|
42
|
+
const FENCE = /^\s*(```|~~~)/;
|
|
43
|
+
const DASH = /^[—–-][ \t]+/;
|
|
44
|
+
const DANGEROUS =
|
|
45
|
+
/<(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;
|
|
46
|
+
const SAFE_SCHEME = /^(https?:|mailto:)/i;
|
|
47
|
+
|
|
48
|
+
/* Reads a type that starts the text: in braces as JSDoc writes it, or as one word with its brackets as PHPDoc does. */
|
|
49
|
+
function takeType(text: string, braces: boolean): { type: string | null; rest: string } {
|
|
50
|
+
const trimmed = text.trimStart();
|
|
51
|
+
if (trimmed.startsWith('{')) {
|
|
52
|
+
let depth = 0;
|
|
53
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
54
|
+
depth += trimmed[i] === '{' ? 1 : trimmed[i] === '}' ? -1 : 0;
|
|
55
|
+
if (depth === 0) {
|
|
56
|
+
return { type: trimmed.slice(1, i).trim(), rest: trimmed.slice(i + 1).trimStart() };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { type: null, rest: trimmed };
|
|
60
|
+
}
|
|
61
|
+
if (braces || /^[$&.]/.test(trimmed)) {
|
|
62
|
+
return { type: null, rest: trimmed };
|
|
63
|
+
}
|
|
64
|
+
let depth = 0;
|
|
65
|
+
let end = 0;
|
|
66
|
+
for (; end < trimmed.length; end++) {
|
|
67
|
+
const character = trimmed[end]!;
|
|
68
|
+
if ('<([{'.includes(character)) {
|
|
69
|
+
depth++;
|
|
70
|
+
} else if ('>)]}'.includes(character)) {
|
|
71
|
+
depth--;
|
|
72
|
+
} else if (/\s/.test(character) && depth <= 0) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { type: end === 0 ? null : trimmed.slice(0, end), rest: trimmed.slice(end).trimStart() };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* The first word of a tag's text: a name with its sigils and the `[name=default]` form of JSDoc. */
|
|
80
|
+
function takeSubject(text: string): { subject: string | null; rest: string } {
|
|
81
|
+
const optional = /^\[([^\]]+)\]\s*/.exec(text);
|
|
82
|
+
if (optional !== null) {
|
|
83
|
+
return { subject: optional[1]!, rest: text.slice(optional[0].length) };
|
|
84
|
+
}
|
|
85
|
+
const word = /^(?:\.\.\.|&)?[$\w.\\:-]+/.exec(text);
|
|
86
|
+
if (word === null) {
|
|
87
|
+
return { subject: null, rest: text };
|
|
88
|
+
}
|
|
89
|
+
return { subject: word[0], rest: text.slice(word[0].length).trimStart() };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* A comment's lines cut where a tag starts at the beginning of a line, outside a fenced block. */
|
|
93
|
+
function splitTags(body: string): { description: string; raw: { name: string; text: string }[] } {
|
|
94
|
+
const description: string[] = [];
|
|
95
|
+
const raw: { name: string; text: string }[] = [];
|
|
96
|
+
let fenced = false;
|
|
97
|
+
for (const line of body.split('\n')) {
|
|
98
|
+
if (FENCE.test(line)) {
|
|
99
|
+
fenced = !fenced;
|
|
100
|
+
}
|
|
101
|
+
const start = fenced ? null : TAG_START.exec(line.trimStart());
|
|
102
|
+
if (start !== null) {
|
|
103
|
+
raw.push({ name: start[1]!.toLowerCase(), text: start[2]! });
|
|
104
|
+
} else if (raw.length > 0) {
|
|
105
|
+
raw[raw.length - 1]!.text += `\n${line}`;
|
|
106
|
+
} else {
|
|
107
|
+
description.push(line);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { description: description.join('\n').trim(), raw };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function entryOf(name: string, text: string, php: boolean): DocTagEntry {
|
|
114
|
+
let rest = text.trim();
|
|
115
|
+
let type: string | null = null;
|
|
116
|
+
let subject: string | null = null;
|
|
117
|
+
if (TYPED.has(name)) {
|
|
118
|
+
const taken = name === 'return' && rest.startsWith('$this') ? { type: '$this', rest: rest.slice(5).trimStart() } : takeType(rest, !php);
|
|
119
|
+
type = taken.type;
|
|
120
|
+
rest = taken.rest;
|
|
121
|
+
// A type that stands alone is the whole text of a tag that has no subject.
|
|
122
|
+
if (SUBJECT_FIRST.has(name) || (name === 'throws' && type === null)) {
|
|
123
|
+
const word = takeSubject(rest);
|
|
124
|
+
subject = word.subject;
|
|
125
|
+
rest = word.rest;
|
|
126
|
+
}
|
|
127
|
+
} else if (name === 'see' && !rest.startsWith('{')) {
|
|
128
|
+
const target = /^\S+/.exec(rest);
|
|
129
|
+
subject = target === null ? null : target[0];
|
|
130
|
+
rest = target === null ? rest : rest.slice(target[0].length).trimStart();
|
|
131
|
+
}
|
|
132
|
+
if (name === 'throws' && type === null && subject !== null) {
|
|
133
|
+
type = subject;
|
|
134
|
+
subject = null;
|
|
135
|
+
}
|
|
136
|
+
return { name, type, subject, text: rest.replace(DASH, '').trim() };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Text outside backtick code, which is the only text HTML and inline tags are read in. */
|
|
140
|
+
function outsideCode(markdown: string, convert: (text: string) => string): string {
|
|
141
|
+
return markdown
|
|
142
|
+
.split(/(```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]*`)/)
|
|
143
|
+
.map((part, index) => (index % 2 === 1 ? part : convert(part)))
|
|
144
|
+
.join('');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ENTITIES: Readonly<Record<string, string>> = { lt: '<', gt: '>', amp: '&', quot: '"', apos: "'", nbsp: ' ', ndash: '-', mdash: '-', hellip: '...' };
|
|
148
|
+
|
|
149
|
+
/* Named entities of the small set docblocks use, and numeric ones; anything else stays as written. */
|
|
150
|
+
export function decodeEntities(text: string): string {
|
|
151
|
+
return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (match, code: string) => {
|
|
152
|
+
if (code[0] === '#') {
|
|
153
|
+
const point = code[1]!.toLowerCase() === 'x' ? Number.parseInt(code.slice(2), 16) : Number.parseInt(code.slice(1), 10);
|
|
154
|
+
return Number.isFinite(point) && point > 0 && point <= 0x10ffff ? String.fromCodePoint(point) : match;
|
|
155
|
+
}
|
|
156
|
+
return ENTITIES[code.toLowerCase()] ?? match;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function dedent(text: string): string {
|
|
161
|
+
const lines = text.replace(/^\n+|\s+$/g, '').split('\n');
|
|
162
|
+
const indents = lines.filter((line) => line.trim() !== '').map((line) => line.length - line.trimStart().length);
|
|
163
|
+
const common = indents.length === 0 ? 0 : Math.min(...indents);
|
|
164
|
+
return lines.map((line) => line.slice(common)).join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function fenced(body: string): string {
|
|
168
|
+
return `\n\n\`\`\`\n${dedent(decodeEntities(stripTags(body)))}\n\`\`\`\n\n`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function stripTags(html: string): string {
|
|
172
|
+
return html.replace(/<\/?[a-zA-Z][^>]*>/g, '');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* The address of a link when it is one a reader may follow; null for everything else, such as a script address. */
|
|
176
|
+
export function safeAddress(address: string): string | null {
|
|
177
|
+
const trimmed = decodeEntities(address.trim());
|
|
178
|
+
return SAFE_SCHEME.test(trimmed) || trimmed.startsWith(DOC_LINK_PREFIX) ? trimmed.replace(/\s/g, '%20').replace(/\(/g, '%28').replace(/\)/g, '%29') : null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/*
|
|
182
|
+
* The HTML subset docblocks use, as Markdown: paragraphs and breaks, bold, italic, code, preformatted
|
|
183
|
+
* blocks, links to http, https and mailto, lists, headings and entities. Dangerous elements go with their
|
|
184
|
+
* content, attributes are never copied except a safe `href`, and a tag that is not in the subset is left
|
|
185
|
+
* as the text it was, which the view draws escaped.
|
|
186
|
+
*/
|
|
187
|
+
export function htmlToMarkdown(source: string): string {
|
|
188
|
+
if (!source.includes('<') && !source.includes('&')) {
|
|
189
|
+
return source;
|
|
190
|
+
}
|
|
191
|
+
const lists: ('ul' | 'ol')[] = [];
|
|
192
|
+
const counters: number[] = [];
|
|
193
|
+
let anchor: string | null = null;
|
|
194
|
+
const blocks = source
|
|
195
|
+
.replace(/<pre\b[^>]*>([\s\S]*?)<\/pre\s*>/gi, (_match, body: string) => fenced(body))
|
|
196
|
+
.replace(/<code>([^<]*\n[^<]*)<\/code>/gi, (_match, body: string) => fenced(body));
|
|
197
|
+
const converted = outsideCode(blocks, (text) =>
|
|
198
|
+
text
|
|
199
|
+
.replace(DANGEROUS, '')
|
|
200
|
+
.replace(
|
|
201
|
+
/<(\/?)(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,
|
|
202
|
+
(_match, close: string, tag: string, attributes: string) => {
|
|
203
|
+
const closing = close === '/';
|
|
204
|
+
switch (tag.toLowerCase()) {
|
|
205
|
+
case 'p':
|
|
206
|
+
case 'div':
|
|
207
|
+
case 'dl':
|
|
208
|
+
return '\n\n';
|
|
209
|
+
case 'br':
|
|
210
|
+
return '\n';
|
|
211
|
+
case 'hr':
|
|
212
|
+
return '\n\n---\n\n';
|
|
213
|
+
case 'b':
|
|
214
|
+
case 'strong':
|
|
215
|
+
return '**';
|
|
216
|
+
case 'i':
|
|
217
|
+
case 'em':
|
|
218
|
+
case 'var':
|
|
219
|
+
return '*';
|
|
220
|
+
case 'code':
|
|
221
|
+
case 'tt':
|
|
222
|
+
case 'kbd':
|
|
223
|
+
case 'samp':
|
|
224
|
+
return '`';
|
|
225
|
+
case 'a': {
|
|
226
|
+
if (closing) {
|
|
227
|
+
const target = anchor;
|
|
228
|
+
anchor = null;
|
|
229
|
+
return target === null ? '' : `](${target})`;
|
|
230
|
+
}
|
|
231
|
+
const href = /\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(attributes);
|
|
232
|
+
anchor = href === null ? null : safeAddress(href[1] ?? href[2] ?? href[3] ?? '');
|
|
233
|
+
return anchor === null ? '' : '[';
|
|
234
|
+
}
|
|
235
|
+
case 'ul':
|
|
236
|
+
case 'ol':
|
|
237
|
+
if (closing) {
|
|
238
|
+
lists.pop();
|
|
239
|
+
counters.pop();
|
|
240
|
+
} else {
|
|
241
|
+
lists.push(tag.toLowerCase() as 'ul' | 'ol');
|
|
242
|
+
counters.push(0);
|
|
243
|
+
}
|
|
244
|
+
return '\n';
|
|
245
|
+
case 'li': {
|
|
246
|
+
if (closing) {
|
|
247
|
+
return '';
|
|
248
|
+
}
|
|
249
|
+
const depth = Math.max(0, lists.length - 1);
|
|
250
|
+
const marker = lists.at(-1) === 'ol' ? `${(counters[depth] = (counters[depth] ?? 0) + 1)}.` : '-';
|
|
251
|
+
return `\n${' '.repeat(depth)}${marker} `;
|
|
252
|
+
}
|
|
253
|
+
case 'blockquote':
|
|
254
|
+
return closing ? '\n\n' : '\n\n> ';
|
|
255
|
+
case 'dt':
|
|
256
|
+
return closing ? '**\n\n' : '\n\n**';
|
|
257
|
+
case 'dd':
|
|
258
|
+
return '\n\n';
|
|
259
|
+
default:
|
|
260
|
+
if (/^h[1-6]$/i.test(tag)) {
|
|
261
|
+
return closing ? '\n\n' : `\n\n${'#'.repeat(Number(tag[1]))} `;
|
|
262
|
+
}
|
|
263
|
+
return '';
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
);
|
|
268
|
+
// Entities last, so `<b>` stays the text it spells and is not read as a tag above.
|
|
269
|
+
return outsideCode(converted, decodeEntities).replace(/\n{3,}/g, '\n\n');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/*
|
|
273
|
+
* Inline `{@link Name}`, `{@link Name label}`, `{@link Name|label}`, the `linkcode` and `linkplain` forms, `{@see Name}`
|
|
274
|
+
* and `{@code text}`. A name becomes a link the view resolves; an address becomes a link to it.
|
|
275
|
+
*/
|
|
276
|
+
export function inlineTagsToMarkdown(markdown: string): string {
|
|
277
|
+
return outsideCode(markdown, (text) =>
|
|
278
|
+
text
|
|
279
|
+
.replace(/\{@(?:link|linkcode|linkplain|see)\s+([^}\s|]+)(?:\s*[|\s]\s*([^}]+?))?\s*\}/g, (_match, target: string, label: string | undefined) => {
|
|
280
|
+
const address = /^https?:\/\//i.test(target) ? safeAddress(target) : `${DOC_LINK_PREFIX}${encodeURIComponent(target)}`;
|
|
281
|
+
const shown = label ?? target;
|
|
282
|
+
return address === null ? `\`${shown}\`` : `[${label === undefined ? `\`${shown}\`` : shown}](${address})`;
|
|
283
|
+
})
|
|
284
|
+
.replace(/\{@code\s+([^}]*)\}/g, (_match, code: string) => `\`${code.trim()}\``)
|
|
285
|
+
.replace(/\{@inheritDoc\}/g, '')
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* 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. */
|
|
290
|
+
function exampleOf(text: string): string {
|
|
291
|
+
const code = text.replace(/^\s*<caption>[\s\S]*?<\/caption>[ \t]*\n?/i, '');
|
|
292
|
+
const [head = '', ...tail] = code.split('\n');
|
|
293
|
+
return head.trim() === '' ? dedent(code) : [head.trim(), dedent(tail.join('\n'))].join('\n').trim();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function proseOf(text: string): string {
|
|
297
|
+
return inlineTagsToMarkdown(htmlToMarkdown(text)).trim();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Reads the body of a documentation comment, as `findDocComments` returns it. `php` lets a type stand without braces. */
|
|
301
|
+
export function parseDocComment(body: string, options: { php?: boolean } = {}): ParsedDocComment {
|
|
302
|
+
const php = options.php === true;
|
|
303
|
+
const { description, raw } = splitTags(body);
|
|
304
|
+
const tags = raw.map(({ name, text }) => {
|
|
305
|
+
const bare = name.replace(/^(?:psalm|phpstan)-/, '');
|
|
306
|
+
const canonical = ALIASES[bare] ?? bare;
|
|
307
|
+
const entry = entryOf(canonical, text, php);
|
|
308
|
+
// An example is code: it keeps its lines and gets no prose treatment, only its caption removed.
|
|
309
|
+
if (canonical === 'example') {
|
|
310
|
+
return { ...entry, text: exampleOf(text) };
|
|
311
|
+
}
|
|
312
|
+
return { ...entry, text: proseOf(entry.text) };
|
|
313
|
+
});
|
|
314
|
+
return { description: proseOf(description), tags };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Tags in the order a reader looks for them; every other tag follows in the order it was written. */
|
|
318
|
+
const ORDER = ['deprecated', 'template', 'param', 'property', 'type', 'var', 'return', 'yield', 'throws', 'see', 'example', 'since', 'version', 'author'];
|
|
319
|
+
|
|
320
|
+
export interface DocTagSection {
|
|
321
|
+
readonly name: string;
|
|
322
|
+
readonly tags: readonly DocTagEntry[];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* The tags gathered by name, so a list of parameters is one section, in reading order. */
|
|
326
|
+
export function sectionsOf(tags: readonly DocTagEntry[]): DocTagSection[] {
|
|
327
|
+
const byName = new Map<string, DocTagEntry[]>();
|
|
328
|
+
for (const tag of tags) {
|
|
329
|
+
byName.set(tag.name, [...(byName.get(tag.name) ?? []), tag]);
|
|
330
|
+
}
|
|
331
|
+
const rank = (name: string): number => {
|
|
332
|
+
const index = ORDER.indexOf(name);
|
|
333
|
+
return index === -1 ? ORDER.length : index;
|
|
334
|
+
};
|
|
335
|
+
return [...byName].map(([name, entries]) => ({ name, tags: entries })).sort((left, right) => rank(left.name) - rank(right.name));
|
|
336
|
+
}
|
package/src/editor-language.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KEYMAP, 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';
|
|
@@ -8,6 +9,7 @@ import { ContextMenuFeature } from './context-menu.ts';
|
|
|
8
9
|
import { DefinitionLinkFeature } from './definition-link.ts';
|
|
9
10
|
import { DiagnosticsFeature } from './diagnostics.ts';
|
|
10
11
|
import { FoldingFeature } from './folding.ts';
|
|
12
|
+
import { FormatOnTypeFeature } from './format-on-type.ts';
|
|
11
13
|
import { HighlightsFeature } from './highlights.ts';
|
|
12
14
|
import { HistoryFeature } from './history.ts';
|
|
13
15
|
import { HoverFeature } from './hover.ts';
|
|
@@ -16,18 +18,25 @@ import { PeekFeature } from './peek.ts';
|
|
|
16
18
|
import { PickFeature } from './pick.ts';
|
|
17
19
|
import { SymbolPickerFeature } from './symbol-picker.ts';
|
|
18
20
|
import { RenameFeature } from './rename.ts';
|
|
21
|
+
import { RenderedDocsFeature } from './rendered-docs.ts';
|
|
19
22
|
import { shortcutsOf } from './shortcut-keys.ts';
|
|
20
23
|
import { createPopupStore } from './popups.ts';
|
|
21
24
|
import { realTimers, type Timers } from './timers.ts';
|
|
22
25
|
import { InlayHintsFeature } from './inlay-hints.ts';
|
|
23
26
|
import { SelectionRangesFeature } from './selection-ranges.ts';
|
|
24
|
-
import { SemanticTokensFeature } from './semantic-tokens.ts';
|
|
27
|
+
import { SemanticTokensFeature, type SemanticTokensOptions } from './semantic-tokens.ts';
|
|
25
28
|
import { SignatureFeature } from './signature.ts';
|
|
26
29
|
import { SnippetFeature } from './snippet-session.ts';
|
|
27
30
|
import { SymbolsFeature } from './symbols.ts';
|
|
28
31
|
import type { Place } from './navigation-history.ts';
|
|
29
32
|
import type { LanguageDocumentHandle, ProjectLanguage } from './project-language.ts';
|
|
30
33
|
|
|
34
|
+
/* What an app tunes about the features of one editor. */
|
|
35
|
+
export interface EditorLanguageOptions {
|
|
36
|
+
/* Where a document counts as large for semantic tokens. */
|
|
37
|
+
semanticTokens?: SemanticTokensOptions;
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
/*
|
|
32
41
|
* The one place that connects an editor to the language servers: it opens the file's document for
|
|
33
42
|
* the editor and lets go of it again. Every language feature of the editor hangs off this object.
|
|
@@ -44,6 +53,7 @@ export class EditorLanguage {
|
|
|
44
53
|
readonly popups = createPopupStore();
|
|
45
54
|
readonly diagnostics: DiagnosticsFeature;
|
|
46
55
|
readonly hover: HoverFeature;
|
|
56
|
+
readonly inlayHints: InlayHintsFeature;
|
|
47
57
|
readonly completion: CompletionFeature;
|
|
48
58
|
readonly signature: SignatureFeature;
|
|
49
59
|
readonly snippets: SnippetFeature;
|
|
@@ -53,16 +63,19 @@ export class EditorLanguage {
|
|
|
53
63
|
readonly rename: RenameFeature;
|
|
54
64
|
readonly navigation: NavigationFeature;
|
|
55
65
|
readonly peek: PeekFeature;
|
|
66
|
+
readonly callHierarchy: CallHierarchyFeature;
|
|
56
67
|
readonly symbolPicker: SymbolPickerFeature;
|
|
57
68
|
readonly contextMenu: ContextMenuFeature;
|
|
58
69
|
readonly history: HistoryFeature;
|
|
59
70
|
readonly definitionLink: DefinitionLinkFeature;
|
|
60
71
|
readonly symbols: SymbolsFeature;
|
|
61
72
|
readonly codeVision: CodeVisionFeature;
|
|
73
|
+
readonly formatOnType: FormatOnTypeFeature;
|
|
74
|
+
readonly renderedDocs: RenderedDocsFeature;
|
|
62
75
|
private readonly disposers: Array<() => void> = [];
|
|
63
76
|
private disposed = false;
|
|
64
77
|
|
|
65
|
-
constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers: Timers = realTimers) {
|
|
78
|
+
constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers: Timers = realTimers, options: EditorLanguageOptions = {}) {
|
|
66
79
|
this.editor = editor;
|
|
67
80
|
this.apple = project.host.apple ?? false;
|
|
68
81
|
this.keymap = project.host.keymap ?? KEYMAP;
|
|
@@ -74,22 +87,25 @@ export class EditorLanguage {
|
|
|
74
87
|
this.diagnostics = new DiagnosticsFeature(this);
|
|
75
88
|
this.hover = new HoverFeature(this, timers);
|
|
76
89
|
this.completion = new CompletionFeature(this, timers);
|
|
77
|
-
this.snippets = new SnippetFeature(this);
|
|
90
|
+
this.snippets = new SnippetFeature(this, timers);
|
|
78
91
|
this.signature = new SignatureFeature(this, timers);
|
|
79
92
|
this.highlights = new HighlightsFeature(this, timers);
|
|
80
93
|
this.symbols = new SymbolsFeature(this, timers);
|
|
81
94
|
new SelectionRangesFeature(this);
|
|
82
|
-
new SemanticTokensFeature(this, timers);
|
|
83
|
-
new InlayHintsFeature(this, timers);
|
|
95
|
+
new SemanticTokensFeature(this, timers, options.semanticTokens);
|
|
96
|
+
this.inlayHints = new InlayHintsFeature(this, timers);
|
|
84
97
|
this.codeActions = new CodeActionsFeature(this, timers);
|
|
85
98
|
this.rename = new RenameFeature(this);
|
|
86
99
|
this.navigation = new NavigationFeature(this);
|
|
87
100
|
this.peek = new PeekFeature(this);
|
|
101
|
+
this.callHierarchy = new CallHierarchyFeature(this, timers);
|
|
88
102
|
this.symbolPicker = new SymbolPickerFeature(this);
|
|
89
103
|
this.contextMenu = new ContextMenuFeature(this);
|
|
90
104
|
this.history = new HistoryFeature(this);
|
|
91
105
|
this.definitionLink = new DefinitionLinkFeature(this);
|
|
92
106
|
this.codeVision = new CodeVisionFeature(this, (listener) => this.symbols.onResult(listener), timers);
|
|
107
|
+
this.formatOnType = new FormatOnTypeFeature(this);
|
|
108
|
+
this.renderedDocs = new RenderedDocsFeature(this, timers);
|
|
93
109
|
new FoldingFeature(this, (listener) => this.symbols.onResult(listener), timers);
|
|
94
110
|
this.placeOpenedCaret(editor.getCaret());
|
|
95
111
|
this.onDispose(editor.onCaret((position) => this.placeOpenedCaret(position)));
|
package/src/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
|
+
}
|