@adecore/editor-react 0.38.0 → 0.40.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.js +30 -55
- package/dist/CodeAuthorsCard.js +0 -1
- package/dist/CompletionPopup.js +150 -181
- package/dist/DiffView.d.ts +1 -1
- package/dist/DiffView.js +2 -29
- package/dist/PickPopup.js +54 -80
- package/dist/SplitDiffView.js +27 -57
- package/dist/SymbolPicker.js +2 -8
- package/dist/code-actions-model.js +2 -9
- package/dist/code-actions.js +10 -11
- package/dist/code-vision.js +2 -1
- package/dist/completion-model.js +2 -2
- package/dist/completion.js +13 -34
- package/dist/definition-link.d.ts +1 -0
- package/dist/definition-link.js +22 -18
- package/dist/diagnostics-model.d.ts +1 -0
- package/dist/diagnostics-model.js +3 -1
- package/dist/diagnostics.js +1 -2
- package/dist/diff-syntax.d.ts +1 -0
- package/dist/diff-syntax.js +31 -0
- package/dist/doc-comment-model.d.ts +1 -0
- package/dist/doc-comment-model.js +1 -1
- package/dist/folding.js +2 -3
- package/dist/format-on-type.js +6 -10
- package/dist/hover-content.d.ts +0 -2
- package/dist/hover-content.js +1 -9
- package/dist/inlay-hints.d.ts +0 -1
- package/dist/inlay-hints.js +11 -41
- package/dist/navigation.js +4 -14
- package/dist/peek.js +2 -12
- package/dist/popup-focus.d.ts +3 -1
- package/dist/popup-focus.js +33 -0
- package/dist/recent-choices.d.ts +0 -1
- package/dist/recent-choices.js +0 -4
- package/dist/rendered-docs.d.ts +1 -0
- package/dist/rendered-docs.js +15 -28
- package/dist/snippet-field-transforms.d.ts +5 -0
- package/dist/snippet-field-transforms.js +27 -16
- package/dist/snippet-session.js +8 -20
- package/dist/symbol-links.js +3 -10
- package/dist/symbol-picker-model.d.ts +1 -0
- package/dist/symbol-picker-model.js +7 -0
- package/dist/unique.d.ts +10 -0
- package/dist/unique.js +15 -0
- package/package.json +9 -9
- package/src/AnchoredPopup.tsx +2 -0
- package/src/BaselineStatus.tsx +1 -0
- package/src/CallHierarchyPanel.tsx +16 -6
- package/src/ChangeReview.tsx +7 -0
- package/src/CodeAuthorsCard.tsx +2 -5
- package/src/CompletionPopup.tsx +21 -15
- package/src/DiffView.tsx +53 -30
- package/src/EditorContextMenu.tsx +7 -0
- package/src/EditorView.tsx +28 -0
- package/src/FindReplace.tsx +8 -0
- package/src/HoverCard.tsx +8 -0
- package/src/HoverSections.tsx +11 -0
- package/src/LanguagePopups.tsx +1 -0
- package/src/PanelResizeHandle.tsx +8 -0
- package/src/PathText.tsx +1 -0
- package/src/PeekPanel.tsx +13 -0
- package/src/PickPopup.tsx +7 -6
- package/src/RenameCard.tsx +13 -0
- package/src/RenderedDocs.tsx +11 -0
- package/src/SplitDiffView.tsx +91 -56
- package/src/SymbolPicker.tsx +10 -18
- package/src/call-hierarchy-model.ts +10 -0
- package/src/call-hierarchy.ts +82 -0
- package/src/code-actions-model.ts +21 -9
- package/src/code-actions.ts +83 -7
- package/src/code-authors.ts +20 -0
- package/src/code-vision-model.ts +37 -0
- package/src/code-vision.ts +47 -3
- package/src/completion-call.ts +19 -0
- package/src/completion-model.ts +58 -2
- package/src/completion.ts +156 -30
- package/src/conflict-model.ts +4 -0
- package/src/context-menu-rows.ts +1 -0
- package/src/context-menu.ts +7 -0
- package/src/definition-link.ts +42 -18
- package/src/diagnostics-model.ts +19 -1
- package/src/diagnostics.ts +31 -2
- package/src/diff-expansion.ts +9 -0
- package/src/diff-overlays.tsx +8 -0
- package/src/diff-syntax.ts +54 -0
- package/src/diff-wordmarks.ts +8 -0
- package/src/doc-comment-model.ts +51 -1
- package/src/editor-language.ts +11 -0
- package/src/find-query.ts +7 -0
- package/src/folding.ts +10 -3
- package/src/format-on-type.ts +23 -10
- package/src/highlight-layers.ts +3 -0
- package/src/highlights.ts +18 -0
- package/src/history.ts +11 -0
- package/src/holder.ts +3 -0
- package/src/hover-content.ts +34 -17
- package/src/hover.ts +57 -0
- package/src/index.ts +4 -0
- package/src/inlay-hints.ts +86 -47
- package/src/key-priority.ts +2 -0
- package/src/line-edits.ts +8 -0
- package/src/models.ts +6 -0
- package/src/navigation-history.ts +5 -0
- package/src/navigation.ts +28 -13
- package/src/panel-size.ts +7 -0
- package/src/patch-decorations.ts +39 -0
- package/src/patch-layout.ts +47 -0
- package/src/peek-model.ts +21 -0
- package/src/peek.ts +49 -13
- package/src/pick.ts +35 -0
- package/src/popup-focus.ts +11 -1
- package/src/popup-placement.ts +5 -0
- package/src/popups.ts +2 -6
- package/src/project-language.ts +33 -0
- package/src/project-problems.ts +11 -0
- package/src/proposal-model.ts +40 -0
- package/src/recent-choices.ts +3 -5
- package/src/refresher.ts +3 -0
- package/src/rename-model.ts +28 -0
- package/src/rename.ts +56 -0
- package/src/rendered-docs.ts +89 -19
- package/src/review-group.ts +4 -0
- package/src/row-host.ts +12 -0
- package/src/selection-ranges.ts +6 -0
- package/src/semantic-delta.ts +7 -0
- package/src/semantic-model.ts +11 -0
- package/src/semantic-tokens.ts +37 -0
- package/src/shortcut-keys.ts +3 -0
- package/src/signature-model.ts +7 -0
- package/src/signature.ts +27 -0
- package/src/snippet-field-transforms.ts +48 -13
- package/src/snippet-indent.ts +10 -0
- package/src/snippet-range-order.ts +5 -0
- package/src/snippet-session.ts +173 -31
- package/src/snippet-transform-client.ts +14 -0
- package/src/snippet-transform-format.ts +98 -0
- package/src/snippet-transform-protocol.ts +5 -0
- package/src/snippet-transform-queue.ts +53 -0
- package/src/snippet-transform-worker.ts +14 -0
- package/src/snippet-variables.ts +9 -0
- package/src/snippet.ts +107 -0
- package/src/split-alignment.ts +10 -0
- package/src/split-scroll.ts +8 -0
- package/src/symbol-links.ts +9 -12
- package/src/symbol-picker-model.ts +25 -0
- package/src/symbol-picker.ts +9 -0
- package/src/symbols.ts +21 -0
- package/src/testing.ts +7 -0
- package/src/timers.ts +5 -0
- package/src/unique.ts +23 -0
- package/src/use-problem-counts.ts +4 -0
- package/src/view-state.ts +9 -0
- package/src/word-diff.ts +5 -0
- package/src/workspace-edit.ts +84 -0
package/src/DiffView.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import { diffDisplay, diffSplit, type DiffDocument, type DiffDisplay } from '@ad
|
|
|
6
6
|
import { wordMarks } from './diff-wordmarks.ts';
|
|
7
7
|
import { DiffOverlays, type MountedDiff } from './diff-overlays.tsx';
|
|
8
8
|
import { SplitDiffView } from './SplitDiffView.tsx';
|
|
9
|
-
import type
|
|
9
|
+
import { repaintOnTokens, type DiffSourceTokens } from './diff-syntax.ts';
|
|
10
10
|
import { LineActionHost, RowHost } from './row-host.ts';
|
|
11
11
|
import { DEFAULT_UNFOLD_LIMITS, expandDiff, type DiffLimits } from './diff-expansion.ts';
|
|
12
12
|
|
|
@@ -60,15 +60,20 @@ function UnifiedDiffView({
|
|
|
60
60
|
const applied = useRef({ theme, wrap, label });
|
|
61
61
|
const [mounted, setMounted] = useState<MountedDiff | null>(null);
|
|
62
62
|
const [height, setHeight] = useState(0);
|
|
63
|
+
|
|
63
64
|
useLayoutEffect(() => {
|
|
64
65
|
current.current = { display, tokens, theme, wrap, label };
|
|
65
66
|
});
|
|
67
|
+
|
|
66
68
|
useLayoutEffect(() => {
|
|
67
69
|
const host = element.current;
|
|
70
|
+
|
|
68
71
|
if (host === null) {
|
|
69
72
|
return;
|
|
70
73
|
}
|
|
74
|
+
|
|
71
75
|
const initial = current.current;
|
|
76
|
+
|
|
72
77
|
const editor = engine.mount(host, {
|
|
73
78
|
text: initial.display.text,
|
|
74
79
|
theme: initial.theme,
|
|
@@ -81,38 +86,53 @@ function UnifiedDiffView({
|
|
|
81
86
|
// Prompt cards use this chord for their primary action; a read-only review has no split-line action.
|
|
82
87
|
handBack: [parseChord('Mod+Enter')]
|
|
83
88
|
});
|
|
89
|
+
|
|
84
90
|
applied.current = { theme: initial.theme, wrap: initial.wrap, label: initial.label };
|
|
85
91
|
editor.setBlocks([]);
|
|
92
|
+
|
|
86
93
|
editor.setLineTokens((line) => {
|
|
87
94
|
const { display, tokens } = current.current;
|
|
88
95
|
const row = display.rows[line];
|
|
96
|
+
|
|
89
97
|
return row === undefined ? null : tokens?.get(row.new === undefined ? 'old' : 'new', (row.new ?? row.old)!.index);
|
|
90
98
|
});
|
|
99
|
+
|
|
91
100
|
setMounted({ engine, editor, rows: new RowHost(editor, 'diff-gaps'), actions: new LineActionHost(editor, 'diff-status') });
|
|
101
|
+
|
|
92
102
|
const measure = (): void => setHeight(editor.getContentHeight());
|
|
103
|
+
|
|
93
104
|
const stop = editor.onViewChange(measure);
|
|
94
105
|
measure();
|
|
106
|
+
|
|
95
107
|
return () => {
|
|
96
108
|
stop();
|
|
97
109
|
editor.dispose();
|
|
98
110
|
};
|
|
99
111
|
}, [engine]);
|
|
112
|
+
|
|
100
113
|
useLayoutEffect(() => {
|
|
101
114
|
if (mounted === null || mounted.engine !== engine) {
|
|
102
115
|
return;
|
|
103
116
|
}
|
|
117
|
+
|
|
104
118
|
const { editor, rows, actions } = mounted;
|
|
105
119
|
editor.setText(display.text);
|
|
120
|
+
|
|
106
121
|
editor.setLineNumbers((line) => {
|
|
107
122
|
const row = current.current.display.rows[line];
|
|
123
|
+
|
|
108
124
|
return row === undefined ? null : String((row.new ?? row.old)!.number);
|
|
109
125
|
});
|
|
126
|
+
|
|
110
127
|
const highlights: EditorLineHighlight[] = [];
|
|
128
|
+
|
|
111
129
|
for (const [index, row] of display.rows.entries()) {
|
|
112
130
|
if (row.change !== 'delete' && row.change !== 'insert') {
|
|
113
131
|
continue;
|
|
114
132
|
}
|
|
133
|
+
|
|
115
134
|
const removed = row.change === 'delete';
|
|
135
|
+
|
|
116
136
|
highlights.push({
|
|
117
137
|
startLine: index + 1,
|
|
118
138
|
endLine: index + 1,
|
|
@@ -121,7 +141,9 @@ function UnifiedDiffView({
|
|
|
121
141
|
emphasis: [(removed ? emphasis.old.get(row.old!.index) : emphasis.new.get(row.new!.index)) ?? []]
|
|
122
142
|
});
|
|
123
143
|
}
|
|
144
|
+
|
|
124
145
|
editor.setLineHighlights(highlights);
|
|
146
|
+
|
|
125
147
|
editor.setChangeMarks(
|
|
126
148
|
highlights.map((highlight) => ({
|
|
127
149
|
startLine: highlight.startLine,
|
|
@@ -129,6 +151,7 @@ function UnifiedDiffView({
|
|
|
129
151
|
kind: highlight.bar === 'hatched' ? 'deleted' : 'added'
|
|
130
152
|
}))
|
|
131
153
|
);
|
|
154
|
+
|
|
132
155
|
rows.set(
|
|
133
156
|
display.gaps.map(({ before, row }) => ({
|
|
134
157
|
id: `gap-${row.id}`,
|
|
@@ -137,87 +160,79 @@ function UnifiedDiffView({
|
|
|
137
160
|
height: 32
|
|
138
161
|
}))
|
|
139
162
|
);
|
|
163
|
+
|
|
140
164
|
actions.set(display.rows.flatMap((row, line) => (row.noNewline === undefined ? [] : [{ id: `no-newline-${line}`, line }])));
|
|
141
165
|
}, [mounted, engine, display, emphasis]);
|
|
166
|
+
|
|
142
167
|
useLayoutEffect(() => {
|
|
143
168
|
if (mounted === null || mounted.engine !== engine) {
|
|
144
169
|
return;
|
|
145
170
|
}
|
|
171
|
+
|
|
146
172
|
// 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.
|
|
147
173
|
mounted.editor.setLineLabels((line) => {
|
|
148
174
|
const row = display.rows[line];
|
|
175
|
+
|
|
149
176
|
if (row?.change === 'delete') {
|
|
150
177
|
return t('diff.removed', { number: row.old!.number });
|
|
151
178
|
}
|
|
179
|
+
|
|
152
180
|
if (row?.change === 'insert') {
|
|
153
181
|
return t('diff.added', { number: row.new!.number });
|
|
154
182
|
}
|
|
183
|
+
|
|
155
184
|
return row?.old !== undefined && row.new !== undefined ? t('diff.unchanged', { old: row.old.number, new: row.new.number }) : null;
|
|
156
185
|
});
|
|
157
186
|
}, [mounted, engine, display, t]);
|
|
187
|
+
|
|
158
188
|
useLayoutEffect(() => {
|
|
159
189
|
if (mounted === null || mounted.engine !== engine) {
|
|
160
190
|
return;
|
|
161
191
|
}
|
|
192
|
+
|
|
162
193
|
const previous = applied.current;
|
|
194
|
+
|
|
163
195
|
if (previous.theme !== theme) {
|
|
164
196
|
mounted.editor.setTheme(theme);
|
|
165
197
|
}
|
|
198
|
+
|
|
166
199
|
if (previous.wrap !== wrap) {
|
|
167
200
|
mounted.editor.setWrap(wrap);
|
|
168
201
|
}
|
|
202
|
+
|
|
169
203
|
if (previous.label !== label) {
|
|
170
204
|
mounted.editor.setLabel(label);
|
|
171
205
|
}
|
|
206
|
+
|
|
172
207
|
applied.current = { theme, wrap, label };
|
|
173
208
|
}, [mounted, engine, theme, wrap, label]);
|
|
209
|
+
|
|
174
210
|
useLayoutEffect(() => {
|
|
175
211
|
if (mounted === null || mounted.engine !== engine) {
|
|
176
212
|
return;
|
|
177
213
|
}
|
|
214
|
+
|
|
178
215
|
const refresh = (): void => {
|
|
179
216
|
mounted.editor.setLineTokens((line) => {
|
|
180
217
|
const row = current.current.display.rows[line];
|
|
218
|
+
|
|
181
219
|
return row === undefined ? null : current.current.tokens?.get(row.new === undefined ? 'old' : 'new', (row.new ?? row.old)!.index);
|
|
182
220
|
});
|
|
183
221
|
};
|
|
222
|
+
|
|
184
223
|
refresh();
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
let stopped = false;
|
|
188
|
-
let frame: number | undefined;
|
|
189
|
-
const schedule = (): void => {
|
|
190
|
-
if (pending) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
pending = true;
|
|
194
|
-
const paint = (): void => {
|
|
195
|
-
pending = false;
|
|
196
|
-
if (!stopped) {
|
|
197
|
-
refresh();
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
if (ownerWindow?.requestAnimationFrame !== undefined) {
|
|
201
|
-
frame = ownerWindow.requestAnimationFrame(paint);
|
|
202
|
-
} else {
|
|
203
|
-
queueMicrotask(paint);
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
const stop = tokens?.subscribe(schedule);
|
|
207
|
-
return () => {
|
|
208
|
-
stopped = true;
|
|
209
|
-
stop?.();
|
|
210
|
-
if (frame !== undefined) {
|
|
211
|
-
ownerWindow?.cancelAnimationFrame?.(frame);
|
|
212
|
-
}
|
|
213
|
-
};
|
|
224
|
+
|
|
225
|
+
return repaintOnTokens(element.current?.ownerDocument.defaultView, tokens, refresh);
|
|
214
226
|
}, [mounted, engine, tokens, display]);
|
|
227
|
+
|
|
215
228
|
useLayoutEffect(() => {
|
|
216
229
|
if (mounted !== null && mounted.engine === engine) {
|
|
217
230
|
return onMount?.(mounted.editor);
|
|
218
231
|
}
|
|
219
232
|
}, [mounted, engine, onMount]);
|
|
233
|
+
|
|
220
234
|
const limit = maxHeight !== undefined && Number.isFinite(maxHeight) && maxHeight > 0 ? maxHeight : Infinity;
|
|
235
|
+
|
|
221
236
|
return (
|
|
222
237
|
<div
|
|
223
238
|
className={`adecore-editor-diff ${className ?? ''}`}
|
|
@@ -233,30 +248,38 @@ function UnifiedDiffView({
|
|
|
233
248
|
|
|
234
249
|
export function DiffView(props: DiffViewProps) {
|
|
235
250
|
const { document, unfoldLimits = DEFAULT_UNFOLD_LIMITS } = props;
|
|
251
|
+
|
|
236
252
|
const [expanded, setExpanded] = useState<{ document: DiffDocument; gaps: ReadonlyMap<number, number>; blocked: ReadonlySet<number> }>(() => ({
|
|
237
253
|
document,
|
|
238
254
|
gaps: new Map(),
|
|
239
255
|
blocked: new Set()
|
|
240
256
|
}));
|
|
257
|
+
|
|
241
258
|
const gaps = expanded.document === document ? expanded.gaps : undefined;
|
|
242
259
|
const blocked = expanded.document === document ? expanded.blocked : new Set<number>();
|
|
243
260
|
const projection = props.layout === 'split' ? diffSplit(document, gaps) : diffDisplay(document, gaps);
|
|
261
|
+
|
|
244
262
|
const unfold = (id: number): void =>
|
|
245
263
|
setExpanded((previous) => {
|
|
246
264
|
const gaps = previous.document === document ? previous.gaps : new Map<number, number>();
|
|
247
265
|
const next = expandDiff(document, gaps, id, unfoldLimits);
|
|
266
|
+
|
|
248
267
|
return {
|
|
249
268
|
document,
|
|
250
269
|
gaps: next,
|
|
251
270
|
blocked: new Set([...(previous.document === document ? previous.blocked : []), ...(next === gaps ? [id] : [])])
|
|
252
271
|
};
|
|
253
272
|
});
|
|
273
|
+
|
|
254
274
|
if ('aligned' in projection && projection.old.lines.length > 0 && projection.new.lines.length > 0) {
|
|
255
275
|
const visibleRows = projection.aligned.reduce((count, row) => count + (row.kind === 'gap' ? 0 : row.row.change === 'pair' ? 2 : 1), 0);
|
|
256
276
|
const wrap = props.wrap !== false && visibleRows <= (props.splitWrapLimit ?? 2000);
|
|
277
|
+
|
|
257
278
|
return <SplitDiffView {...props} wrap={wrap} display={projection} unfold={unfold} blocked={blocked} />;
|
|
258
279
|
}
|
|
280
|
+
|
|
259
281
|
// An absent side has no selectable text; keep additions and deletions in the unified editor.
|
|
260
282
|
const display = 'aligned' in projection ? (projection.old.lines.length > 0 ? projection.old : projection.new) : projection;
|
|
283
|
+
|
|
261
284
|
return <UnifiedDiffView {...props} display={display} unfold={unfold} blocked={blocked} />;
|
|
262
285
|
}
|
|
@@ -13,11 +13,14 @@ import type { MenuView } from './popups.ts';
|
|
|
13
13
|
export function EditorContextMenu({ language, view, children }: { language: EditorLanguage; view: MenuView; children?: ReactNode }) {
|
|
14
14
|
const { t } = useTranslation('editor');
|
|
15
15
|
const trigger = useRef<HTMLDivElement>(null);
|
|
16
|
+
|
|
16
17
|
useEffect(() => {
|
|
17
18
|
trigger.current?.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, clientX: view.x, clientY: view.y }));
|
|
18
19
|
}, [view.x, view.y]);
|
|
20
|
+
|
|
19
21
|
const { service } = language.project;
|
|
20
22
|
const groups = menuGroupsOf((method) => service.supports(method, language.uri));
|
|
23
|
+
|
|
21
24
|
const run: Record<MenuRowId, () => void> = {
|
|
22
25
|
definition: () => void language.navigation.go('definition'),
|
|
23
26
|
declaration: () => void language.navigation.go('declaration'),
|
|
@@ -28,6 +31,7 @@ export function EditorContextMenu({ language, view, children }: { language: Edit
|
|
|
28
31
|
renameSymbol: () => void language.rename.start(),
|
|
29
32
|
codeActions: () => void language.codeActions.open()
|
|
30
33
|
};
|
|
34
|
+
|
|
31
35
|
const label = (id: MenuRowId): string => {
|
|
32
36
|
switch (id) {
|
|
33
37
|
case 'definition':
|
|
@@ -41,8 +45,10 @@ export function EditorContextMenu({ language, view, children }: { language: Edit
|
|
|
41
45
|
return t(`commands.${id}`);
|
|
42
46
|
}
|
|
43
47
|
};
|
|
48
|
+
|
|
44
49
|
const item = (row: MenuRowSpec): ReactNode => {
|
|
45
50
|
const shortcut = row.shortcut === null ? null : language.shortcuts[row.shortcut];
|
|
51
|
+
|
|
46
52
|
return (
|
|
47
53
|
<ContextMenu.Item key={row.id} onClick={run[row.id]}>
|
|
48
54
|
{label(row.id)}
|
|
@@ -50,6 +56,7 @@ export function EditorContextMenu({ language, view, children }: { language: Edit
|
|
|
50
56
|
</ContextMenu.Item>
|
|
51
57
|
);
|
|
52
58
|
};
|
|
59
|
+
|
|
53
60
|
return (
|
|
54
61
|
<ContextMenu.Root onOpenChange={(open) => !open && language.contextMenu.close()}>
|
|
55
62
|
<ContextMenu.Trigger ref={trigger} className="fixed top-0 left-0 h-0 w-0" aria-hidden />
|
package/src/EditorView.tsx
CHANGED
|
@@ -50,6 +50,7 @@ const DEFAULT_INDENTATION: EditorIndentation = { tabSize: 4, insertSpaces: true
|
|
|
50
50
|
/* The names of the fold controls in the editor's `{start}` and `{end}` placeholders, which the editor fills in per control. */
|
|
51
51
|
function foldMessages(t: TFunction<'editor'>): Pick<EditorMessages, 'fold' | 'unfold'> {
|
|
52
52
|
const lines = { start: '{start}', end: '{end}' };
|
|
53
|
+
|
|
53
54
|
return { fold: t('language.fold.fold', lines), unfold: t('language.fold.unfold', lines) };
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -57,10 +58,13 @@ function sameFields<T extends object>(left: T | undefined, right: T | undefined)
|
|
|
57
58
|
if (left === right) {
|
|
58
59
|
return true;
|
|
59
60
|
}
|
|
61
|
+
|
|
60
62
|
if (left === undefined || right === undefined) {
|
|
61
63
|
return false;
|
|
62
64
|
}
|
|
65
|
+
|
|
63
66
|
const keys = new Set([...Object.keys(left), ...Object.keys(right)] as (keyof T)[]);
|
|
67
|
+
|
|
64
68
|
return [...keys].every((key) => left[key] === right[key]);
|
|
65
69
|
}
|
|
66
70
|
|
|
@@ -69,34 +73,44 @@ function applyChangedOptions(editor: Editor, before: EditorOptions, after: Edito
|
|
|
69
73
|
if (before.theme !== after.theme) {
|
|
70
74
|
editor.setTheme(after.theme);
|
|
71
75
|
}
|
|
76
|
+
|
|
72
77
|
if (before.readOnly !== after.readOnly || before.readOnlyReason !== after.readOnlyReason) {
|
|
73
78
|
editor.setReadOnly(after.readOnly ?? false, after.readOnlyReason);
|
|
74
79
|
}
|
|
80
|
+
|
|
75
81
|
if (before.wrap !== after.wrap) {
|
|
76
82
|
editor.setWrap(after.wrap ?? false);
|
|
77
83
|
}
|
|
84
|
+
|
|
78
85
|
if (!sameFields(before.indentation, after.indentation)) {
|
|
79
86
|
editor.setIndentation(after.indentation ?? DEFAULT_INDENTATION);
|
|
80
87
|
}
|
|
88
|
+
|
|
81
89
|
if (!sameFields<Partial<EditorSmartKeys>>(before.smartKeys, after.smartKeys)) {
|
|
82
90
|
// The setter keeps the keys left out, where the options mean the default.
|
|
83
91
|
editor.setSmartKeys({ ...DEFAULT_SMART_KEYS, ...after.smartKeys });
|
|
84
92
|
}
|
|
93
|
+
|
|
85
94
|
if (before.guides !== after.guides) {
|
|
86
95
|
editor.setGuides(after.guides ?? true);
|
|
87
96
|
}
|
|
97
|
+
|
|
88
98
|
if (before.stickyScroll !== after.stickyScroll) {
|
|
89
99
|
editor.setStickyScroll?.(after.stickyScroll ?? true);
|
|
90
100
|
}
|
|
101
|
+
|
|
91
102
|
if (before.whitespace !== after.whitespace) {
|
|
92
103
|
editor.setWhitespace(after.whitespace ?? false);
|
|
93
104
|
}
|
|
105
|
+
|
|
94
106
|
if (before.rightMargin !== after.rightMargin) {
|
|
95
107
|
editor.setRightMargin(after.rightMargin ?? null);
|
|
96
108
|
}
|
|
109
|
+
|
|
97
110
|
if (before.foldOutline !== after.foldOutline) {
|
|
98
111
|
editor.setFoldOutline(after.foldOutline ?? 'hover');
|
|
99
112
|
}
|
|
113
|
+
|
|
100
114
|
if (after.label !== undefined && before.label !== after.label) {
|
|
101
115
|
editor.setLabel(after.label);
|
|
102
116
|
}
|
|
@@ -124,24 +138,31 @@ export function EditorView({
|
|
|
124
138
|
const applied = useRef(options);
|
|
125
139
|
const holder = useMemo(() => createHolder<EditorLanguage>(), []);
|
|
126
140
|
const language = useSyncExternalStore(holder.subscribe, holder.get, holder.get);
|
|
141
|
+
|
|
127
142
|
useLayoutEffect(() => {
|
|
128
143
|
latest.current = { options, timers, languageOptions, onMount };
|
|
129
144
|
});
|
|
145
|
+
|
|
130
146
|
useLayoutEffect(() => {
|
|
131
147
|
const host = element.current;
|
|
148
|
+
|
|
132
149
|
if (host === null) {
|
|
133
150
|
return;
|
|
134
151
|
}
|
|
152
|
+
|
|
135
153
|
const inputs = latest.current;
|
|
136
154
|
const editor = engine.mount(host, { ...inputs.options, messages: { ...foldMessages(words.current), ...inputs.options.messages } });
|
|
137
155
|
mounted.current = editor;
|
|
138
156
|
applied.current = inputs.options;
|
|
157
|
+
|
|
139
158
|
const attached =
|
|
140
159
|
project === undefined || uri === undefined || languageId === undefined
|
|
141
160
|
? null
|
|
142
161
|
: new EditorLanguage(project, editor, uri, languageId, inputs.timers, inputs.languageOptions);
|
|
162
|
+
|
|
143
163
|
holder.set(attached);
|
|
144
164
|
const stop = inputs.onMount?.(editor, attached);
|
|
165
|
+
|
|
145
166
|
return () => {
|
|
146
167
|
mounted.current = null;
|
|
147
168
|
holder.set(null);
|
|
@@ -150,24 +171,31 @@ export function EditorView({
|
|
|
150
171
|
editor.dispose();
|
|
151
172
|
};
|
|
152
173
|
}, [engine, project, uri, languageId, holder]);
|
|
174
|
+
|
|
153
175
|
useLayoutEffect(() => {
|
|
154
176
|
const editor = mounted.current;
|
|
177
|
+
|
|
155
178
|
if (editor === null || applied.current === options) {
|
|
156
179
|
return;
|
|
157
180
|
}
|
|
181
|
+
|
|
158
182
|
applyChangedOptions(editor, applied.current, options);
|
|
159
183
|
applied.current = options;
|
|
160
184
|
});
|
|
185
|
+
|
|
161
186
|
const { fold, unfold, noMoreOccurrences } = options.messages ?? {};
|
|
187
|
+
|
|
162
188
|
useLayoutEffect(() => {
|
|
163
189
|
words.current = t;
|
|
164
190
|
const localized = foldMessages(t);
|
|
191
|
+
|
|
165
192
|
mounted.current?.setMessages({
|
|
166
193
|
fold: fold ?? localized.fold,
|
|
167
194
|
unfold: unfold ?? localized.unfold,
|
|
168
195
|
...(noMoreOccurrences === undefined ? {} : { noMoreOccurrences })
|
|
169
196
|
});
|
|
170
197
|
}, [t, fold, unfold, noMoreOccurrences]);
|
|
198
|
+
|
|
171
199
|
return (
|
|
172
200
|
<div className={className} ref={ref} style={{ position: 'relative', minHeight: 0 }}>
|
|
173
201
|
<div ref={element} style={{ position: 'absolute', inset: 0 }} />
|
package/src/FindReplace.tsx
CHANGED
|
@@ -14,12 +14,15 @@ export function FindReplace({ editor, className, onClose }: { editor: Editor; cl
|
|
|
14
14
|
const root = useRef<HTMLDivElement>(null);
|
|
15
15
|
const compiled = compileFind(query);
|
|
16
16
|
useEffect(() => editor.onFind(setState), [editor]);
|
|
17
|
+
|
|
17
18
|
useEffect(() => {
|
|
18
19
|
editor.find(compiled.kind === 'pattern' ? query : null);
|
|
19
20
|
}, [editor, query, compiled]);
|
|
21
|
+
|
|
20
22
|
useEffect(() => {
|
|
21
23
|
editor.setReplacePreview(compiled.kind === 'pattern' ? replacement : null, { preserveCase });
|
|
22
24
|
}, [editor, compiled, replacement, preserveCase]);
|
|
25
|
+
|
|
23
26
|
useEffect(
|
|
24
27
|
() => () => {
|
|
25
28
|
editor.endFind();
|
|
@@ -27,6 +30,7 @@ export function FindReplace({ editor, className, onClose }: { editor: Editor; cl
|
|
|
27
30
|
},
|
|
28
31
|
[editor]
|
|
29
32
|
);
|
|
33
|
+
|
|
30
34
|
// The host closes the widget by unmounting it, with the keyboard in it or not; only a keyboard in it is handed back.
|
|
31
35
|
useLayoutEffect(
|
|
32
36
|
() => () => {
|
|
@@ -36,7 +40,9 @@ export function FindReplace({ editor, className, onClose }: { editor: Editor; cl
|
|
|
36
40
|
},
|
|
37
41
|
[editor]
|
|
38
42
|
);
|
|
43
|
+
|
|
39
44
|
const failure = state.failed ?? state.captureFailed;
|
|
45
|
+
|
|
40
46
|
const label =
|
|
41
47
|
compiled.kind === 'invalid'
|
|
42
48
|
? t('find.invalid')
|
|
@@ -61,6 +67,7 @@ export function FindReplace({ editor, className, onClose }: { editor: Editor; cl
|
|
|
61
67
|
total: formatNumber(state.count),
|
|
62
68
|
current: state.current === null ? '0' : formatNumber(state.current + 1)
|
|
63
69
|
});
|
|
70
|
+
|
|
64
71
|
return (
|
|
65
72
|
<div
|
|
66
73
|
ref={root}
|
|
@@ -87,6 +94,7 @@ export function FindReplace({ editor, className, onClose }: { editor: Editor; cl
|
|
|
87
94
|
if (event.nativeEvent.isComposing) {
|
|
88
95
|
return;
|
|
89
96
|
}
|
|
97
|
+
|
|
90
98
|
if (event.key === 'Enter') {
|
|
91
99
|
event.preventDefault();
|
|
92
100
|
editor.findStep(event.shiftKey ? -1 : 1);
|
package/src/HoverCard.tsx
CHANGED
|
@@ -22,12 +22,15 @@ export function DefinitionPlace({ definition, uri, folder }: { definition: Locat
|
|
|
22
22
|
const { t } = useTranslation('editor');
|
|
23
23
|
const path = fileUriToPath(definition.uri);
|
|
24
24
|
const line = formatNumber(definition.range.start.line + 1);
|
|
25
|
+
|
|
25
26
|
if (path === null || (folder !== '' && !path.startsWith(`${folder}/`))) {
|
|
26
27
|
return null;
|
|
27
28
|
}
|
|
29
|
+
|
|
28
30
|
if (definition.uri === uri) {
|
|
29
31
|
return <span className="text-text-faint">{t('language.hover.line', { line })}</span>;
|
|
30
32
|
}
|
|
33
|
+
|
|
31
34
|
return (
|
|
32
35
|
<span className="flex min-w-0 font-mono text-text-faint">
|
|
33
36
|
<span className="truncate">{basenameOf(path)}</span>
|
|
@@ -40,9 +43,12 @@ function ProblemSection({ problem, language }: { problem: Problem; language: Edi
|
|
|
40
43
|
const { t } = useTranslation('editor');
|
|
41
44
|
const { diagnostic } = problem;
|
|
42
45
|
const severity = severityOf(diagnostic);
|
|
46
|
+
|
|
43
47
|
const label =
|
|
44
48
|
diagnostic.source === undefined ? [codeLabelOf(diagnostic), problem.server].filter((part) => part !== '').join(' · ') : codeLabelOf(diagnostic);
|
|
49
|
+
|
|
45
50
|
const supported = language.codeActions.supported;
|
|
51
|
+
|
|
46
52
|
return (
|
|
47
53
|
<div className="flex flex-col gap-1.5 px-3 py-2.5">
|
|
48
54
|
<div className="flex items-start gap-2">
|
|
@@ -52,6 +58,7 @@ function ProblemSection({ problem, language }: { problem: Problem; language: Edi
|
|
|
52
58
|
{label !== '' && <div className="pl-[22px] text-xs text-text-muted select-text">{label}</div>}
|
|
53
59
|
{diagnostic.relatedInformation?.map((related, index) => {
|
|
54
60
|
const path = fileUriToPath(related.location.uri);
|
|
61
|
+
|
|
55
62
|
return (
|
|
56
63
|
<div key={index} className="pl-[22px] text-xs text-text-muted">
|
|
57
64
|
{path !== null && (
|
|
@@ -101,6 +108,7 @@ export function HoverCard({
|
|
|
101
108
|
footer?: ReactNode;
|
|
102
109
|
}) {
|
|
103
110
|
const { t } = useTranslation('editor');
|
|
111
|
+
|
|
104
112
|
return (
|
|
105
113
|
<div className="flex w-max min-w-[280px] max-w-[min(520px,calc(100vw-16px))] flex-col divide-y divide-border">
|
|
106
114
|
{info !== null && (
|
package/src/HoverSections.tsx
CHANGED
|
@@ -18,10 +18,13 @@ export function Signature({ code, language, onName }: { code: string; language:
|
|
|
18
18
|
if (highlight === undefined) {
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
|
+
|
|
21
22
|
let alive = true;
|
|
23
|
+
|
|
22
24
|
highlight(code, language, theme)
|
|
23
25
|
.then((result) => alive && setHtml({ key, html: linked ? linkTypeNames(result) : result }))
|
|
24
26
|
.catch(() => undefined);
|
|
27
|
+
|
|
25
28
|
return () => {
|
|
26
29
|
alive = false;
|
|
27
30
|
};
|
|
@@ -29,13 +32,16 @@ export function Signature({ code, language, onName }: { code: string; language:
|
|
|
29
32
|
|
|
30
33
|
const className =
|
|
31
34
|
'font-mono text-code break-words whitespace-pre-wrap [&_.line]:block [&_pre]:m-0 [&_pre]:bg-transparent! [&_pre]:whitespace-pre-wrap [&_code]:font-mono';
|
|
35
|
+
|
|
32
36
|
// One handler for every name, since the names are part of the highlighted HTML and not elements of ours.
|
|
33
37
|
const follow = (event: MouseEvent<HTMLDivElement>): void => {
|
|
34
38
|
const name = (event.target as HTMLElement).closest<HTMLElement>('[data-symbol]')?.dataset.symbol;
|
|
39
|
+
|
|
35
40
|
if (name !== undefined && onName !== undefined) {
|
|
36
41
|
onName(name);
|
|
37
42
|
}
|
|
38
43
|
};
|
|
44
|
+
|
|
39
45
|
return html?.key === key ? (
|
|
40
46
|
<div className={className} onClick={follow} dangerouslySetInnerHTML={{ __html: html.html }} />
|
|
41
47
|
) : (
|
|
@@ -63,6 +69,7 @@ export function DocTags({ tags }: { tags: readonly DocTag[] }) {
|
|
|
63
69
|
export function SymbolSection({ section, onName }: { section: HoverSection; onName?: (name: string, declared: string | null) => void }) {
|
|
64
70
|
const declared = declaredNameOf(section.signatures[0]?.code ?? '');
|
|
65
71
|
const follow = onName === undefined ? undefined : (name: string): void => onName(name, declared);
|
|
72
|
+
|
|
66
73
|
return (
|
|
67
74
|
<div className="flex flex-col gap-1.5 px-3 py-2">
|
|
68
75
|
{section.title !== null && <div className="truncate font-mono text-xs text-text-faint select-text">{section.title}</div>}
|
|
@@ -113,10 +120,13 @@ export function SourceLine({ text, language }: { text: string; language: string
|
|
|
113
120
|
if (!highlighted || highlight === undefined) {
|
|
114
121
|
return;
|
|
115
122
|
}
|
|
123
|
+
|
|
116
124
|
let alive = true;
|
|
125
|
+
|
|
117
126
|
highlight(`<?php ${text}`, 'php', theme)
|
|
118
127
|
.then((result) => alive && setHtml({ key, html: phpWithoutOpenTag(result) }))
|
|
119
128
|
.catch(() => undefined);
|
|
129
|
+
|
|
120
130
|
return () => {
|
|
121
131
|
alive = false;
|
|
122
132
|
};
|
|
@@ -124,6 +134,7 @@ export function SourceLine({ text, language }: { text: string; language: string
|
|
|
124
134
|
|
|
125
135
|
const className =
|
|
126
136
|
'block overflow-hidden text-left font-mono text-code text-ellipsis whitespace-nowrap [direction:rtl] [&_code]:font-mono [&_pre]:m-0 [&_pre]:inline [&_pre]:bg-transparent! [&_pre]:p-0 [&_code]:inline [&_.line]:inline';
|
|
137
|
+
|
|
127
138
|
return (
|
|
128
139
|
<Tooltip label={text}>
|
|
129
140
|
{highlighted && html?.key === key ? (
|
package/src/LanguagePopups.tsx
CHANGED
|
@@ -53,6 +53,7 @@ export function LanguagePopups<Language extends EditorLanguage>({ language, cont
|
|
|
53
53
|
const pickRect = pick === null ? null : language.editor.rectAt(pick.anchor);
|
|
54
54
|
const renameRect = rename === null ? null : language.editor.rectAt(rename.range.start);
|
|
55
55
|
const renameEnd = rename === null ? null : language.editor.rectAt(rename.range.end);
|
|
56
|
+
|
|
56
57
|
return (
|
|
57
58
|
<>
|
|
58
59
|
<RenderedDocs language={language} />
|
|
@@ -20,17 +20,21 @@ export function PanelResizeHandle({ container, height, onHeight, onCommit }: Pro
|
|
|
20
20
|
useEffect(() => {
|
|
21
21
|
const viewport = container.closest<HTMLElement>('.se-viewport');
|
|
22
22
|
const Observer = viewport?.ownerDocument.defaultView?.ResizeObserver;
|
|
23
|
+
|
|
23
24
|
if (viewport === null || viewport === undefined || Observer === undefined) {
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
27
|
+
|
|
26
28
|
const observer = new Observer(() => setMax(panelBodyMax(availableHeightOf(container))));
|
|
27
29
|
observer.observe(viewport);
|
|
30
|
+
|
|
28
31
|
return () => observer.disconnect();
|
|
29
32
|
}, [container]);
|
|
30
33
|
|
|
31
34
|
function settle(value: number, from: number): void {
|
|
32
35
|
const next = clampPanelBody(value, availableHeightOf(container));
|
|
33
36
|
onHeight(next);
|
|
37
|
+
|
|
34
38
|
if (next !== from) {
|
|
35
39
|
onCommit(next);
|
|
36
40
|
revealRow(realTimers, container);
|
|
@@ -45,6 +49,7 @@ export function PanelResizeHandle({ container, height, onHeight, onCommit }: Pro
|
|
|
45
49
|
if (event.button !== 0) {
|
|
46
50
|
return;
|
|
47
51
|
}
|
|
52
|
+
|
|
48
53
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
49
54
|
drag.current = { startY: event.clientY, startHeight: height, lastY: event.clientY };
|
|
50
55
|
setDragging(true);
|
|
@@ -62,6 +67,7 @@ export function PanelResizeHandle({ container, height, onHeight, onCommit }: Pro
|
|
|
62
67
|
if (drag.current === null) {
|
|
63
68
|
return;
|
|
64
69
|
}
|
|
70
|
+
|
|
65
71
|
const { startHeight } = drag.current;
|
|
66
72
|
const next = heightAt(drag.current.lastY);
|
|
67
73
|
drag.current = null;
|
|
@@ -74,9 +80,11 @@ export function PanelResizeHandle({ container, height, onHeight, onCommit }: Pro
|
|
|
74
80
|
const step = event.shiftKey ? PANEL_KEY_STEP * 4 : PANEL_KEY_STEP;
|
|
75
81
|
const end = panelBodyMax(availableHeightOf(container));
|
|
76
82
|
const target = { ArrowUp: height - step, ArrowDown: height + step, Home: PANEL_BODY_MIN, End: end }[event.key];
|
|
83
|
+
|
|
77
84
|
if (target === undefined) {
|
|
78
85
|
return;
|
|
79
86
|
}
|
|
87
|
+
|
|
80
88
|
event.preventDefault();
|
|
81
89
|
event.stopPropagation();
|
|
82
90
|
settle(target, height);
|
package/src/PathText.tsx
CHANGED
|
@@ -8,6 +8,7 @@ export function PathText({ path, className = '' }: { path: string; className?: s
|
|
|
8
8
|
if (path === '') {
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
|
+
|
|
11
12
|
return (
|
|
12
13
|
<Tooltip label={path}>
|
|
13
14
|
<span className={`min-w-0 overflow-hidden text-left text-ellipsis whitespace-nowrap [direction:rtl] ${className}`}>
|