@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,6 @@
|
|
|
1
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
2
|
+
import type { CallHierarchyView } from './popups.ts';
|
|
3
|
+
export declare function CallHierarchyPanel({ language, view }: {
|
|
4
|
+
language: EditorLanguage;
|
|
5
|
+
view: CallHierarchyView;
|
|
6
|
+
}): import("react").ReactPortal | null;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { ChevronDown, ChevronLeft, ChevronRight, X } from 'lucide-react';
|
|
6
|
+
import { fileUriToPath } from '@adecore/lsp';
|
|
7
|
+
import { Icon, IconButton, Segmented, Spinner, Tooltip } from '@adecore/ui';
|
|
8
|
+
import { formatNumber } from '@adecore/ui/format';
|
|
9
|
+
import { CALL_HIERARCHY_HEIGHT } from './call-hierarchy.js';
|
|
10
|
+
import { CALL_LEVEL_LIMIT } from './call-hierarchy-model.js';
|
|
11
|
+
import { basenameOf, relativeTo } from './paths.js';
|
|
12
|
+
import { PathText } from './PathText.js';
|
|
13
|
+
import { Preview } from './PeekPanel.js';
|
|
14
|
+
import { SymbolBadge } from './SymbolPicker.js';
|
|
15
|
+
const INDENT = 14;
|
|
16
|
+
const BASE_INDENT = 8;
|
|
17
|
+
function folderOf(path) {
|
|
18
|
+
return path.slice(0, Math.max(0, path.lastIndexOf('/')));
|
|
19
|
+
}
|
|
20
|
+
/* One line of the tree. Status lines are treeitems too, so the level of a symbol's calls is read the same way, but nothing selects them. */
|
|
21
|
+
function Line({ row, id, active, expanded, position, size, ref, children, onClick, onDoubleClick }) {
|
|
22
|
+
return (_jsx("div", { ref: ref, id: id, role: "treeitem", "aria-level": row.level, "aria-selected": active, "aria-expanded": expanded, "aria-posinset": position, "aria-setsize": size, "aria-disabled": row.kind === 'status' ? true : undefined, "data-active": active, className: "flex h-[22px] w-full cursor-default items-center gap-1.5 pr-3 text-left font-mono text-code hover:bg-surface-hover data-[active=true]:bg-accent-soft", style: { paddingLeft: BASE_INDENT + (row.level - 1) * INDENT }, onClick: onClick, onDoubleClick: onDoubleClick, children: children }));
|
|
23
|
+
}
|
|
24
|
+
function Symbol({ language, row, active, id, ref }) {
|
|
25
|
+
const { t } = useTranslation('editor');
|
|
26
|
+
const { item } = row;
|
|
27
|
+
const path = relativeTo(fileUriToPath(item.uri) ?? item.uri, language.project.folder);
|
|
28
|
+
const tool = language.callHierarchy;
|
|
29
|
+
return (_jsxs(Line, { ref: ref, row: row, id: id, active: active, expanded: row.leaf ? undefined : row.expanded, position: row.position, size: row.size, onClick: () => (active ? tool.go(row.id) : tool.select(row.id)), onDoubleClick: () => tool.go(row.id), children: [_jsx("span", { "aria-hidden": true, className: "grid size-3.5 shrink-0 place-items-center text-text-muted", onClick: (event) => {
|
|
30
|
+
event.stopPropagation();
|
|
31
|
+
tool.toggle(row.id);
|
|
32
|
+
}, children: row.leaf ? null : _jsx(Icon, { icon: row.expanded ? ChevronDown : ChevronRight, size: 14 }) }), _jsx(SymbolBadge, { kind: item.kind }), _jsx("span", { className: `shrink-0 text-text ${item.tags?.includes(1) ? 'line-through' : ''}`, children: item.name }), item.detail !== undefined && item.detail !== '' && _jsx("span", { className: "min-w-0 flex-1 truncate text-text-muted", children: item.detail }), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2 pl-2 font-sans text-xs text-text-faint", children: [row.sites > 1 && (_jsx(Tooltip, { label: t('language.callHierarchy.sites', { count: row.sites, formatted: formatNumber(row.sites) }), children: _jsx("span", { className: "text-text-muted", children: `×${formatNumber(row.sites)}` }) })), _jsx(Tooltip, { label: path, children: _jsx("span", { className: "max-w-40 truncate", children: `${basenameOf(path)}:${formatNumber(item.selectionRange.start.line + 1)}` }) })] })] }));
|
|
33
|
+
}
|
|
34
|
+
function Tree({ language, view }) {
|
|
35
|
+
const { t } = useTranslation('editor');
|
|
36
|
+
const treeId = useId();
|
|
37
|
+
const activeRow = useRef(null);
|
|
38
|
+
const idOf = (row) => `${treeId}-${row.id}`;
|
|
39
|
+
const activeId = view.rows.some((row) => row.id === view.active) ? `${treeId}-${view.active}` : undefined;
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
activeRow.current?.scrollIntoView({ block: 'nearest' });
|
|
42
|
+
}, [view.active]);
|
|
43
|
+
// The focus stays in the text, so the input is what names the tree and its active row.
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
language.editor.setInputPopup({ controls: treeId, activeDescendant: activeId });
|
|
46
|
+
return () => language.editor.setInputPopup(null);
|
|
47
|
+
}, [language, treeId, activeId]);
|
|
48
|
+
return (_jsx("div", { id: treeId, role: "tree", "aria-label": t('language.callHierarchy.label'), className: "w-1/2 shrink-0 overflow-y-auto border-r border-border py-1", children: view.rows.map((row) => {
|
|
49
|
+
const active = row.id === view.active;
|
|
50
|
+
const ref = active ? activeRow : undefined;
|
|
51
|
+
if (row.kind === 'node') {
|
|
52
|
+
return _jsx(Symbol, { ref: ref, language: language, row: row, active: active, id: idOf(row) }, row.id);
|
|
53
|
+
}
|
|
54
|
+
if (row.kind === 'more') {
|
|
55
|
+
const next = Math.min(row.hidden, CALL_LEVEL_LIMIT);
|
|
56
|
+
return (_jsx(Line, { ref: ref, row: row, id: idOf(row), active: active, onClick: () => language.callHierarchy.showMore(row.parent), children: _jsx("span", { className: "font-sans text-xs text-accent", children: t('language.callHierarchy.more', { count: next, formatted: formatNumber(next), hidden: formatNumber(row.hidden) }) }) }, row.id));
|
|
57
|
+
}
|
|
58
|
+
return (_jsx(Line, { row: row, id: idOf(row), active: false, onClick: () => language.callHierarchy.retry(row.parent), children: _jsx(StatusText, { row: row, direction: view.direction }) }, row.id));
|
|
59
|
+
}) }));
|
|
60
|
+
}
|
|
61
|
+
function StatusText({ row, direction }) {
|
|
62
|
+
const { t } = useTranslation('editor');
|
|
63
|
+
if (row.status === 'loading') {
|
|
64
|
+
return (_jsxs("span", { className: "flex items-center gap-2 font-sans text-xs text-text-muted", children: [_jsx(Spinner, { size: 12 }), t('language.callHierarchy.loading')] }));
|
|
65
|
+
}
|
|
66
|
+
if (row.status === 'failed') {
|
|
67
|
+
return (_jsx("span", { className: "min-w-0 truncate font-sans text-xs text-status-error", children: t('language.callHierarchy.retry', { message: row.message ?? '' }) }));
|
|
68
|
+
}
|
|
69
|
+
return (_jsx("span", { className: "font-sans text-xs text-text-muted", children: t(direction === 'incoming' ? 'language.callHierarchy.noIncoming' : 'language.callHierarchy.noOutgoing') }));
|
|
70
|
+
}
|
|
71
|
+
/*
|
|
72
|
+
* The call hierarchy of a symbol in the row the editor leaves under the line: the tree of callers or callees on
|
|
73
|
+
* the left and the code of the active call on the right. Like the peek it portals into that row, which the
|
|
74
|
+
* editor makes again when it scrolls back into view.
|
|
75
|
+
*/
|
|
76
|
+
export function CallHierarchyPanel({ language, view }) {
|
|
77
|
+
const { t } = useTranslation('editor');
|
|
78
|
+
if (view.container === null) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const folder = language.project.folder;
|
|
82
|
+
const path = view.preview === null ? null : fileUriToPath(view.preview.uri);
|
|
83
|
+
const shown = path === null ? '' : folderOf(relativeTo(path, folder));
|
|
84
|
+
return createPortal(_jsxs("div", { className: "flex flex-col bg-surface pt-[2px] font-sans text-xs text-text", style: { height: CALL_HIERARCHY_HEIGHT }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "peek-hatch h-[14px]" }), _jsxs("div", { className: "flex h-[33px] shrink-0 items-center gap-2 border-b border-border pr-2 pl-3 text-sm", children: [_jsx(Segmented, { value: view.direction, onValueChange: (direction) => language.callHierarchy.setDirection(direction), label: t('language.callHierarchy.direction'), options: [
|
|
85
|
+
{ id: 'incoming', label: t('language.callHierarchy.incoming') },
|
|
86
|
+
{ id: 'outgoing', label: t('language.callHierarchy.outgoing') }
|
|
87
|
+
] }), _jsx("span", { className: "shrink-0 font-medium", children: view.name }), _jsx("span", { className: "shrink-0 font-medium text-text-muted", children: path === null ? '' : basenameOf(path) }), _jsx(PathText, { path: shown, className: "text-text-muted" }), _jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: [view.sites > 1 && (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: ChevronLeft, size: "xs", label: t('language.callHierarchy.previousSite'), onClick: () => language.callHierarchy.stepSite(-1) }), _jsx("span", { className: "text-text-muted", children: t('language.callHierarchy.site', { current: formatNumber(view.site + 1), total: formatNumber(view.sites) }) }), _jsx(IconButton, { icon: ChevronRight, size: "xs", label: t('language.callHierarchy.nextSite'), onClick: () => language.callHierarchy.stepSite(1) })] })), _jsx(IconButton, { icon: X, size: "xs", label: t('language.callHierarchy.close'), onClick: () => language.callHierarchy.close() })] })] }), _jsxs("div", { className: "flex h-[180px] shrink-0", children: [_jsx(Tree, { language: language, view: view }), _jsx(Preview, { preview: view.preview })] }), _jsx("div", { className: "peek-hatch h-[14px]" })] }), view.container);
|
|
88
|
+
}
|
package/dist/CompletionPopup.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useId, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { kindLetterOf, kindToneOf, rowWindow } from './completion-model.js';
|
|
@@ -47,6 +47,7 @@ const ROW_CHROME = 72;
|
|
|
47
47
|
*/
|
|
48
48
|
export function CompletionPopup({ language, view, rect }) {
|
|
49
49
|
const { t } = useTranslation('editor');
|
|
50
|
+
const listId = useId();
|
|
50
51
|
const list = useRef(null);
|
|
51
52
|
const docs = useRef(null);
|
|
52
53
|
const activeRow = useRef(null);
|
|
@@ -94,10 +95,17 @@ export function CompletionPopup({ language, view, rect }) {
|
|
|
94
95
|
}, [view.active, view.rows]);
|
|
95
96
|
const total = view.rows.length;
|
|
96
97
|
const { first, last } = rowWindow(listTop, view.active, total, ROW_HEIGHT, VISIBLE_ROWS, OVERSCAN_ROWS, LIST_PADDING);
|
|
98
|
+
const optionId = (index) => `${listId}-option-${index}`;
|
|
99
|
+
const activeId = view.active >= first && view.active < last ? optionId(view.active) : undefined;
|
|
100
|
+
// The focus stays in the text, so the input is what names the list and its active option.
|
|
101
|
+
useLayoutEffect(() => {
|
|
102
|
+
language.editor.setInputPopup({ controls: listId, activeDescendant: activeId });
|
|
103
|
+
return () => language.editor.setInputPopup(null);
|
|
104
|
+
}, [language, listId, activeId]);
|
|
97
105
|
return createPortal(_jsxs(_Fragment, { children: [_jsxs("div", { ref: list, className: `${CARD} flex w-max max-w-[min(560px,calc(100vw-16px))] min-w-[340px] flex-col`, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { "aria-hidden": true, className: "h-0 overflow-hidden font-mono text-code", style: {
|
|
98
106
|
width: `calc(${Math.max(widest, need)}ch + ${ROW_CHROME}px)`
|
|
99
|
-
} }), _jsxs("div", { role: "listbox", className: "w-0 min-w-full overflow-y-auto p-1", style: { maxHeight: ROW_HEIGHT * VISIBLE_ROWS + 8 }, onScroll: (event) => setListTop(event.currentTarget.scrollTop), children: [_jsx("div", { "aria-hidden": true, style: { height: first * ROW_HEIGHT } }), view.rows.slice(first, last).map((row, offset) => {
|
|
107
|
+
} }), _jsxs("div", { id: listId, role: "listbox", className: "w-0 min-w-full overflow-y-auto p-1", style: { maxHeight: ROW_HEIGHT * VISIBLE_ROWS + 8 }, onScroll: (event) => setListTop(event.currentTarget.scrollTop), children: [_jsx("div", { "aria-hidden": true, style: { height: first * ROW_HEIGHT } }), view.rows.slice(first, last).map((row, offset) => {
|
|
100
108
|
const index = first + offset;
|
|
101
|
-
return (_jsxs("button", { ref: index === view.active ? activeRow : undefined, type: "button", role: "option", "aria-selected": index === view.active, "aria-setsize": total, "aria-posinset": index + 1, "data-active": index === view.active, className: "flex h-6 w-full items-center gap-2 overflow-hidden rounded-md px-1.5 text-left text-xs cursor-row", onClick: () => void language.completion.accept(false, index), children: [_jsx("span", { className: `grid size-4 shrink-0 place-items-center rounded-sm font-mono text-xs leading-none font-semibold ${TONES[kindToneOf(row.kind)]}`, children: kindLetterOf(row.kind) }), _jsx("span", { className: `max-w-full shrink-0 truncate font-mono text-code ${row.deprecated ? 'line-through' : ''}`, children: _jsx(MarkedLabel, { label: row.label, matches: row.matches }) }), _jsx("span", { className: "min-w-0 grow truncate font-mono text-text-faint", children: row.detail }), _jsx(PathText, { path: row.description, className: "max-w-[60%] font-mono text-text-faint" })] }, `${index}:${row.label}`));
|
|
109
|
+
return (_jsxs("button", { id: optionId(index), ref: index === view.active ? activeRow : undefined, type: "button", role: "option", "aria-selected": index === view.active, "aria-setsize": total, "aria-posinset": index + 1, "data-active": index === view.active, className: "flex h-6 w-full items-center gap-2 overflow-hidden rounded-md px-1.5 text-left text-xs cursor-row", onClick: () => void language.completion.accept(false, index), children: [_jsx("span", { className: `grid size-4 shrink-0 place-items-center rounded-sm font-mono text-xs leading-none font-semibold ${TONES[kindToneOf(row.kind)]}`, children: kindLetterOf(row.kind) }), _jsx("span", { className: `max-w-full shrink-0 truncate font-mono text-code ${row.deprecated ? 'line-through' : ''}`, children: _jsx(MarkedLabel, { label: row.label, matches: row.matches }) }), _jsx("span", { className: "min-w-0 grow truncate font-mono text-text-faint", children: row.detail }), _jsx(PathText, { path: row.description, className: "max-w-[60%] font-mono text-text-faint" })] }, `${index}:${row.label}`));
|
|
102
110
|
}), _jsx("div", { "aria-hidden": true, style: { height: (total - last) * ROW_HEIGHT } })] }), _jsxs("div", { className: "flex items-center gap-3 border-t border-border px-2 py-1 text-xs whitespace-nowrap text-text-faint", children: [_jsxs("span", { children: ["\u21B5 ", t('language.completion.insert')] }), _jsxs("span", { children: ["\u21E5 ", t('language.completion.replace')] }), _jsxs("span", { children: ["^Space ", t('language.completion.details')] }), _jsx("span", { className: "ml-auto", children: view.server })] })] }), view.detailsOpen && view.docs !== null && (_jsxs("div", { ref: docs, className: `${CARD} flex w-[360px] flex-col divide-y divide-border overflow-y-auto select-text`, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), children: [view.docs.source !== '' && (_jsx("div", { className: "px-3 py-2", children: _jsx(SourceLine, { text: view.docs.source, language: view.highlightLanguage }) })), _jsx(SymbolSections, { text: view.docs.text })] }))] }), document.body);
|
|
103
111
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Ref } from 'react';
|
|
2
|
+
import type { Editor, EditorEngine, EditorTheme } from '@adecore/editor';
|
|
3
|
+
import { type DiffDocument } from '@adecore/merge';
|
|
4
|
+
import type { DiffSourceTokens } from './diff-syntax.ts';
|
|
5
|
+
import { type DiffLimits } from './diff-expansion.ts';
|
|
6
|
+
export interface DiffViewProps {
|
|
7
|
+
engine: EditorEngine;
|
|
8
|
+
document: DiffDocument;
|
|
9
|
+
theme: EditorTheme;
|
|
10
|
+
tokens?: DiffSourceTokens;
|
|
11
|
+
wrap?: boolean;
|
|
12
|
+
layout?: 'unified' | 'split';
|
|
13
|
+
splitWrapLimit?: number;
|
|
14
|
+
fill?: boolean;
|
|
15
|
+
maxHeight?: number;
|
|
16
|
+
unfoldLimits?: DiffLimits;
|
|
17
|
+
label: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
ref?: Ref<HTMLDivElement>;
|
|
20
|
+
onMount?(editor: Editor, side?: 'old' | 'new'): void | (() => void);
|
|
21
|
+
}
|
|
22
|
+
export declare function DiffView(props: DiffViewProps): import("react").JSX.Element;
|
package/dist/DiffView.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { parseChord } from '@adecore/editor/keymap';
|
|
5
|
+
import { diffDisplay, diffSplit } from '@adecore/merge';
|
|
6
|
+
import { wordMarks } from './diff-wordmarks.js';
|
|
7
|
+
import { DiffOverlays } from './diff-overlays.js';
|
|
8
|
+
import { SplitDiffView } from './SplitDiffView.js';
|
|
9
|
+
import { LineActionHost, RowHost } from './row-host.js';
|
|
10
|
+
import { DEFAULT_UNFOLD_LIMITS, expandDiff } from './diff-expansion.js';
|
|
11
|
+
function UnifiedDiffView({ engine, document, display, unfold, blocked, theme, tokens, wrap = true, fill = false, maxHeight, label, className, ref, onMount }) {
|
|
12
|
+
const { t } = useTranslation('editor');
|
|
13
|
+
const emphasis = useMemo(() => wordMarks(document), [document]);
|
|
14
|
+
const element = useRef(null);
|
|
15
|
+
const current = useRef({ display, tokens, theme, wrap, label });
|
|
16
|
+
const applied = useRef({ theme, wrap, label });
|
|
17
|
+
const [mounted, setMounted] = useState(null);
|
|
18
|
+
const [height, setHeight] = useState(0);
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
current.current = { display, tokens, theme, wrap, label };
|
|
21
|
+
});
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
const host = element.current;
|
|
24
|
+
if (host === null) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const initial = current.current;
|
|
28
|
+
const editor = engine.mount(host, {
|
|
29
|
+
text: initial.display.text,
|
|
30
|
+
theme: initial.theme,
|
|
31
|
+
wrap: initial.wrap,
|
|
32
|
+
label: initial.label,
|
|
33
|
+
readOnly: true,
|
|
34
|
+
guides: false,
|
|
35
|
+
foldOutline: 'off',
|
|
36
|
+
folding: false,
|
|
37
|
+
// Prompt cards use this chord for their primary action; a read-only review has no split-line action.
|
|
38
|
+
handBack: [parseChord('Mod+Enter')]
|
|
39
|
+
});
|
|
40
|
+
applied.current = { theme: initial.theme, wrap: initial.wrap, label: initial.label };
|
|
41
|
+
editor.setBlocks([]);
|
|
42
|
+
editor.setLineTokens((line) => {
|
|
43
|
+
const { display, tokens } = current.current;
|
|
44
|
+
const row = display.rows[line];
|
|
45
|
+
return row === undefined ? null : tokens?.get(row.new === undefined ? 'old' : 'new', (row.new ?? row.old).index);
|
|
46
|
+
});
|
|
47
|
+
setMounted({ engine, editor, rows: new RowHost(editor, 'diff-gaps'), actions: new LineActionHost(editor, 'diff-status') });
|
|
48
|
+
const measure = () => setHeight(editor.getContentHeight());
|
|
49
|
+
const stop = editor.onViewChange(measure);
|
|
50
|
+
measure();
|
|
51
|
+
return () => {
|
|
52
|
+
stop();
|
|
53
|
+
editor.dispose();
|
|
54
|
+
};
|
|
55
|
+
}, [engine]);
|
|
56
|
+
useLayoutEffect(() => {
|
|
57
|
+
if (mounted === null || mounted.engine !== engine) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const { editor, rows, actions } = mounted;
|
|
61
|
+
editor.setText(display.text);
|
|
62
|
+
editor.setLineNumbers((line) => {
|
|
63
|
+
const row = current.current.display.rows[line];
|
|
64
|
+
return row === undefined ? null : String((row.new ?? row.old).number);
|
|
65
|
+
});
|
|
66
|
+
const highlights = [];
|
|
67
|
+
for (const [index, row] of display.rows.entries()) {
|
|
68
|
+
if (row.change !== 'delete' && row.change !== 'insert') {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const removed = row.change === 'delete';
|
|
72
|
+
highlights.push({
|
|
73
|
+
startLine: index + 1,
|
|
74
|
+
endLine: index + 1,
|
|
75
|
+
color: removed ? '--editor-deleted' : '--editor-added',
|
|
76
|
+
bar: removed ? 'hatched' : 'solid',
|
|
77
|
+
emphasis: [(removed ? emphasis.old.get(row.old.index) : emphasis.new.get(row.new.index)) ?? []]
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
editor.setLineHighlights(highlights);
|
|
81
|
+
editor.setChangeMarks(highlights.map((highlight) => ({
|
|
82
|
+
startLine: highlight.startLine,
|
|
83
|
+
endLine: highlight.endLine,
|
|
84
|
+
kind: highlight.bar === 'hatched' ? 'deleted' : 'added'
|
|
85
|
+
})));
|
|
86
|
+
rows.set(display.gaps.map(({ before, row }) => ({
|
|
87
|
+
id: `gap-${row.id}`,
|
|
88
|
+
line: Math.min(before, Math.max(0, display.rows.length - 1)),
|
|
89
|
+
placement: before < display.rows.length ? 'above' : 'below',
|
|
90
|
+
height: 32
|
|
91
|
+
})));
|
|
92
|
+
actions.set(display.rows.flatMap((row, line) => (row.noNewline === undefined ? [] : [{ id: `no-newline-${line}`, line }])));
|
|
93
|
+
}, [mounted, engine, display, emphasis]);
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
if (mounted === null || mounted.engine !== engine) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// The color of a changed line is not the only thing that says so: the gutter carries a sentence with the numbers it reads from.
|
|
99
|
+
mounted.editor.setLineLabels((line) => {
|
|
100
|
+
const row = display.rows[line];
|
|
101
|
+
if (row?.change === 'delete') {
|
|
102
|
+
return t('diff.removed', { number: row.old.number });
|
|
103
|
+
}
|
|
104
|
+
if (row?.change === 'insert') {
|
|
105
|
+
return t('diff.added', { number: row.new.number });
|
|
106
|
+
}
|
|
107
|
+
return row?.old !== undefined && row.new !== undefined ? t('diff.unchanged', { old: row.old.number, new: row.new.number }) : null;
|
|
108
|
+
});
|
|
109
|
+
}, [mounted, engine, display, t]);
|
|
110
|
+
useLayoutEffect(() => {
|
|
111
|
+
if (mounted === null || mounted.engine !== engine) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const previous = applied.current;
|
|
115
|
+
if (previous.theme !== theme) {
|
|
116
|
+
mounted.editor.setTheme(theme);
|
|
117
|
+
}
|
|
118
|
+
if (previous.wrap !== wrap) {
|
|
119
|
+
mounted.editor.setWrap(wrap);
|
|
120
|
+
}
|
|
121
|
+
if (previous.label !== label) {
|
|
122
|
+
mounted.editor.setLabel(label);
|
|
123
|
+
}
|
|
124
|
+
applied.current = { theme, wrap, label };
|
|
125
|
+
}, [mounted, engine, theme, wrap, label]);
|
|
126
|
+
useLayoutEffect(() => {
|
|
127
|
+
if (mounted === null || mounted.engine !== engine) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const refresh = () => {
|
|
131
|
+
mounted.editor.setLineTokens((line) => {
|
|
132
|
+
const row = current.current.display.rows[line];
|
|
133
|
+
return row === undefined ? null : current.current.tokens?.get(row.new === undefined ? 'old' : 'new', (row.new ?? row.old).index);
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
refresh();
|
|
137
|
+
const ownerWindow = element.current?.ownerDocument.defaultView;
|
|
138
|
+
let pending = false;
|
|
139
|
+
let stopped = false;
|
|
140
|
+
let frame;
|
|
141
|
+
const schedule = () => {
|
|
142
|
+
if (pending) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
pending = true;
|
|
146
|
+
const paint = () => {
|
|
147
|
+
pending = false;
|
|
148
|
+
if (!stopped) {
|
|
149
|
+
refresh();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
if (ownerWindow?.requestAnimationFrame !== undefined) {
|
|
153
|
+
frame = ownerWindow.requestAnimationFrame(paint);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
queueMicrotask(paint);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const stop = tokens?.subscribe(schedule);
|
|
160
|
+
return () => {
|
|
161
|
+
stopped = true;
|
|
162
|
+
stop?.();
|
|
163
|
+
if (frame !== undefined) {
|
|
164
|
+
ownerWindow?.cancelAnimationFrame?.(frame);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}, [mounted, engine, tokens, display]);
|
|
168
|
+
useLayoutEffect(() => {
|
|
169
|
+
if (mounted !== null && mounted.engine === engine) {
|
|
170
|
+
return onMount?.(mounted.editor);
|
|
171
|
+
}
|
|
172
|
+
}, [mounted, engine, onMount]);
|
|
173
|
+
const limit = maxHeight !== undefined && Number.isFinite(maxHeight) && maxHeight > 0 ? maxHeight : Infinity;
|
|
174
|
+
return (_jsxs("div", { className: `adecore-editor-diff ${className ?? ''}`, ref: ref, "data-scroll": fill || height > limit, style: { position: 'relative', minHeight: 0, height: fill ? '100%' : Math.min(height, limit) }, children: [_jsx("div", { ref: element, style: { position: 'absolute', inset: 0 } }), mounted !== null && mounted.engine === engine && _jsx(DiffOverlays, { mounted: mounted, display: display, unfold: unfold, blocked: blocked })] }));
|
|
175
|
+
}
|
|
176
|
+
export function DiffView(props) {
|
|
177
|
+
const { document, unfoldLimits = DEFAULT_UNFOLD_LIMITS } = props;
|
|
178
|
+
const [expanded, setExpanded] = useState(() => ({
|
|
179
|
+
document,
|
|
180
|
+
gaps: new Map(),
|
|
181
|
+
blocked: new Set()
|
|
182
|
+
}));
|
|
183
|
+
const gaps = expanded.document === document ? expanded.gaps : undefined;
|
|
184
|
+
const blocked = expanded.document === document ? expanded.blocked : new Set();
|
|
185
|
+
const projection = useMemo(() => (props.layout === 'split' ? diffSplit(document, gaps) : diffDisplay(document, gaps)), [props.layout, document, gaps]);
|
|
186
|
+
const unfold = (id) => setExpanded((previous) => {
|
|
187
|
+
const gaps = previous.document === document ? previous.gaps : new Map();
|
|
188
|
+
const next = expandDiff(document, gaps, id, unfoldLimits);
|
|
189
|
+
return {
|
|
190
|
+
document,
|
|
191
|
+
gaps: next,
|
|
192
|
+
blocked: new Set([...(previous.document === document ? previous.blocked : []), ...(next === gaps ? [id] : [])])
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
if ('aligned' in projection && projection.old.lines.length > 0 && projection.new.lines.length > 0) {
|
|
196
|
+
const visibleRows = projection.aligned.reduce((count, row) => count + (row.kind === 'gap' ? 0 : row.row.change === 'pair' ? 2 : 1), 0);
|
|
197
|
+
const wrap = props.wrap !== false && visibleRows <= (props.splitWrapLimit ?? 2000);
|
|
198
|
+
return _jsx(SplitDiffView, { ...props, wrap: wrap, display: projection, unfold: unfold, blocked: blocked });
|
|
199
|
+
}
|
|
200
|
+
// An absent side has no selectable text; keep additions and deletions in the unified editor.
|
|
201
|
+
const display = 'aligned' in projection ? (projection.old.lines.length > 0 ? projection.old : projection.new) : projection;
|
|
202
|
+
return _jsx(UnifiedDiffView, { ...props, display: display, unfold: unfold, blocked: blocked });
|
|
203
|
+
}
|
|
@@ -14,6 +14,7 @@ export function EditorContextMenu({ language, view, children }) {
|
|
|
14
14
|
{ id: 'goToTypeDefinition', method: 'textDocument/typeDefinition', run: () => void language.navigation.go('typeDefinition') },
|
|
15
15
|
{ id: 'goToImplementation', method: 'textDocument/implementation', run: () => void language.navigation.go('implementation') },
|
|
16
16
|
{ id: 'peekReferences', method: 'textDocument/references', run: () => void language.peek.open() },
|
|
17
|
+
{ id: 'callHierarchy', method: 'textDocument/prepareCallHierarchy', run: () => void language.callHierarchy.open() },
|
|
17
18
|
{ id: 'renameSymbol', method: 'textDocument/rename', run: () => void language.rename.start() },
|
|
18
19
|
{ id: 'codeActions', method: 'textDocument/codeAction', run: () => void language.codeActions.open() }
|
|
19
20
|
];
|
package/dist/EditorView.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createHolder } from './holder.js';
|
|
3
3
|
import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
4
5
|
import { EditorLanguage } from './editor-language.js';
|
|
5
6
|
import { LanguagePopups } from './LanguagePopups.js';
|
|
7
|
+
/* The names of the fold controls in the editor's `{start}` and `{end}` placeholders, which the editor fills in per control. */
|
|
8
|
+
function foldMessages(t) {
|
|
9
|
+
const lines = { start: '{start}', end: '{end}' };
|
|
10
|
+
return { fold: t('language.fold.fold', lines), unfold: t('language.fold.unfold', lines) };
|
|
11
|
+
}
|
|
6
12
|
export function EditorView({ engine, options, project, uri, languageId, className, ref, onMount, contextMenuItems }) {
|
|
13
|
+
const { t } = useTranslation('editor');
|
|
7
14
|
const element = useRef(null);
|
|
15
|
+
const mounted = useRef(null);
|
|
16
|
+
const words = useRef(t);
|
|
8
17
|
const holder = useMemo(() => createHolder(), []);
|
|
9
18
|
const language = useSyncExternalStore(holder.subscribe, holder.get, holder.get);
|
|
10
19
|
useLayoutEffect(() => {
|
|
@@ -12,16 +21,24 @@ export function EditorView({ engine, options, project, uri, languageId, classNam
|
|
|
12
21
|
if (host === null) {
|
|
13
22
|
return;
|
|
14
23
|
}
|
|
15
|
-
const editor = engine.mount(host, options);
|
|
24
|
+
const editor = engine.mount(host, { ...options, messages: { ...foldMessages(words.current), ...options.messages } });
|
|
25
|
+
mounted.current = editor;
|
|
16
26
|
const attached = project === undefined || uri === undefined || languageId === undefined ? null : new EditorLanguage(project, editor, uri, languageId);
|
|
17
27
|
holder.set(attached);
|
|
18
28
|
const stop = onMount?.(editor, attached);
|
|
19
29
|
return () => {
|
|
30
|
+
mounted.current = null;
|
|
20
31
|
holder.set(null);
|
|
21
32
|
stop?.();
|
|
22
33
|
attached?.dispose();
|
|
23
34
|
editor.dispose();
|
|
24
35
|
};
|
|
25
36
|
}, [engine, options, project, uri, languageId, holder, onMount]);
|
|
37
|
+
useLayoutEffect(() => {
|
|
38
|
+
words.current = t;
|
|
39
|
+
const { fold, unfold } = options.messages ?? {};
|
|
40
|
+
const localized = foldMessages(t);
|
|
41
|
+
mounted.current?.setMessages({ fold: fold ?? localized.fold, unfold: unfold ?? localized.unfold });
|
|
42
|
+
}, [t, options.messages]);
|
|
26
43
|
return (_jsxs("div", { className: className, ref: ref, style: { position: 'relative', minHeight: 0 }, children: [_jsx("div", { ref: element, style: { position: 'absolute', inset: 0 } }), language !== null && _jsx(LanguagePopups, { language: language, contextMenuItems: contextMenuItems })] }));
|
|
27
44
|
}
|
package/dist/FindReplace.d.ts
CHANGED
package/dist/FindReplace.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Button } from '@adecore/ui';
|
|
5
5
|
import { formatNumber } from '@adecore/ui/format';
|
|
6
|
+
import { MAX_REPLACEMENT_PAGE, MAX_REPLACEMENT_CHARACTERS } from '@adecore/editor';
|
|
6
7
|
import { compileFind, EMPTY_FIND_QUERY } from './find-query.js';
|
|
7
8
|
export function FindReplace({ editor, className, onClose }) {
|
|
8
9
|
const { t } = useTranslation('editor');
|
|
@@ -10,6 +11,7 @@ export function FindReplace({ editor, className, onClose }) {
|
|
|
10
11
|
const [replacement, setReplacement] = useState('');
|
|
11
12
|
const [preserveCase, setPreserveCase] = useState(false);
|
|
12
13
|
const [state, setState] = useState({ count: 0, current: null });
|
|
14
|
+
const root = useRef(null);
|
|
13
15
|
const compiled = useMemo(() => compileFind(query), [query]);
|
|
14
16
|
useEffect(() => editor.onFind(setState), [editor]);
|
|
15
17
|
useEffect(() => {
|
|
@@ -22,12 +24,45 @@ export function FindReplace({ editor, className, onClose }) {
|
|
|
22
24
|
editor.endFind();
|
|
23
25
|
editor.setReplacePreview(null);
|
|
24
26
|
}, [editor]);
|
|
27
|
+
// The host closes the widget by unmounting it, with the keyboard in it or not; only a keyboard in it is handed back.
|
|
28
|
+
useLayoutEffect(() => () => {
|
|
29
|
+
if (root.current?.contains(root.current.ownerDocument.activeElement) === true) {
|
|
30
|
+
editor.focus();
|
|
31
|
+
}
|
|
32
|
+
}, [editor]);
|
|
33
|
+
const failure = state.failed ?? state.captureFailed;
|
|
25
34
|
const label = compiled.kind === 'invalid'
|
|
26
35
|
? t('find.invalid')
|
|
27
|
-
: state.
|
|
28
|
-
? t(
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
: state.replaceFailed !== undefined
|
|
37
|
+
? t(`find.replace.failed.${state.replaceFailed}`, {
|
|
38
|
+
edits: formatNumber(MAX_REPLACEMENT_PAGE),
|
|
39
|
+
characters: formatNumber(MAX_REPLACEMENT_CHARACTERS)
|
|
40
|
+
})
|
|
41
|
+
: failure !== undefined
|
|
42
|
+
? t(`find.failed.${failure}`)
|
|
43
|
+
: state.searching
|
|
44
|
+
? t('find.searching')
|
|
45
|
+
: state.large
|
|
46
|
+
? t('find.large')
|
|
47
|
+
: state.noSelection
|
|
48
|
+
? t('find.noSelection')
|
|
49
|
+
: state.replaced !== undefined
|
|
50
|
+
? t('find.replaced', { count: formatNumber(state.replaced) })
|
|
51
|
+
: state.limited && state.current === null
|
|
52
|
+
? t('find.countMore', { total: formatNumber(state.count) })
|
|
53
|
+
: t(state.limited ? 'find.countLimited' : 'find.count', {
|
|
54
|
+
total: formatNumber(state.count),
|
|
55
|
+
current: state.current === null ? '0' : formatNumber(state.current + 1)
|
|
56
|
+
});
|
|
57
|
+
return (_jsxs("div", { ref: root, className: `adecore-editor-find ${className ?? ''}`, role: "search", "aria-label": t('find.label'), onKeyDown: (event) => {
|
|
58
|
+
if (event.key === 'Escape' && !event.nativeEvent.isComposing) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
editor.endFind();
|
|
61
|
+
editor.setReplacePreview(null);
|
|
62
|
+
onClose?.();
|
|
63
|
+
editor.focus();
|
|
64
|
+
}
|
|
65
|
+
}, children: [_jsxs("div", { className: "adecore-editor-find-row", children: [_jsx("input", { type: "text", "aria-label": t('find.label'), value: query.text, onChange: (event) => setQuery({ ...query, text: event.target.value }), onKeyDown: (event) => {
|
|
31
66
|
if (event.nativeEvent.isComposing) {
|
|
32
67
|
return;
|
|
33
68
|
}
|
|
@@ -35,10 +70,5 @@ export function FindReplace({ editor, className, onClose }) {
|
|
|
35
70
|
event.preventDefault();
|
|
36
71
|
editor.findStep(event.shiftKey ? -1 : 1);
|
|
37
72
|
}
|
|
38
|
-
|
|
39
|
-
event.preventDefault();
|
|
40
|
-
onClose?.();
|
|
41
|
-
editor.focus();
|
|
42
|
-
}
|
|
43
|
-
} }), _jsx("output", { "aria-live": "polite", children: label }), _jsx(Button, { size: "xs", onClick: () => editor.findStep(-1), children: t('find.previous') }), _jsx(Button, { size: "xs", onClick: () => editor.findStep(1), children: t('find.next') })] }), _jsx("div", { className: "adecore-editor-find-row", children: ['caseSensitive', 'wholeWord', 'regex', 'inSelection'].map((option) => (_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: query[option] === true, onChange: (event) => setQuery({ ...query, [option]: event.target.checked }) }), t(`find.${option}`)] }, option))) }), _jsxs("div", { className: "adecore-editor-find-row", children: [_jsx("input", { type: "text", "aria-label": t('find.replace.label'), value: replacement, onChange: (event) => setReplacement(event.target.value) }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preserveCase, onChange: (event) => setPreserveCase(event.target.checked) }), t('find.replace.preserveCase')] }), _jsx(Button, { size: "xs", onClick: () => editor.replace(replacement, { preserveCase }), children: t('find.replace.one') }), _jsx(Button, { size: "xs", onClick: () => editor.replaceAll(replacement, { preserveCase }), children: t('find.replace.allLabel') })] })] }));
|
|
73
|
+
} }), _jsx("output", { "aria-live": "polite", children: label }), _jsx(Button, { size: "xs", onClick: () => editor.findStep(-1), children: t('find.previous') }), _jsx(Button, { size: "xs", onClick: () => editor.findStep(1), children: t('find.next') })] }), _jsx("div", { className: "adecore-editor-find-row", children: ['caseSensitive', 'wholeWord', 'regex', 'inSelection'].map((option) => (_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: query[option] === true, onChange: (event) => setQuery({ ...query, [option]: event.target.checked }) }), t(`find.${option}`)] }, option))) }), _jsxs("div", { className: "adecore-editor-find-row", children: [_jsx("input", { type: "text", "aria-label": t('find.replace.label'), value: replacement, onChange: (event) => setReplacement(event.target.value) }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preserveCase, onChange: (event) => setPreserveCase(event.target.checked) }), t('find.replace.preserveCase')] }), _jsx(Button, { size: "xs", disabled: state.searching === true, onClick: () => editor.replace(replacement, { preserveCase }), children: t('find.replace.one') }), _jsx(Button, { size: "xs", disabled: state.searching === true, onClick: () => void editor.replaceAllAsync(replacement, { preserveCase }), children: t('find.replace.allLabel') })] })] }));
|
|
44
74
|
}
|
package/dist/HoverCard.js
CHANGED
|
@@ -46,8 +46,8 @@ export function HoverCard({ language, problems, info, anchor, position }) {
|
|
|
46
46
|
return (_jsxs("div", { className: "flex w-max min-w-[280px] max-w-[min(520px,calc(100vw-16px))] flex-col divide-y divide-border", children: [info !== null && (_jsxs(_Fragment, { children: [_jsx(SymbolSections, { text: info.text, onName: (name, declared) => {
|
|
47
47
|
language.hover.hide();
|
|
48
48
|
void language.navigation.goToName(name, anchor, name === (declared ?? info.word) ? info.definition : null);
|
|
49
|
-
} }), _jsxs("div", { className: "flex flex-col gap-0.5 px-3 py-1.5 text-xs", children: [_jsxs("div", { className: "flex items-center gap-3 whitespace-nowrap", children: [info.definition !== null && (_jsx("button", { type: "button", className: "text-accent hover:underline", onClick: () => language.goTo(info.definition), children: t('language.hover.definition') })), info.references !== null && info.references > 0 && (_jsx("button", { type: "button", className: "text-accent hover:underline", onClick: () => {
|
|
49
|
+
} }), info.plain !== true && (_jsxs("div", { className: "flex flex-col gap-0.5 px-3 py-1.5 text-xs", children: [_jsxs("div", { className: "flex items-center gap-3 whitespace-nowrap", children: [info.definition !== null && (_jsx("button", { type: "button", className: "text-accent hover:underline", onClick: () => language.goTo(info.definition), children: t('language.hover.definition') })), info.references !== null && info.references > 0 && (_jsx("button", { type: "button", className: "text-accent hover:underline", onClick: () => {
|
|
50
50
|
language.hover.hide();
|
|
51
51
|
void language.peek.open(position);
|
|
52
|
-
}, children: t('language.hover.references', { count: info.references, formatted: formatNumber(info.references) }) }))] }), info.definition !== null && _jsx(DefinitionPlace, { definition: info.definition, uri: language.uri, folder: language.project.folder })] })] })), problems.map((problem, index) => (_jsx(ProblemSection, { problem: problem, language: language }, index)))] }));
|
|
52
|
+
}, children: t('language.hover.references', { count: info.references, formatted: formatNumber(info.references) }) }))] }), info.definition !== null && _jsx(DefinitionPlace, { definition: info.definition, uri: language.uri, folder: language.project.folder })] }))] })), problems.map((problem, index) => (_jsx(ProblemSection, { problem: problem, language: language }, index)))] }));
|
|
53
53
|
}
|
package/dist/LanguagePopups.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useReducer } from 'react';
|
|
3
3
|
import { useStore } from 'zustand';
|
|
4
4
|
import { AnchoredPopup } from './AnchoredPopup.js';
|
|
5
|
+
import { CallHierarchyPanel } from './CallHierarchyPanel.js';
|
|
5
6
|
import { CodeAuthorsCard } from './CodeAuthorsCard.js';
|
|
6
7
|
import { CompletionPopup } from './CompletionPopup.js';
|
|
7
8
|
import { EditorContextMenu } from './EditorContextMenu.js';
|
|
@@ -9,6 +10,7 @@ import { HoverCard } from './HoverCard.js';
|
|
|
9
10
|
import { PeekPanel } from './PeekPanel.js';
|
|
10
11
|
import { PickPopup } from './PickPopup.js';
|
|
11
12
|
import { RenameCard } from './RenameCard.js';
|
|
13
|
+
import { RenderedDocs } from './RenderedDocs.js';
|
|
12
14
|
import { SymbolPicker } from './SymbolPicker.js';
|
|
13
15
|
import { SignatureCard } from './SignatureCard.js';
|
|
14
16
|
/*
|
|
@@ -24,6 +26,7 @@ export function LanguagePopups({ language, contextMenuItems }) {
|
|
|
24
26
|
const pick = useStore(language.popups, (state) => state.pick);
|
|
25
27
|
const rename = useStore(language.popups, (state) => state.rename);
|
|
26
28
|
const peek = useStore(language.popups, (state) => state.peek);
|
|
29
|
+
const callHierarchy = useStore(language.popups, (state) => state.callHierarchy ?? null);
|
|
27
30
|
const symbols = useStore(language.popups, (state) => state.symbols);
|
|
28
31
|
const menu = useStore(language.popups, (state) => state.menu);
|
|
29
32
|
const authors = useStore(language.popups, (state) => state.authors);
|
|
@@ -34,5 +37,5 @@ export function LanguagePopups({ language, contextMenuItems }) {
|
|
|
34
37
|
const pickRect = pick === null ? null : language.editor.rectAt(pick.anchor);
|
|
35
38
|
const renameRect = rename === null ? null : language.editor.rectAt(rename.range.start);
|
|
36
39
|
const renameEnd = rename === null ? null : language.editor.rectAt(rename.range.end);
|
|
37
|
-
return (_jsxs(_Fragment, { children: [hover !== null && hoverRect !== null && (_jsx(AnchoredPopup, { rect: hoverRect, onPointerEnter: () => language.hover.holdCard(true), onPointerLeave: () => language.hover.holdCard(false), children: _jsx(HoverCard, { language: language, problems: hover.problems, info: hover.info, anchor: hover.anchor, position: hover.position }) })), signature !== null && signatureRect !== null && (_jsx(AnchoredPopup, { rect: signatureRect, placement: { prefer: 'above' }, children: _jsx(SignatureCard, { model: signature.model }) })), completion !== null && completionRect !== null && _jsx(CompletionPopup, { language: language, view: completion, rect: completionRect }), pick !== null && pickRect !== null && _jsx(PickPopup, { language: language, view: pick, rect: pickRect }), menu !== null && (_jsx(EditorContextMenu, { language: language, view: menu, children: contextMenuItems?.(language) })), symbols !== null && _jsx(SymbolPicker, { language: language, view: symbols }), peek !== null && _jsx(PeekPanel, { language: language, view: peek }), authors !== null && _jsx(CodeAuthorsCard, { language: language, view: authors }), rename !== null && renameRect !== null && _jsx(RenameCard, { language: language, view: rename, rect: renameRect, endRect: renameEnd })] }));
|
|
40
|
+
return (_jsxs(_Fragment, { children: [_jsx(RenderedDocs, { language: language }), hover !== null && hoverRect !== null && (_jsx(AnchoredPopup, { rect: hoverRect, onPointerEnter: () => language.hover.holdCard(true), onPointerLeave: () => language.hover.holdCard(false), children: _jsx(HoverCard, { language: language, problems: hover.problems, info: hover.info, anchor: hover.anchor, position: hover.position }) })), signature !== null && signatureRect !== null && (_jsx(AnchoredPopup, { rect: signatureRect, placement: { prefer: 'above' }, children: _jsx(SignatureCard, { model: signature.model }) })), completion !== null && completionRect !== null && _jsx(CompletionPopup, { language: language, view: completion, rect: completionRect }), pick !== null && pickRect !== null && _jsx(PickPopup, { language: language, view: pick, rect: pickRect }), menu !== null && (_jsx(EditorContextMenu, { language: language, view: menu, children: contextMenuItems?.(language) })), symbols !== null && _jsx(SymbolPicker, { language: language, view: symbols }), peek !== null && _jsx(PeekPanel, { language: language, view: peek }), callHierarchy !== null && _jsx(CallHierarchyPanel, { language: language, view: callHierarchy }), authors !== null && _jsx(CodeAuthorsCard, { language: language, view: authors }), rename !== null && renameRect !== null && _jsx(RenameCard, { language: language, view: rename, rect: renameRect, endRect: renameEnd })] }));
|
|
38
41
|
}
|
package/dist/Markdown.d.ts
CHANGED
package/dist/Markdown.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import ReactMarkdown from 'react-markdown';
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import ReactMarkdown, {} from 'react-markdown';
|
|
3
3
|
import remarkGfm from 'remark-gfm';
|
|
4
4
|
import remarkBreaks from 'remark-breaks';
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/* An image becomes its alt text, so a comment cannot make the editor fetch a page of its choosing. */
|
|
6
|
+
const WITHOUT_IMAGES = {
|
|
7
|
+
img: ({ alt }) => _jsx(_Fragment, { children: alt ?? '' })
|
|
8
|
+
};
|
|
9
|
+
export function Markdown({ text, images = true }) {
|
|
10
|
+
return (_jsx("div", { className: "adecore-editor-markdown", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], components: images ? undefined : WITHOUT_IMAGES, children: text }) }));
|
|
7
11
|
}
|
package/dist/PeekPanel.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { EditorLanguage } from './editor-language.ts';
|
|
2
2
|
import type { PeekView } from './popups.ts';
|
|
3
|
+
export declare function Preview({ preview }: {
|
|
4
|
+
preview: PeekView['preview'];
|
|
5
|
+
}): import("react").JSX.Element;
|
|
3
6
|
export declare function PeekPanel({ language, view }: {
|
|
4
7
|
language: EditorLanguage;
|
|
5
8
|
view: PeekView;
|
package/dist/PeekPanel.js
CHANGED
|
@@ -15,10 +15,9 @@ import { useEditorRendering } from './rendering-context.js';
|
|
|
15
15
|
const TAB_SIZE = 4;
|
|
16
16
|
const CODE = 'font-mono text-(length:--code-font-size) [tab-size:4] [&_.line]:block [&_.line]:h-(--code-line-height) [&_.line]:leading-(--code-line-height) [&_pre]:m-0 [&_pre]:bg-transparent! [&_pre]:p-0 [&_pre]:whitespace-pre [&_code]:font-mono';
|
|
17
17
|
/* The code around the active reference, drawn like the editor's own rows: its gutter, the line the reference is on and the name marked. */
|
|
18
|
-
function Preview({
|
|
18
|
+
export function Preview({ preview }) {
|
|
19
19
|
const { theme, highlight } = useEditorRendering();
|
|
20
20
|
const scroller = useRef(null);
|
|
21
|
-
const { preview } = view;
|
|
22
21
|
const [html, setHtml] = useState(null);
|
|
23
22
|
const path = preview === null ? null : fileUriToPath(preview.uri);
|
|
24
23
|
const key = preview === null ? '' : `${theme}\0${path}\0${preview.text}`;
|
|
@@ -100,5 +99,5 @@ export function PeekPanel({ language, view }) {
|
|
|
100
99
|
return createPortal(_jsxs("div", { className: "flex flex-col bg-surface pt-[2px] font-sans text-xs text-text", style: { height: PEEK_HEIGHT }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { className: "peek-hatch h-[14px]" }), _jsxs("div", { className: "flex h-[31px] shrink-0 items-center gap-2 border-b border-border pr-2 pl-3 text-sm", children: [_jsx("span", { className: "shrink-0 font-medium", children: path === null ? '' : basenameOf(path) }), _jsx(PathText, { path: shown, className: "text-text-muted" }), _jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: [_jsx("span", { className: "text-text-muted", children: t(view.kind === 'definitions' ? 'language.peek.definitions' : 'language.peek.references', {
|
|
101
100
|
count: view.count,
|
|
102
101
|
formatted: formatNumber(view.count)
|
|
103
|
-
}) }), _jsx(IconButton, { icon: X, size: "xs", label: t('language.peek.close'), onClick: () => language.peek.close() })] })] }), _jsxs("div", { className: "flex h-[180px] shrink-0", children: [_jsx(Preview, {
|
|
102
|
+
}) }), _jsx(IconButton, { icon: X, size: "xs", label: t('language.peek.close'), onClick: () => language.peek.close() })] })] }), _jsxs("div", { className: "flex h-[180px] shrink-0", children: [_jsx(Preview, { preview: view.preview }), _jsx(Places, { language: language, view: view })] }), _jsx("div", { className: "peek-hatch h-[14px]" })] }), view.container);
|
|
104
103
|
}
|