@adecore/editor-react 0.22.1 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CallHierarchyPanel.d.ts +6 -0
- package/dist/CallHierarchyPanel.js +92 -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/PanelResizeHandle.d.ts +8 -0
- package/dist/PanelResizeHandle.js +71 -0
- package/dist/PeekPanel.d.ts +3 -0
- package/dist/PeekPanel.js +7 -4
- 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 +93 -0
- package/dist/call-hierarchy.d.ts +42 -0
- package/dist/call-hierarchy.js +398 -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 +15 -5
- 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 +19 -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 +117 -3
- package/dist/locales/nl.json +117 -3
- package/dist/models.d.ts +6 -2
- package/dist/models.js +5 -1
- package/dist/panel-size.d.ts +6 -0
- package/dist/panel-size.js +25 -0
- 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.d.ts +3 -1
- package/dist/peek.js +6 -1
- 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 +12 -0
- package/dist/project-language.js +52 -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 +64 -0
- package/dist/rendered-docs.js +374 -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/timers.d.ts +1 -0
- package/dist/timers.js +4 -0
- package/dist/use-panel-body.d.ts +6 -0
- package/dist/use-panel-body.js +13 -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 +244 -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/PanelResizeHandle.tsx +104 -0
- package/src/PeekPanel.tsx +11 -6
- 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 +151 -0
- package/src/call-hierarchy.ts +440 -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 +22 -6
- package/src/editor-react.css +79 -0
- package/src/format-on-type.ts +111 -0
- package/src/host-types.ts +23 -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 +117 -3
- package/src/locales/nl.json +117 -3
- package/src/models.ts +40 -1
- package/src/panel-size.ts +28 -0
- package/src/patch-decorations.ts +224 -0
- package/src/patch-layout.ts +388 -0
- package/src/peek.ts +6 -1
- package/src/pick.ts +12 -1
- package/src/popups.ts +24 -0
- package/src/project-language.ts +63 -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 +430 -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/timers.ts +5 -0
- package/src/use-panel-body.ts +17 -0
- package/src/workspace-edit.ts +347 -169
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { diffRows } from '@adecore/merge';
|
|
2
|
+
export const DEFAULT_UNFOLD_LIMITS = { rows: 20000, characters: 2000000, lineLength: 10000 };
|
|
3
|
+
export function fitsDiff(document, unfolded, limits, maxRows = limits.rows) {
|
|
4
|
+
let rows = 0;
|
|
5
|
+
let characters = 0;
|
|
6
|
+
for (const row of diffRows(document, 'unified', unfolded)) {
|
|
7
|
+
if (row.kind === 'gap') {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const text = row.new === undefined ? document.sides.old[row.old.index] : document.sides.new[row.new.index];
|
|
11
|
+
characters += text.length + (rows > 0 ? 1 : 0);
|
|
12
|
+
if (++rows > maxRows || characters > limits.characters || text.length > limits.lineLength) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
export function expandDiff(document, unfolded, id, limits) {
|
|
19
|
+
const rows = diffRows(document, 'unified', unfolded);
|
|
20
|
+
const gap = rows.find((row) => row.kind === 'gap' && row.id === id);
|
|
21
|
+
if (gap?.kind !== 'gap' || !gap.unfoldable) {
|
|
22
|
+
return unfolded;
|
|
23
|
+
}
|
|
24
|
+
const visible = rows.filter((row) => row.kind === 'line').length;
|
|
25
|
+
const maxRows = Math.min(limits.rows, visible + 500);
|
|
26
|
+
const previous = unfolded.get(id) ?? 0;
|
|
27
|
+
let low = 0;
|
|
28
|
+
let high = Math.min(500, gap.oldCount);
|
|
29
|
+
// Between-hunk gaps reveal from both ends; search the per-end count within the shared row budget.
|
|
30
|
+
while (low < high) {
|
|
31
|
+
const count = Math.ceil((low + high) / 2);
|
|
32
|
+
const candidate = new Map(unfolded).set(id, previous + count);
|
|
33
|
+
if (fitsDiff(document, candidate, limits, maxRows)) {
|
|
34
|
+
low = count;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
high = count - 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return low === 0 ? unfolded : new Map(unfolded).set(id, previous + low);
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Editor, EditorEngine } from '@adecore/editor';
|
|
2
|
+
import type { DiffDisplay } from '@adecore/merge';
|
|
3
|
+
import { RowHost, LineActionHost } from './row-host.ts';
|
|
4
|
+
export interface MountedDiff {
|
|
5
|
+
engine: EditorEngine;
|
|
6
|
+
editor: Editor;
|
|
7
|
+
rows: RowHost;
|
|
8
|
+
actions: LineActionHost;
|
|
9
|
+
}
|
|
10
|
+
export declare function DiffOverlays({ mounted, display, unfold, blocked }: {
|
|
11
|
+
mounted: MountedDiff;
|
|
12
|
+
display: DiffDisplay;
|
|
13
|
+
unfold(id: number): void;
|
|
14
|
+
blocked?: ReadonlySet<number>;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Button } from '@adecore/ui';
|
|
6
|
+
import { formatNumber } from '@adecore/ui/format';
|
|
7
|
+
import { RowHost, LineActionHost } from './row-host.js';
|
|
8
|
+
export function DiffOverlays({ mounted, display, unfold, blocked }) {
|
|
9
|
+
const { t } = useTranslation('editor');
|
|
10
|
+
useSyncExternalStore(mounted.rows.subscribe, mounted.rows.getVersion, mounted.rows.getVersion);
|
|
11
|
+
useSyncExternalStore(mounted.actions.subscribe, mounted.actions.getVersion, mounted.actions.getVersion);
|
|
12
|
+
return (_jsxs(_Fragment, { children: [display.gaps.map(({ row }) => {
|
|
13
|
+
const container = mounted.rows.container(`gap-${row.id}`);
|
|
14
|
+
if (container === undefined) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const text = t('diff.hidden', { count: formatNumber(Math.max(row.oldCount, row.newCount)) });
|
|
18
|
+
return createPortal(_jsx("div", { className: "adecore-editor-diff-gap", children: row.unfoldable ? (_jsx(Button, { size: "xs", disabled: blocked?.has(row.id), title: blocked?.has(row.id) ? t('diff.contextLimit') : undefined, onClick: () => unfold(row.id), children: text })) : (_jsx("span", { children: text })) }), container, `gap-${row.id}`);
|
|
19
|
+
}), display.rows.map((row, index) => {
|
|
20
|
+
if (row.noNewline === undefined) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const container = mounted.actions.container(`no-newline-${index}`);
|
|
24
|
+
return container === undefined
|
|
25
|
+
? null
|
|
26
|
+
: createPortal(_jsx("span", { className: "adecore-editor-diff-no-newline", children: t('diff.noNewline') }), container, `no-newline-${index}`);
|
|
27
|
+
})] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LineToken } from '@adecore/editor';
|
|
2
|
+
import type { DiffSyntaxDocument } from '@adecore/editor/shiki-worker';
|
|
3
|
+
import type { DiffDocument } from '@adecore/merge';
|
|
4
|
+
export type DiffSourceSide = 'old' | 'new';
|
|
5
|
+
export interface DiffSourceTokens {
|
|
6
|
+
get(side: DiffSourceSide, index: number): readonly LineToken[] | null | undefined;
|
|
7
|
+
subscribe(listener: () => void): () => void;
|
|
8
|
+
}
|
|
9
|
+
export interface DiffSyntaxSources {
|
|
10
|
+
readonly documents: Readonly<Record<DiffSourceSide, DiffSyntaxDocument>>;
|
|
11
|
+
locate(side: DiffSourceSide, index: number): {
|
|
12
|
+
readonly block: number;
|
|
13
|
+
readonly line: number;
|
|
14
|
+
} | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare function diffSyntaxDocument(document: DiffDocument, language: string, theme: string): DiffSyntaxSources;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function canTokenize(source) {
|
|
2
|
+
if (source.length > 20000) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
let characters = Math.max(0, source.length - 1);
|
|
6
|
+
for (const line of source) {
|
|
7
|
+
characters += line.length;
|
|
8
|
+
if (line.length > 10000 || characters > 2000000) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
export function diffSyntaxDocument(document, language, theme) {
|
|
15
|
+
const locations = { old: [], new: [] };
|
|
16
|
+
const build = (side) => {
|
|
17
|
+
const source = document.sides[side];
|
|
18
|
+
// Skipping a long hidden line loses grammar state; keep the entire side plain instead.
|
|
19
|
+
if (!canTokenize(source)) {
|
|
20
|
+
return { language, theme, blocks: [], resets: [] };
|
|
21
|
+
}
|
|
22
|
+
const blocks = [];
|
|
23
|
+
const resets = [];
|
|
24
|
+
const ranges = document.snapshots
|
|
25
|
+
? [{ from: 0, to: source.length }]
|
|
26
|
+
: document.offsets.map((offset, index) => ({ from: offset[side], to: document.offsets[index + 1]?.[side] ?? source.length }));
|
|
27
|
+
for (const range of ranges) {
|
|
28
|
+
if (!document.snapshots && range.from < range.to) {
|
|
29
|
+
resets.push(blocks.length);
|
|
30
|
+
}
|
|
31
|
+
for (let from = range.from; from < range.to; from += 200) {
|
|
32
|
+
const to = Math.min(from + 200, range.to);
|
|
33
|
+
const block = blocks.length;
|
|
34
|
+
blocks.push(source.slice(from, to).join('\n'));
|
|
35
|
+
for (let index = from; index < to; index++) {
|
|
36
|
+
locations[side][index] = { block, line: index - from };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { language, theme, blocks, resets };
|
|
41
|
+
};
|
|
42
|
+
const documents = { old: build('old'), new: build('new') };
|
|
43
|
+
return {
|
|
44
|
+
documents,
|
|
45
|
+
locate(side, index) {
|
|
46
|
+
return Number.isInteger(index) && index >= 0 ? locations[side][index] : undefined;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { diffRows, wordChanges } from '@adecore/merge';
|
|
2
|
+
export function wordMarks(document) {
|
|
3
|
+
const marks = { old: new Map(), new: new Map() };
|
|
4
|
+
let pairs = 0;
|
|
5
|
+
let characters = 0;
|
|
6
|
+
for (const row of diffRows(document, 'split')) {
|
|
7
|
+
if (row.kind !== 'line' || row.change !== 'pair' || document.hunks[row.hunk].coarse) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const old = document.sides.old[row.old.index];
|
|
11
|
+
const fresh = document.sides.new[row.new.index];
|
|
12
|
+
characters += old.length + fresh.length;
|
|
13
|
+
// Per-pair limits alone still allow thousands of expensive word diffs during one mount.
|
|
14
|
+
if (++pairs > 1000 || characters > 100000) {
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
const words = wordChanges(old, fresh);
|
|
18
|
+
if (words === null) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const ranges = (text, ranges) => ranges.filter((range) => !document.ignoreWhitespace || /\S/.test(text.slice(range.from, range.to))).map((range) => [range.from, range.to]);
|
|
22
|
+
marks.old.set(row.old.index, ranges(old, words.old));
|
|
23
|
+
marks.new.set(row.new.index, ranges(fresh, words.new));
|
|
24
|
+
}
|
|
25
|
+
return marks;
|
|
26
|
+
}
|
|
@@ -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
|
-
this.peek = new PeekFeature(this);
|
|
88
|
+
this.peek = new PeekFeature(this, timers);
|
|
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)));
|