@adecore/editor-react 0.47.0 → 0.48.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.
@@ -1,6 +1,6 @@
1
1
  import { type Ref } from 'react';
2
2
  import type { Editor, EditorEngine, EditorTheme } from '@adecore/editor';
3
- import { type DiffDocument } from '@adecore/merge';
3
+ import { type DiffDocument, type DiffSourceLine } from '@adecore/merge';
4
4
  import { type DiffSourceTokens } from './diff-syntax.ts';
5
5
  import { type DiffLimits } from './diff-expansion.ts';
6
6
  export interface DiffViewProps {
@@ -18,5 +18,11 @@ export interface DiffViewProps {
18
18
  className?: string;
19
19
  ref?: Ref<HTMLDivElement>;
20
20
  onMount?(editor: Editor, side?: 'old' | 'new'): void | (() => void);
21
+ onSource?(source: DiffSource): void | (() => void);
22
+ }
23
+ export interface DiffSource {
24
+ readonly editor: Editor;
25
+ readonly side?: 'old' | 'new';
26
+ at(): DiffSourceLine | null;
21
27
  }
22
28
  export declare function DiffView(props: DiffViewProps): import("react").JSX.Element;
package/dist/DiffView.js CHANGED
@@ -2,7 +2,7 @@ import { c as _c } from "react/compiler-runtime";
2
2
  import { useLayoutEffect, useRef, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { parseChord } from '@adecore/editor/keymap';
5
- import { diffDisplay, diffSplit } from '@adecore/merge';
5
+ import { diffDisplay, diffSplit, sourceLineOf } from '@adecore/merge';
6
6
  import { wordMarks } from './diff-wordmarks.js';
7
7
  import { DiffOverlays } from "./diff-overlays.js";
8
8
  import { SplitDiffView } from "./SplitDiffView.js";
@@ -11,7 +11,7 @@ import { LineActionHost, RowHost } from './row-host.js';
11
11
  import { DEFAULT_UNFOLD_LIMITS, expandDiff } from './diff-expansion.js';
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  function UnifiedDiffView(t0) {
14
- const $ = _c(69);
14
+ const $ = _c(76);
15
15
  const {
16
16
  engine,
17
17
  document,
@@ -26,7 +26,8 @@ function UnifiedDiffView(t0) {
26
26
  label,
27
27
  className,
28
28
  ref,
29
- onMount
29
+ onMount,
30
+ onSource
30
31
  } = t0;
31
32
  const wrap = t1 === undefined ? true : t1;
32
33
  const fill = t2 === undefined ? false : t2;
@@ -44,8 +45,9 @@ function UnifiedDiffView(t0) {
44
45
  const emphasis = t3;
45
46
  const element = useRef(null);
46
47
  let t4;
47
- if ($[2] !== display || $[3] !== label || $[4] !== theme || $[5] !== tokens || $[6] !== wrap) {
48
+ if ($[2] !== display || $[3] !== document || $[4] !== label || $[5] !== theme || $[6] !== tokens || $[7] !== wrap) {
48
49
  t4 = {
50
+ document,
49
51
  display,
50
52
  tokens,
51
53
  theme,
@@ -53,36 +55,38 @@ function UnifiedDiffView(t0) {
53
55
  label
54
56
  };
55
57
  $[2] = display;
56
- $[3] = label;
57
- $[4] = theme;
58
- $[5] = tokens;
59
- $[6] = wrap;
60
- $[7] = t4;
58
+ $[3] = document;
59
+ $[4] = label;
60
+ $[5] = theme;
61
+ $[6] = tokens;
62
+ $[7] = wrap;
63
+ $[8] = t4;
61
64
  } else {
62
- t4 = $[7];
65
+ t4 = $[8];
63
66
  }
64
67
  const current = useRef(t4);
65
68
  let t5;
66
- if ($[8] !== label || $[9] !== theme || $[10] !== wrap) {
69
+ if ($[9] !== label || $[10] !== theme || $[11] !== wrap) {
67
70
  t5 = {
68
71
  theme,
69
72
  wrap,
70
73
  label
71
74
  };
72
- $[8] = label;
73
- $[9] = theme;
74
- $[10] = wrap;
75
- $[11] = t5;
75
+ $[9] = label;
76
+ $[10] = theme;
77
+ $[11] = wrap;
78
+ $[12] = t5;
76
79
  } else {
77
- t5 = $[11];
80
+ t5 = $[12];
78
81
  }
79
82
  const applied = useRef(t5);
80
83
  const [mounted, setMounted] = useState(null);
81
84
  const [height, setHeight] = useState(0);
82
85
  let t6;
83
- if ($[12] !== display || $[13] !== label || $[14] !== theme || $[15] !== tokens || $[16] !== wrap) {
86
+ if ($[13] !== display || $[14] !== document || $[15] !== label || $[16] !== theme || $[17] !== tokens || $[18] !== wrap) {
84
87
  t6 = () => {
85
88
  current.current = {
89
+ document,
86
90
  display,
87
91
  tokens,
88
92
  theme,
@@ -90,19 +94,20 @@ function UnifiedDiffView(t0) {
90
94
  label
91
95
  };
92
96
  };
93
- $[12] = display;
94
- $[13] = label;
95
- $[14] = theme;
96
- $[15] = tokens;
97
- $[16] = wrap;
98
- $[17] = t6;
97
+ $[13] = display;
98
+ $[14] = document;
99
+ $[15] = label;
100
+ $[16] = theme;
101
+ $[17] = tokens;
102
+ $[18] = wrap;
103
+ $[19] = t6;
99
104
  } else {
100
- t6 = $[17];
105
+ t6 = $[19];
101
106
  }
102
107
  useLayoutEffect(t6);
103
108
  let t7;
104
109
  let t8;
105
- if ($[18] !== engine) {
110
+ if ($[20] !== engine) {
106
111
  t7 = () => {
107
112
  const host = element.current;
108
113
  if (host === null) {
@@ -149,17 +154,17 @@ function UnifiedDiffView(t0) {
149
154
  };
150
155
  };
151
156
  t8 = [engine];
152
- $[18] = engine;
153
- $[19] = t7;
154
- $[20] = t8;
157
+ $[20] = engine;
158
+ $[21] = t7;
159
+ $[22] = t8;
155
160
  } else {
156
- t7 = $[19];
157
- t8 = $[20];
161
+ t7 = $[21];
162
+ t8 = $[22];
158
163
  }
159
164
  useLayoutEffect(t7, t8);
160
165
  let t10;
161
166
  let t9;
162
- if ($[21] !== display || $[22] !== emphasis || $[23] !== engine || $[24] !== mounted) {
167
+ if ($[23] !== display || $[24] !== emphasis || $[25] !== engine || $[26] !== mounted) {
163
168
  t9 = () => {
164
169
  if (mounted === null || mounted.engine !== engine) {
165
170
  return;
@@ -205,20 +210,20 @@ function UnifiedDiffView(t0) {
205
210
  actions.set(display.rows.flatMap(_temp2));
206
211
  };
207
212
  t10 = [mounted, engine, display, emphasis];
208
- $[21] = display;
209
- $[22] = emphasis;
210
- $[23] = engine;
211
- $[24] = mounted;
212
- $[25] = t10;
213
- $[26] = t9;
213
+ $[23] = display;
214
+ $[24] = emphasis;
215
+ $[25] = engine;
216
+ $[26] = mounted;
217
+ $[27] = t10;
218
+ $[28] = t9;
214
219
  } else {
215
- t10 = $[25];
216
- t9 = $[26];
220
+ t10 = $[27];
221
+ t9 = $[28];
217
222
  }
218
223
  useLayoutEffect(t9, t10);
219
224
  let t11;
220
225
  let t12;
221
- if ($[27] !== display || $[28] !== engine || $[29] !== mounted || $[30] !== t) {
226
+ if ($[29] !== display || $[30] !== engine || $[31] !== mounted || $[32] !== t) {
222
227
  t11 = () => {
223
228
  if (mounted === null || mounted.engine !== engine) {
224
229
  return;
@@ -242,20 +247,20 @@ function UnifiedDiffView(t0) {
242
247
  });
243
248
  };
244
249
  t12 = [mounted, engine, display, t];
245
- $[27] = display;
246
- $[28] = engine;
247
- $[29] = mounted;
248
- $[30] = t;
249
- $[31] = t11;
250
- $[32] = t12;
250
+ $[29] = display;
251
+ $[30] = engine;
252
+ $[31] = mounted;
253
+ $[32] = t;
254
+ $[33] = t11;
255
+ $[34] = t12;
251
256
  } else {
252
- t11 = $[31];
253
- t12 = $[32];
257
+ t11 = $[33];
258
+ t12 = $[34];
254
259
  }
255
260
  useLayoutEffect(t11, t12);
256
261
  let t13;
257
262
  let t14;
258
- if ($[33] !== engine || $[34] !== label || $[35] !== mounted || $[36] !== theme || $[37] !== wrap) {
263
+ if ($[35] !== engine || $[36] !== label || $[37] !== mounted || $[38] !== theme || $[39] !== wrap) {
259
264
  t13 = () => {
260
265
  if (mounted === null || mounted.engine !== engine) {
261
266
  return;
@@ -277,20 +282,20 @@ function UnifiedDiffView(t0) {
277
282
  };
278
283
  };
279
284
  t14 = [mounted, engine, theme, wrap, label];
280
- $[33] = engine;
281
- $[34] = label;
282
- $[35] = mounted;
283
- $[36] = theme;
284
- $[37] = wrap;
285
- $[38] = t13;
286
- $[39] = t14;
285
+ $[35] = engine;
286
+ $[36] = label;
287
+ $[37] = mounted;
288
+ $[38] = theme;
289
+ $[39] = wrap;
290
+ $[40] = t13;
291
+ $[41] = t14;
287
292
  } else {
288
- t13 = $[38];
289
- t14 = $[39];
293
+ t13 = $[40];
294
+ t14 = $[41];
290
295
  }
291
296
  useLayoutEffect(t13, t14);
292
297
  let t15;
293
- if ($[40] !== engine || $[41] !== mounted || $[42] !== tokens) {
298
+ if ($[42] !== engine || $[43] !== mounted || $[44] !== tokens) {
294
299
  t15 = () => {
295
300
  if (mounted === null || mounted.engine !== engine) {
296
301
  return;
@@ -304,109 +309,138 @@ function UnifiedDiffView(t0) {
304
309
  refresh();
305
310
  return repaintOnTokens(element.current?.ownerDocument.defaultView, tokens, refresh);
306
311
  };
307
- $[40] = engine;
308
- $[41] = mounted;
309
- $[42] = tokens;
310
- $[43] = t15;
312
+ $[42] = engine;
313
+ $[43] = mounted;
314
+ $[44] = tokens;
315
+ $[45] = t15;
311
316
  } else {
312
- t15 = $[43];
317
+ t15 = $[45];
313
318
  }
314
319
  let t16;
315
- if ($[44] !== display || $[45] !== engine || $[46] !== mounted || $[47] !== tokens) {
320
+ if ($[46] !== display || $[47] !== engine || $[48] !== mounted || $[49] !== tokens) {
316
321
  t16 = [mounted, engine, tokens, display];
317
- $[44] = display;
318
- $[45] = engine;
319
- $[46] = mounted;
320
- $[47] = tokens;
321
- $[48] = t16;
322
+ $[46] = display;
323
+ $[47] = engine;
324
+ $[48] = mounted;
325
+ $[49] = tokens;
326
+ $[50] = t16;
322
327
  } else {
323
- t16 = $[48];
328
+ t16 = $[50];
324
329
  }
325
330
  useLayoutEffect(t15, t16);
326
331
  let t17;
327
332
  let t18;
328
- if ($[49] !== engine || $[50] !== mounted || $[51] !== onMount) {
333
+ if ($[51] !== engine || $[52] !== mounted || $[53] !== onMount) {
329
334
  t17 = () => {
330
335
  if (mounted !== null && mounted.engine === engine) {
331
336
  return onMount?.(mounted.editor);
332
337
  }
333
338
  };
334
339
  t18 = [mounted, engine, onMount];
335
- $[49] = engine;
336
- $[50] = mounted;
337
- $[51] = onMount;
338
- $[52] = t17;
339
- $[53] = t18;
340
+ $[51] = engine;
341
+ $[52] = mounted;
342
+ $[53] = onMount;
343
+ $[54] = t17;
344
+ $[55] = t18;
340
345
  } else {
341
- t17 = $[52];
342
- t18 = $[53];
346
+ t17 = $[54];
347
+ t18 = $[55];
343
348
  }
344
349
  useLayoutEffect(t17, t18);
350
+ let t19;
351
+ let t20;
352
+ if ($[56] !== engine || $[57] !== mounted || $[58] !== onSource) {
353
+ t19 = () => {
354
+ if (mounted === null || mounted.engine !== engine || onSource === undefined) {
355
+ return;
356
+ }
357
+ const {
358
+ editor: editor_1
359
+ } = mounted;
360
+ return onSource({
361
+ editor: editor_1,
362
+ at: () => {
363
+ const caret = editor_1.getCaret();
364
+ return sourceLineOf(current.current.display.rows, current.current.document.hunks, caret.line, caret.character, "new");
365
+ }
366
+ });
367
+ };
368
+ t20 = [mounted, engine, onSource];
369
+ $[56] = engine;
370
+ $[57] = mounted;
371
+ $[58] = onSource;
372
+ $[59] = t19;
373
+ $[60] = t20;
374
+ } else {
375
+ t19 = $[59];
376
+ t20 = $[60];
377
+ }
378
+ useLayoutEffect(t19, t20);
345
379
  const limit = maxHeight !== undefined && Number.isFinite(maxHeight) && maxHeight > 0 ? maxHeight : Infinity;
346
- const t19 = `adecore-editor-diff ${className ?? ""}`;
347
- const t20 = fill || height > limit;
348
- const t21 = fill ? "100%" : Math.min(height, limit);
349
- let t22;
350
- if ($[54] !== t21) {
351
- t22 = {
380
+ const t21 = `adecore-editor-diff ${className ?? ""}`;
381
+ const t22 = fill || height > limit;
382
+ const t23 = fill ? "100%" : Math.min(height, limit);
383
+ let t24;
384
+ if ($[61] !== t23) {
385
+ t24 = {
352
386
  position: "relative",
353
387
  minHeight: 0,
354
- height: t21
388
+ height: t23
355
389
  };
356
- $[54] = t21;
357
- $[55] = t22;
390
+ $[61] = t23;
391
+ $[62] = t24;
358
392
  } else {
359
- t22 = $[55];
393
+ t24 = $[62];
360
394
  }
361
- let t23;
362
- if ($[56] === Symbol.for("react.memo_cache_sentinel")) {
363
- t23 = /*#__PURE__*/_jsx("div", {
395
+ let t25;
396
+ if ($[63] === Symbol.for("react.memo_cache_sentinel")) {
397
+ t25 = /*#__PURE__*/_jsx("div", {
364
398
  ref: element,
365
399
  style: {
366
400
  position: "absolute",
367
401
  inset: 0
368
402
  }
369
403
  });
370
- $[56] = t23;
404
+ $[63] = t25;
371
405
  } else {
372
- t23 = $[56];
406
+ t25 = $[63];
373
407
  }
374
- let t24;
375
- if ($[57] !== blocked || $[58] !== display || $[59] !== engine || $[60] !== mounted || $[61] !== unfold) {
376
- t24 = mounted !== null && mounted.engine === engine && /*#__PURE__*/_jsx(DiffOverlays, {
408
+ let t26;
409
+ if ($[64] !== blocked || $[65] !== display || $[66] !== engine || $[67] !== mounted || $[68] !== unfold) {
410
+ t26 = mounted !== null && mounted.engine === engine && /*#__PURE__*/_jsx(DiffOverlays, {
377
411
  mounted: mounted,
378
412
  display: display,
379
413
  unfold: unfold,
380
414
  blocked: blocked
381
415
  });
382
- $[57] = blocked;
383
- $[58] = display;
384
- $[59] = engine;
385
- $[60] = mounted;
386
- $[61] = unfold;
387
- $[62] = t24;
416
+ $[64] = blocked;
417
+ $[65] = display;
418
+ $[66] = engine;
419
+ $[67] = mounted;
420
+ $[68] = unfold;
421
+ $[69] = t26;
388
422
  } else {
389
- t24 = $[62];
423
+ t26 = $[69];
390
424
  }
391
- let t25;
392
- if ($[63] !== ref || $[64] !== t19 || $[65] !== t20 || $[66] !== t22 || $[67] !== t24) {
393
- t25 = /*#__PURE__*/_jsxs("div", {
394
- className: t19,
425
+ let t27;
426
+ if ($[70] !== ref || $[71] !== t21 || $[72] !== t22 || $[73] !== t24 || $[74] !== t26) {
427
+ t27 = /*#__PURE__*/_jsxs("div", {
428
+ className: t21,
395
429
  ref: ref,
396
- "data-scroll": t20,
397
- style: t22,
398
- children: [t23, t24]
430
+ "data-scroll": t22,
431
+ style: t24,
432
+ children: [t25, t26]
399
433
  });
400
- $[63] = ref;
401
- $[64] = t19;
402
- $[65] = t20;
403
- $[66] = t22;
404
- $[67] = t24;
405
- $[68] = t25;
434
+ $[70] = ref;
435
+ $[71] = t21;
436
+ $[72] = t22;
437
+ $[73] = t24;
438
+ $[74] = t26;
439
+ $[75] = t27;
406
440
  } else {
407
- t25 = $[68];
441
+ t27 = $[75];
408
442
  }
409
- return t25;
443
+ return t27;
410
444
  }
411
445
  function _temp2(row_3, line_1) {
412
446
  return row_3.noNewline === undefined ? [] : [{
@@ -1,9 +1,9 @@
1
- import type { DiffSplit } from '@adecore/merge';
1
+ import { type DiffSplit } from '@adecore/merge';
2
2
  import type { DiffViewProps } from './DiffView.tsx';
3
3
  interface SplitViewProps extends DiffViewProps {
4
4
  display: DiffSplit;
5
5
  unfold(id: number): void;
6
6
  blocked: ReadonlySet<number>;
7
7
  }
8
- export declare function SplitDiffView({ engine, document, display, unfold, blocked, theme, tokens, wrap, fill, maxHeight, label, className, ref, onMount }: SplitViewProps): import("react").JSX.Element;
8
+ export declare function SplitDiffView({ engine, document, display, unfold, blocked, theme, tokens, wrap, fill, maxHeight, label, className, ref, onMount, onSource }: SplitViewProps): import("react").JSX.Element;
9
9
  export {};
@@ -1,6 +1,7 @@
1
1
  import { useLayoutEffect, useMemo, useRef, useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { parseChord } from '@adecore/editor/keymap';
4
+ import { sourceLineOf } from '@adecore/merge';
4
5
  import { repaintOnTokens } from './diff-syntax.js';
5
6
  import { DiffOverlays } from "./diff-overlays.js";
6
7
  import { wordMarks } from './diff-wordmarks.js';
@@ -23,7 +24,8 @@ export function SplitDiffView({
23
24
  label,
24
25
  className,
25
26
  ref,
26
- onMount
27
+ onMount,
28
+ onSource
27
29
  }) {
28
30
  const {
29
31
  t
@@ -32,6 +34,7 @@ export function SplitDiffView({
32
34
  const oldElement = useRef(null);
33
35
  const newElement = useRef(null);
34
36
  const current = useRef({
37
+ document,
35
38
  display,
36
39
  tokens,
37
40
  theme,
@@ -48,6 +51,7 @@ export function SplitDiffView({
48
51
  const [height, setHeight] = useState(0);
49
52
  useLayoutEffect(() => {
50
53
  current.current = {
54
+ document,
51
55
  display,
52
56
  tokens,
53
57
  theme,
@@ -295,6 +299,31 @@ export function SplitDiffView({
295
299
  }
296
300
  };
297
301
  }, [mounted, engine, onMount]);
302
+ useLayoutEffect(() => {
303
+ if (mounted === null || mounted.old.engine !== engine || onSource === undefined) {
304
+ return;
305
+ }
306
+ // The old pane's rows lose their new side in the split, so it reads the rows as they were aligned.
307
+ const rowsOf = side_8 => side_8 === 'new' ? current.current.display.new.rows : current.current.display.aligned.flatMap(entry => entry.kind === 'line' && entry.old !== undefined ? [entry.row] : []);
308
+ const stops_1 = SIDES.map(side_9 => {
309
+ const {
310
+ editor: editor_1
311
+ } = mounted[side_9];
312
+ return onSource({
313
+ editor: editor_1,
314
+ side: side_9,
315
+ at: () => {
316
+ const caret = editor_1.getCaret();
317
+ return sourceLineOf(rowsOf(side_9), current.current.document.hunks, caret.line, caret.character, side_9);
318
+ }
319
+ });
320
+ });
321
+ return () => {
322
+ for (const stop_1 of stops_1) {
323
+ stop_1?.();
324
+ }
325
+ };
326
+ }, [mounted, engine, onSource]);
298
327
  const limit = maxHeight !== undefined && Number.isFinite(maxHeight) && maxHeight > 0 ? maxHeight : Infinity;
299
328
  if (failure !== null) {
300
329
  throw failure.error;
@@ -308,29 +337,29 @@ export function SplitDiffView({
308
337
  style: {
309
338
  height: fill ? '100%' : Math.min(height, limit)
310
339
  },
311
- children: SIDES.map(side_8 => {
340
+ children: SIDES.map(side_10 => {
312
341
  const drive = () => {
313
- mounted?.drive(side_8);
342
+ mounted?.drive(side_10);
314
343
  };
315
344
  return /*#__PURE__*/_jsxs("div", {
316
345
  className: "adecore-editor-diff-side",
317
- "data-side": side_8,
346
+ "data-side": side_10,
318
347
  role: "group",
319
- "aria-label": t(`diff.${side_8}Side`),
348
+ "aria-label": t(`diff.${side_10}Side`),
320
349
  onWheelCapture: drive,
321
350
  onFocusCapture: drive,
322
351
  onPointerDownCapture: drive,
323
352
  onKeyDownCapture: drive,
324
353
  children: [/*#__PURE__*/_jsx("div", {
325
- ref: side_8 === 'old' ? oldElement : newElement,
354
+ ref: side_10 === 'old' ? oldElement : newElement,
326
355
  className: "adecore-editor-diff-surface"
327
- }), mounted !== null && mounted[side_8].engine === engine && /*#__PURE__*/_jsx(DiffOverlays, {
328
- mounted: mounted[side_8],
329
- display: display[side_8],
356
+ }), mounted !== null && mounted[side_10].engine === engine && /*#__PURE__*/_jsx(DiffOverlays, {
357
+ mounted: mounted[side_10],
358
+ display: display[side_10],
330
359
  unfold: unfold,
331
360
  blocked: blocked
332
361
  })]
333
- }, side_8);
362
+ }, side_10);
334
363
  })
335
364
  });
336
365
  }
package/dist/index.d.ts CHANGED
@@ -53,7 +53,7 @@ export type { AttributionCardProps } from './AttributionCard.tsx';
53
53
  export { ReviewGroup, joinReviewGroup } from './review-group.ts';
54
54
  export type { ReviewMember } from './review-group.ts';
55
55
  export { DiffView } from './DiffView.tsx';
56
- export type { DiffViewProps } from './DiffView.tsx';
56
+ export type { DiffSource, DiffViewProps } from './DiffView.tsx';
57
57
  export type { DiffLimits } from './diff-expansion.ts';
58
58
  export { diffSyntaxDocument } from './diff-syntax.ts';
59
59
  export type { DiffSourceSide, DiffSourceTokens, DiffSyntaxSources } from './diff-syntax.ts';
@@ -54,6 +54,7 @@ export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
54
54
  inlineEdit: LanguageShortcut | null;
55
55
  insertLiveTemplate: LanguageShortcut | null;
56
56
  joinLines: LanguageShortcut | null;
57
+ jumpToSource: LanguageShortcut | null;
57
58
  lastEditLocation: LanguageShortcut | null;
58
59
  matchBrace: LanguageShortcut | null;
59
60
  methodDown: LanguageShortcut | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/editor-react",
3
- "version": "0.47.0",
3
+ "version": "0.48.0",
4
4
  "type": "module",
5
5
  "description": "Language-enabled editors, feature coordination and React popups over injected language services.",
6
6
  "license": "FSL-1.1-MIT",
@@ -62,9 +62,9 @@
62
62
  "test": "bun test --conditions=source"
63
63
  },
64
64
  "dependencies": {
65
- "@adecore/editor": "0.47.0",
66
- "@adecore/lsp": "0.47.0",
67
- "@adecore/merge": "0.47.0",
65
+ "@adecore/editor": "0.48.0",
66
+ "@adecore/lsp": "0.48.0",
67
+ "@adecore/merge": "0.48.0",
68
68
  "emmet": "^2.4.11",
69
69
  "lucide-react": "^1.61.0",
70
70
  "react-markdown": "^10.1.0",
@@ -73,14 +73,14 @@
73
73
  "zustand": "^5.0.16"
74
74
  },
75
75
  "peerDependencies": {
76
- "@adecore/ui": "^0.47.0",
76
+ "@adecore/ui": "^0.48.0",
77
77
  "i18next": "^26.4.2",
78
78
  "react": "^19.3.0",
79
79
  "react-dom": "^19.3.0",
80
80
  "react-i18next": "^17.0.15"
81
81
  },
82
82
  "devDependencies": {
83
- "@adecore/ui": "0.47.0",
83
+ "@adecore/ui": "0.48.0",
84
84
  "@types/bun": "^1.4.4",
85
85
  "@types/react": "^19.3.0",
86
86
  "@types/react-dom": "^19.3.0",
package/src/DiffView.tsx CHANGED
@@ -2,7 +2,7 @@ import { useLayoutEffect, useRef, useState, type Ref } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import type { Editor, EditorEngine, EditorLineHighlight, EditorTheme } from '@adecore/editor';
4
4
  import { parseChord } from '@adecore/editor/keymap';
5
- import { diffDisplay, diffSplit, type DiffDocument, type DiffDisplay } from '@adecore/merge';
5
+ import { diffDisplay, diffSplit, sourceLineOf, type DiffDocument, type DiffDisplay, type DiffSourceLine } from '@adecore/merge';
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';
@@ -29,6 +29,16 @@ export interface DiffViewProps {
29
29
  className?: string;
30
30
  ref?: Ref<HTMLDivElement>;
31
31
  onMount?(editor: Editor, side?: 'old' | 'new'): void | (() => void);
32
+ /* Runs per editor like `onMount`, with a way to ask where its caret stands in the new file, for a host that opens the file there. */
33
+ onSource?(source: DiffSource): void | (() => void);
34
+ }
35
+
36
+ export interface DiffSource {
37
+ readonly editor: Editor;
38
+ /* `old` or `new` in split, absent in unified. */
39
+ readonly side?: 'old' | 'new';
40
+ /* The line and column of the new file under the caret, or null where the new file has none. */
41
+ at(): DiffSourceLine | null;
32
42
  }
33
43
 
34
44
  interface UnifiedViewProps extends DiffViewProps {
@@ -51,18 +61,19 @@ function UnifiedDiffView({
51
61
  label,
52
62
  className,
53
63
  ref,
54
- onMount
64
+ onMount,
65
+ onSource
55
66
  }: UnifiedViewProps) {
56
67
  const { t } = useTranslation('editor');
57
68
  const emphasis = wordMarks(document);
58
69
  const element = useRef<HTMLDivElement>(null);
59
- const current = useRef({ display, tokens, theme, wrap, label });
70
+ const current = useRef({ document, display, tokens, theme, wrap, label });
60
71
  const applied = useRef({ theme, wrap, label });
61
72
  const [mounted, setMounted] = useState<MountedDiff | null>(null);
62
73
  const [height, setHeight] = useState(0);
63
74
 
64
75
  useLayoutEffect(() => {
65
- current.current = { display, tokens, theme, wrap, label };
76
+ current.current = { document, display, tokens, theme, wrap, label };
66
77
  });
67
78
 
68
79
  useLayoutEffect(() => {
@@ -231,6 +242,23 @@ function UnifiedDiffView({
231
242
  }
232
243
  }, [mounted, engine, onMount]);
233
244
 
245
+ useLayoutEffect(() => {
246
+ if (mounted === null || mounted.engine !== engine || onSource === undefined) {
247
+ return;
248
+ }
249
+
250
+ const { editor } = mounted;
251
+
252
+ return onSource({
253
+ editor,
254
+ at: () => {
255
+ const caret = editor.getCaret();
256
+
257
+ return sourceLineOf(current.current.display.rows, current.current.document.hunks, caret.line, caret.character, 'new');
258
+ }
259
+ });
260
+ }, [mounted, engine, onSource]);
261
+
234
262
  const limit = maxHeight !== undefined && Number.isFinite(maxHeight) && maxHeight > 0 ? maxHeight : Infinity;
235
263
 
236
264
  return (
@@ -2,7 +2,7 @@ import { useLayoutEffect, useMemo, useRef, useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { parseChord } from '@adecore/editor/keymap';
4
4
  import type { EditorLineHighlight } from '@adecore/editor';
5
- import type { DiffSplit } from '@adecore/merge';
5
+ import { sourceLineOf, type DiffLineRow, type DiffSplit } from '@adecore/merge';
6
6
  import type { DiffViewProps } from './DiffView.tsx';
7
7
  import { repaintOnTokens, type DiffSourceSide } from './diff-syntax.ts';
8
8
  import { DiffOverlays, type MountedDiff } from './diff-overlays.tsx';
@@ -41,20 +41,21 @@ export function SplitDiffView({
41
41
  label,
42
42
  className,
43
43
  ref,
44
- onMount
44
+ onMount,
45
+ onSource
45
46
  }: SplitViewProps) {
46
47
  const { t } = useTranslation('editor');
47
48
  const emphasis = useMemo(() => wordMarks(document), [document]);
48
49
  const oldElement = useRef<HTMLDivElement>(null);
49
50
  const newElement = useRef<HTMLDivElement>(null);
50
- const current = useRef({ display, tokens, theme, wrap, label });
51
+ const current = useRef({ document, display, tokens, theme, wrap, label });
51
52
  const applied = useRef({ theme, wrap, label });
52
53
  const [failure, setFailure] = useState<{ error: unknown } | null>(null);
53
54
  const [mounted, setMounted] = useState<MountedSplit | null>(null);
54
55
  const [height, setHeight] = useState(0);
55
56
 
56
57
  useLayoutEffect(() => {
57
- current.current = { display, tokens, theme, wrap, label };
58
+ current.current = { document, display, tokens, theme, wrap, label };
58
59
  });
59
60
 
60
61
  useLayoutEffect(() => {
@@ -328,6 +329,38 @@ export function SplitDiffView({
328
329
  };
329
330
  }, [mounted, engine, onMount]);
330
331
 
332
+ useLayoutEffect(() => {
333
+ if (mounted === null || mounted.old.engine !== engine || onSource === undefined) {
334
+ return;
335
+ }
336
+
337
+ // The old pane's rows lose their new side in the split, so it reads the rows as they were aligned.
338
+ const rowsOf = (side: DiffSourceSide): readonly DiffLineRow[] =>
339
+ side === 'new'
340
+ ? current.current.display.new.rows
341
+ : current.current.display.aligned.flatMap((entry) => (entry.kind === 'line' && entry.old !== undefined ? [entry.row] : []));
342
+
343
+ const stops = SIDES.map((side) => {
344
+ const { editor } = mounted[side];
345
+
346
+ return onSource({
347
+ editor,
348
+ side,
349
+ at: () => {
350
+ const caret = editor.getCaret();
351
+
352
+ return sourceLineOf(rowsOf(side), current.current.document.hunks, caret.line, caret.character, side);
353
+ }
354
+ });
355
+ });
356
+
357
+ return () => {
358
+ for (const stop of stops) {
359
+ stop?.();
360
+ }
361
+ };
362
+ }, [mounted, engine, onSource]);
363
+
331
364
  const limit = maxHeight !== undefined && Number.isFinite(maxHeight) && maxHeight > 0 ? maxHeight : Infinity;
332
365
 
333
366
  if (failure !== null) {
package/src/index.ts CHANGED
@@ -99,7 +99,7 @@ export type { AttributionCardProps } from './AttributionCard.tsx';
99
99
  export { ReviewGroup, joinReviewGroup } from './review-group.ts';
100
100
  export type { ReviewMember } from './review-group.ts';
101
101
  export { DiffView } from './DiffView.tsx';
102
- export type { DiffViewProps } from './DiffView.tsx';
102
+ export type { DiffSource, DiffViewProps } from './DiffView.tsx';
103
103
  export type { DiffLimits } from './diff-expansion.ts';
104
104
  export { diffSyntaxDocument } from './diff-syntax.ts';
105
105
  export type { DiffSourceSide, DiffSourceTokens, DiffSyntaxSources } from './diff-syntax.ts';