@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.
Files changed (209) hide show
  1. package/dist/CallHierarchyPanel.d.ts +6 -0
  2. package/dist/CallHierarchyPanel.js +92 -0
  3. package/dist/CompletionPopup.js +11 -3
  4. package/dist/DiffView.d.ts +22 -0
  5. package/dist/DiffView.js +203 -0
  6. package/dist/EditorContextMenu.js +1 -0
  7. package/dist/EditorView.js +18 -1
  8. package/dist/FindReplace.d.ts +1 -1
  9. package/dist/FindReplace.js +41 -11
  10. package/dist/HoverCard.js +2 -2
  11. package/dist/LanguagePopups.js +4 -1
  12. package/dist/Markdown.d.ts +2 -1
  13. package/dist/Markdown.js +8 -4
  14. package/dist/PanelResizeHandle.d.ts +8 -0
  15. package/dist/PanelResizeHandle.js +71 -0
  16. package/dist/PeekPanel.d.ts +3 -0
  17. package/dist/PeekPanel.js +7 -4
  18. package/dist/PickPopup.js +15 -2
  19. package/dist/RenameCard.js +10 -5
  20. package/dist/RenderedDocs.d.ts +9 -0
  21. package/dist/RenderedDocs.js +84 -0
  22. package/dist/SplitDiffView.d.ts +9 -0
  23. package/dist/SplitDiffView.js +276 -0
  24. package/dist/SymbolPicker.d.ts +3 -0
  25. package/dist/SymbolPicker.js +4 -3
  26. package/dist/call-hierarchy-model.d.ts +52 -0
  27. package/dist/call-hierarchy-model.js +93 -0
  28. package/dist/call-hierarchy.d.ts +42 -0
  29. package/dist/call-hierarchy.js +398 -0
  30. package/dist/code-actions-model.d.ts +2 -0
  31. package/dist/code-actions-model.js +12 -10
  32. package/dist/code-actions.js +3 -1
  33. package/dist/completion-model.d.ts +14 -4
  34. package/dist/completion-model.js +40 -33
  35. package/dist/completion.d.ts +4 -0
  36. package/dist/completion.js +257 -42
  37. package/dist/diagnostics.js +8 -6
  38. package/dist/diff-expansion.d.ts +9 -0
  39. package/dist/diff-expansion.js +41 -0
  40. package/dist/diff-overlays.d.ts +15 -0
  41. package/dist/diff-overlays.js +28 -0
  42. package/dist/diff-syntax.d.ts +16 -0
  43. package/dist/diff-syntax.js +49 -0
  44. package/dist/diff-wordmarks.d.ts +5 -0
  45. package/dist/diff-wordmarks.js +26 -0
  46. package/dist/doc-comment-model.d.ts +23 -0
  47. package/dist/doc-comment-model.js +291 -0
  48. package/dist/editor-language.d.ts +13 -1
  49. package/dist/editor-language.js +15 -5
  50. package/dist/editor-react.css +79 -0
  51. package/dist/format-on-type.d.ts +14 -0
  52. package/dist/format-on-type.js +96 -0
  53. package/dist/host-types.d.ts +19 -0
  54. package/dist/hover.d.ts +4 -0
  55. package/dist/hover.js +15 -2
  56. package/dist/index.d.ts +19 -5
  57. package/dist/index.js +9 -1
  58. package/dist/inlay-hints.d.ts +47 -2
  59. package/dist/inlay-hints.js +353 -16
  60. package/dist/language-ids.js +4 -2
  61. package/dist/locales/en.json +117 -3
  62. package/dist/locales/nl.json +117 -3
  63. package/dist/models.d.ts +6 -2
  64. package/dist/models.js +5 -1
  65. package/dist/panel-size.d.ts +6 -0
  66. package/dist/panel-size.js +25 -0
  67. package/dist/patch-decorations.d.ts +27 -0
  68. package/dist/patch-decorations.js +182 -0
  69. package/dist/patch-layout.d.ts +40 -0
  70. package/dist/patch-layout.js +304 -0
  71. package/dist/peek.d.ts +3 -1
  72. package/dist/peek.js +6 -1
  73. package/dist/pick.d.ts +1 -0
  74. package/dist/pick.js +11 -1
  75. package/dist/popups.d.ts +17 -0
  76. package/dist/popups.js +1 -0
  77. package/dist/project-language.d.ts +12 -0
  78. package/dist/project-language.js +52 -2
  79. package/dist/refresher.d.ts +1 -0
  80. package/dist/refresher.js +5 -0
  81. package/dist/rename-model.d.ts +1 -1
  82. package/dist/rename-model.js +3 -7
  83. package/dist/rename.js +5 -8
  84. package/dist/rendered-docs.d.ts +64 -0
  85. package/dist/rendered-docs.js +374 -0
  86. package/dist/row-host.d.ts +2 -0
  87. package/dist/row-host.js +11 -3
  88. package/dist/semantic-delta.d.ts +2 -0
  89. package/dist/semantic-delta.js +27 -0
  90. package/dist/semantic-model.js +16 -0
  91. package/dist/semantic-tokens.d.ts +26 -1
  92. package/dist/semantic-tokens.js +174 -14
  93. package/dist/shortcut-keys.d.ts +2 -0
  94. package/dist/shortcut-keys.js +6 -2
  95. package/dist/signature.js +1 -1
  96. package/dist/snippet-field-transforms.d.ts +30 -0
  97. package/dist/snippet-field-transforms.js +116 -0
  98. package/dist/snippet-indent.d.ts +2 -0
  99. package/dist/snippet-indent.js +49 -0
  100. package/dist/snippet-range-order.d.ts +3 -0
  101. package/dist/snippet-range-order.js +19 -0
  102. package/dist/snippet-session.d.ts +42 -6
  103. package/dist/snippet-session.js +650 -53
  104. package/dist/snippet-transform-client.d.ts +19 -0
  105. package/dist/snippet-transform-client.js +66 -0
  106. package/dist/snippet-transform-format.d.ts +15 -0
  107. package/dist/snippet-transform-format.js +302 -0
  108. package/dist/snippet-transform-protocol.d.ts +45 -0
  109. package/dist/snippet-transform-protocol.js +8 -0
  110. package/dist/snippet-transform-queue.d.ts +32 -0
  111. package/dist/snippet-transform-queue.js +173 -0
  112. package/dist/snippet-transform-worker.d.ts +10 -0
  113. package/dist/snippet-transform-worker.js +41 -0
  114. package/dist/snippet-variable-names.d.ts +1 -0
  115. package/dist/snippet-variable-names.js +36 -0
  116. package/dist/snippet-variables.d.ts +7 -0
  117. package/dist/snippet-variables.js +66 -0
  118. package/dist/snippet.d.ts +30 -3
  119. package/dist/snippet.js +402 -87
  120. package/dist/split-alignment.d.ts +6 -0
  121. package/dist/split-alignment.js +38 -0
  122. package/dist/split-scroll.d.ts +14 -0
  123. package/dist/split-scroll.js +45 -0
  124. package/dist/symbols.d.ts +2 -1
  125. package/dist/symbols.js +39 -7
  126. package/dist/testing.d.ts +5 -1
  127. package/dist/testing.js +12 -0
  128. package/dist/timers.d.ts +1 -0
  129. package/dist/timers.js +4 -0
  130. package/dist/use-panel-body.d.ts +6 -0
  131. package/dist/use-panel-body.js +13 -0
  132. package/dist/workspace-edit.d.ts +25 -3
  133. package/dist/workspace-edit.js +282 -143
  134. package/package.json +10 -5
  135. package/src/CallHierarchyPanel.tsx +244 -0
  136. package/src/CompletionPopup.tsx +12 -1
  137. package/src/DiffView.tsx +262 -0
  138. package/src/EditorContextMenu.tsx +1 -0
  139. package/src/EditorView.tsx +21 -2
  140. package/src/FindReplace.tsx +51 -13
  141. package/src/HoverCard.tsx +23 -21
  142. package/src/LanguagePopups.tsx +5 -0
  143. package/src/Markdown.tsx +10 -3
  144. package/src/PanelResizeHandle.tsx +104 -0
  145. package/src/PeekPanel.tsx +11 -6
  146. package/src/PickPopup.tsx +18 -2
  147. package/src/RenameCard.tsx +18 -0
  148. package/src/RenderedDocs.tsx +155 -0
  149. package/src/SplitDiffView.tsx +337 -0
  150. package/src/SymbolPicker.tsx +4 -3
  151. package/src/call-hierarchy-model.ts +151 -0
  152. package/src/call-hierarchy.ts +440 -0
  153. package/src/code-actions-model.ts +17 -11
  154. package/src/code-actions.ts +3 -1
  155. package/src/completion-model.ts +61 -35
  156. package/src/completion.ts +302 -42
  157. package/src/diagnostics.ts +8 -6
  158. package/src/diff-expansion.ts +49 -0
  159. package/src/diff-overlays.tsx +69 -0
  160. package/src/diff-syntax.ts +67 -0
  161. package/src/diff-wordmarks.ts +31 -0
  162. package/src/doc-comment-model.ts +336 -0
  163. package/src/editor-language.ts +22 -6
  164. package/src/editor-react.css +79 -0
  165. package/src/format-on-type.ts +111 -0
  166. package/src/host-types.ts +23 -0
  167. package/src/hover.ts +18 -3
  168. package/src/index.ts +27 -4
  169. package/src/inlay-hints.ts +394 -18
  170. package/src/language-ids.ts +4 -2
  171. package/src/locales/en.json +117 -3
  172. package/src/locales/nl.json +117 -3
  173. package/src/models.ts +40 -1
  174. package/src/panel-size.ts +28 -0
  175. package/src/patch-decorations.ts +224 -0
  176. package/src/patch-layout.ts +388 -0
  177. package/src/peek.ts +6 -1
  178. package/src/pick.ts +12 -1
  179. package/src/popups.ts +24 -0
  180. package/src/project-language.ts +63 -2
  181. package/src/refresher.ts +6 -0
  182. package/src/rename-model.ts +4 -8
  183. package/src/rename.ts +5 -8
  184. package/src/rendered-docs.ts +430 -0
  185. package/src/row-host.ts +12 -3
  186. package/src/semantic-delta.ts +29 -0
  187. package/src/semantic-model.ts +17 -0
  188. package/src/semantic-tokens.ts +202 -19
  189. package/src/shortcut-keys.ts +6 -2
  190. package/src/signature.ts +1 -1
  191. package/src/snippet-field-transforms.ts +144 -0
  192. package/src/snippet-indent.ts +53 -0
  193. package/src/snippet-range-order.ts +19 -0
  194. package/src/snippet-session.ts +797 -56
  195. package/src/snippet-transform-client.ts +84 -0
  196. package/src/snippet-transform-format.ts +325 -0
  197. package/src/snippet-transform-protocol.ts +24 -0
  198. package/src/snippet-transform-queue.ts +199 -0
  199. package/src/snippet-transform-worker.ts +58 -0
  200. package/src/snippet-variable-names.ts +36 -0
  201. package/src/snippet-variables.ts +74 -0
  202. package/src/snippet.ts +452 -89
  203. package/src/split-alignment.ts +45 -0
  204. package/src/split-scroll.ts +54 -0
  205. package/src/symbols.ts +43 -7
  206. package/src/testing.ts +25 -0
  207. package/src/timers.ts +5 -0
  208. package/src/use-panel-body.ts +17 -0
  209. 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,92 @@
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 { PanelResizeHandle } from './PanelResizeHandle.js';
11
+ import { usePanelBody } from './use-panel-body.js';
12
+ import { PANEL_BODY_HEIGHT } from './panel-size.js';
13
+ import { CALL_LEVEL_LIMIT } from './call-hierarchy-model.js';
14
+ import { basenameOf, relativeTo } from './paths.js';
15
+ import { PathText } from './PathText.js';
16
+ import { Preview } from './PeekPanel.js';
17
+ import { SymbolBadge } from './SymbolPicker.js';
18
+ const INDENT = 14;
19
+ const BASE_INDENT = 8;
20
+ function folderOf(path) {
21
+ return path.slice(0, Math.max(0, path.lastIndexOf('/')));
22
+ }
23
+ /* 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. */
24
+ function Line({ row, id, active, expanded, position, size, ref, children, onClick, onDoubleClick }) {
25
+ 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 }));
26
+ }
27
+ function Symbol({ language, row, active, id, ref }) {
28
+ const { t } = useTranslation('editor');
29
+ const { item } = row;
30
+ const path = relativeTo(fileUriToPath(item.uri) ?? item.uri, language.project.folder);
31
+ const tool = language.callHierarchy;
32
+ 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) => {
33
+ event.stopPropagation();
34
+ tool.toggle(row.id);
35
+ }, 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)}` }) })] })] }));
36
+ }
37
+ function Tree({ language, view }) {
38
+ const { t } = useTranslation('editor');
39
+ const treeId = useId();
40
+ const activeRow = useRef(null);
41
+ const idOf = (row) => `${treeId}-${row.id}`;
42
+ const activeId = view.rows.some((row) => row.id === view.active) ? `${treeId}-${view.active}` : undefined;
43
+ useEffect(() => {
44
+ activeRow.current?.scrollIntoView({ block: 'nearest' });
45
+ }, [view.active]);
46
+ // The focus stays in the text, so the input is what names the tree and its active row.
47
+ useLayoutEffect(() => {
48
+ language.editor.setInputPopup({ controls: treeId, activeDescendant: activeId });
49
+ return () => language.editor.setInputPopup(null);
50
+ }, [language, treeId, activeId]);
51
+ 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) => {
52
+ const active = row.id === view.active;
53
+ const ref = active ? activeRow : undefined;
54
+ if (row.kind === 'node') {
55
+ return _jsx(Symbol, { ref: ref, language: language, row: row, active: active, id: idOf(row) }, row.id);
56
+ }
57
+ if (row.kind === 'more') {
58
+ const next = Math.min(row.hidden, CALL_LEVEL_LIMIT);
59
+ 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));
60
+ }
61
+ 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));
62
+ }) }));
63
+ }
64
+ function StatusText({ row, direction }) {
65
+ const { t } = useTranslation('editor');
66
+ if (row.status === 'loading') {
67
+ 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')] }));
68
+ }
69
+ if (row.status === 'failed') {
70
+ return (_jsx("span", { className: "min-w-0 truncate font-sans text-xs text-status-error", children: t('language.callHierarchy.retry', { message: row.message ?? '' }) }));
71
+ }
72
+ return (_jsx("span", { className: "font-sans text-xs text-text-muted", children: t(direction === 'incoming' ? 'language.callHierarchy.noIncoming' : 'language.callHierarchy.noOutgoing') }));
73
+ }
74
+ /*
75
+ * The call hierarchy of a symbol in the row the editor leaves under the line: the tree of callers or callees on
76
+ * the left and the code of the active call on the right. Like the peek it portals into that row, which the
77
+ * editor makes again when it scrolls back into view.
78
+ */
79
+ export function CallHierarchyPanel({ language, view }) {
80
+ const { t } = useTranslation('editor');
81
+ const body = usePanelBody(language);
82
+ if (view.container === null) {
83
+ return null;
84
+ }
85
+ const folder = language.project.folder;
86
+ const path = view.preview === null ? null : fileUriToPath(view.preview.uri);
87
+ const shown = path === null ? '' : folderOf(relativeTo(path, folder));
88
+ return createPortal(_jsxs("div", { className: "flex flex-col bg-surface pt-[2px] font-sans text-xs text-text", style: { height: CALL_HIERARCHY_HEIGHT - PANEL_BODY_HEIGHT + body.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: [
89
+ { id: 'incoming', label: t('language.callHierarchy.incoming') },
90
+ { id: 'outgoing', label: t('language.callHierarchy.outgoing') }
91
+ ] }), _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 shrink-0", style: { height: body.height }, children: [_jsx(Tree, { language: language, view: view }), _jsx(Preview, { preview: view.preview })] }), _jsx("div", { className: "peek-hatch relative h-[14px]", children: _jsx(PanelResizeHandle, { container: view.container, height: body.height, onHeight: body.set, onCommit: body.commit }) })] }), view.container);
92
+ }
@@ -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;
@@ -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
  ];
@@ -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
  }
@@ -1,4 +1,4 @@
1
- import type { Editor } from '@adecore/editor';
1
+ import { type Editor } from '@adecore/editor';
2
2
  export declare function FindReplace({ editor, className, onClose }: {
3
3
  editor: Editor;
4
4
  className?: string;
@@ -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.noSelection
28
- ? t('find.noSelection')
29
- : t('find.count', { total: formatNumber(state.count), current: state.current === null ? '0' : formatNumber(state.current + 1) });
30
- return (_jsxs("div", { className: `adecore-editor-find ${className ?? ''}`, role: "search", "aria-label": t('find.label'), 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) => {
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
- if (event.key === 'Escape') {
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
  }
@@ -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
  }
@@ -1,3 +1,4 @@
1
- export declare function Markdown({ text }: {
1
+ export declare function Markdown({ text, images }: {
2
2
  text: string;
3
+ images?: boolean;
3
4
  }): import("react").JSX.Element;
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
- export function Markdown({ text }) {
6
- return (_jsx("div", { className: "adecore-editor-markdown", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], children: text }) }));
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
  }
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ container: HTMLElement;
3
+ height: number;
4
+ onHeight(height: number): void;
5
+ onCommit(height: number): void;
6
+ }
7
+ export declare function PanelResizeHandle({ container, height, onHeight, onCommit }: Props): import("react").JSX.Element;
8
+ export {};