xtralab 0.7.0 → 0.9.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.
Files changed (54) hide show
  1. package/README.md +18 -10
  2. package/lib/fileBrowser/commands.d.ts +7 -0
  3. package/lib/fileBrowser/commands.js +74 -1
  4. package/lib/fileBrowser/fileBrowser.js +4 -1
  5. package/lib/fileBrowser/icons.d.ts +5 -9
  6. package/lib/fileBrowser/icons.js +97 -13
  7. package/lib/fileBrowser/index.js +5 -3
  8. package/lib/fileTypeIcons/index.d.ts +6 -0
  9. package/lib/fileTypeIcons/index.js +144 -0
  10. package/lib/git/commands.d.ts +34 -33
  11. package/lib/git/commands.js +132 -10
  12. package/lib/git/diffModel.d.ts +14 -0
  13. package/lib/git/diffModel.js +75 -0
  14. package/lib/git/diffProvider.d.ts +0 -5
  15. package/lib/git/diffProvider.js +6 -284
  16. package/lib/git/diffSurface.d.ts +64 -25
  17. package/lib/git/diffSurface.js +84 -27
  18. package/lib/git/diffTheme.d.ts +9 -0
  19. package/lib/git/diffTheme.js +31 -0
  20. package/lib/git/diffWidget.d.ts +67 -85
  21. package/lib/git/diffWidget.js +242 -318
  22. package/lib/git/index.d.ts +1 -6
  23. package/lib/git/index.js +7 -23
  24. package/lib/git/notebookDiff.d.ts +6 -1
  25. package/lib/git/notebookDiff.js +4 -3
  26. package/lib/index.js +2 -0
  27. package/lib/terminals/model.d.ts +1 -1
  28. package/lib/terminals/model.js +1 -1
  29. package/lib/terminals/widget.d.ts +1 -1
  30. package/lib/terminals/widget.js +1 -1
  31. package/package.json +3 -3
  32. package/schema/plugin.json +6 -0
  33. package/src/fileBrowser/commands.ts +91 -2
  34. package/src/fileBrowser/fileBrowser.tsx +4 -1
  35. package/src/fileBrowser/icons.ts +103 -13
  36. package/src/fileBrowser/index.ts +9 -3
  37. package/src/fileTypeIcons/index.ts +174 -0
  38. package/src/git/commands.ts +177 -43
  39. package/src/git/diffModel.ts +102 -0
  40. package/src/git/diffProvider.tsx +8 -409
  41. package/src/git/diffSurface.tsx +187 -35
  42. package/src/git/diffTheme.ts +40 -0
  43. package/src/git/diffWidget.tsx +338 -393
  44. package/src/git/index.ts +14 -28
  45. package/src/git/notebookDiff.tsx +10 -3
  46. package/src/index.ts +2 -0
  47. package/src/terminals/model.ts +1 -1
  48. package/src/terminals/widget.tsx +1 -1
  49. package/style/base.css +1 -0
  50. package/style/git.css +40 -12
  51. package/style/index.css +1 -0
  52. package/style/index.js +1 -0
  53. package/style/launcher.css +46 -18
  54. package/style/sidebar.css +14 -0
@@ -1,26 +1,19 @@
1
1
  import * as React from 'react';
2
2
  import { IThemeManager } from '@jupyterlab/apputils';
3
3
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
- /**
5
- * Shared diff rendering surface used by both diff hosts in this extension:
6
- *
7
- * - the launcher dashboard's own `xtralab:git:open-diff` command
8
- * (via {@link DiffContentWidget} in `diffWidget.tsx`), and
9
- * - the `jupyterlab-git` diff providers registered into the upstream git
10
- * panel (via `XtralabDiffWidget` in `diffProvider.tsx`).
11
- *
12
- * The surface is deliberately content-source agnostic: callers resolve the
13
- * old/new text however they like (xtralab's REST helper, or a
14
- * `Git.Diff.IModel`'s `content()` getters) and hand the resolved strings in.
15
- * Everything visual — the `@pierre/diffs` split view, the cell-by-cell
16
- * notebook view, the draggable column resizer, and the per-hunk discard
17
- * affordance — lives here so the two hosts stay pixel-identical.
18
- */
19
4
  /**
20
5
  * The CSS class added to the diff main-area widget. The selectors that
21
6
  * style the embedded `@pierre/diffs` viewer hang off this class.
22
7
  */
23
8
  export declare const DIFF_WIDGET_CSS_CLASS = "jp-xtralab-DiffWidget";
9
+ /**
10
+ * Layout for textual/code diffs, forwarded to `@pierre/diffs` as its
11
+ * `diffStyle` option. `split` is the side-by-side old|new view; `unified`
12
+ * is the single-column inline view (what diffs.com calls "stacked").
13
+ */
14
+ export type DiffStyle = 'split' | 'unified';
15
+ export declare function readStoredDiffStyle(): DiffStyle;
16
+ export declare function writeStoredDiffStyle(style: DiffStyle): void;
24
17
  /**
25
18
  * View modes available for `.ipynb` diffs. `notebook` is the cell-by-cell
26
19
  * rendered view; `json` is the raw nbformat JSON file diff, for inspecting
@@ -37,6 +30,10 @@ export declare function writeStoredNotebookViewMode(mode: NotebookDiffViewMode):
37
30
  * provided (lite, certain notebook configurations, …).
38
31
  */
39
32
  export declare function isDarkTheme(themeManager: IThemeManager | null): boolean;
33
+ /**
34
+ * Whether the active JupyterLab theme should keep Pierre diff highlighting.
35
+ */
36
+ export declare function isPierreTheme(themeManager: IThemeManager | null): boolean;
40
37
  /**
41
38
  * Optional per-hunk discard wiring. When supplied and `enabled`, each hunk
42
39
  * in a plain-text/code diff gets an inline "discard" button; clicking it
@@ -51,15 +48,25 @@ export interface IHunkDiscard {
51
48
  onAfterSave: () => void;
52
49
  }
53
50
  export interface IDiffSurfaceProps {
54
- /** Whether the host is still resolving the file contents. */
51
+ /**
52
+ * Whether the host is still resolving the file contents.
53
+ */
55
54
  loading: boolean;
56
- /** Fatal error message to show instead of a diff, or `null`. */
55
+ /**
56
+ * Fatal error message to show instead of a diff, or `null`.
57
+ */
57
58
  error: string | null;
58
- /** Whether the file is binary (no textual diff is rendered). */
59
+ /**
60
+ * Whether the file is binary (no textual diff is rendered).
61
+ */
59
62
  isBinary: boolean;
60
- /** Resolved old/reference text. Ignored while loading/binary/errored. */
63
+ /**
64
+ * Resolved old/reference text. Ignored while loading/binary/errored.
65
+ */
61
66
  oldText: string;
62
- /** Resolved new/challenger text. Ignored while loading/binary/errored. */
67
+ /**
68
+ * Resolved new/challenger text. Ignored while loading/binary/errored.
69
+ */
63
70
  newText: string;
64
71
  /**
65
72
  * New-side path. Drives notebook detection and the `@pierre/diffs` file
@@ -71,21 +78,40 @@ export interface IDiffSurfaceProps {
71
78
  * to {@link newName} when the host does not track a previous path.
72
79
  */
73
80
  oldName?: string;
74
- /** Whether to render with the dark `@pierre/diffs` theme. */
81
+ /**
82
+ * Whether to render with the dark `@pierre/diffs` theme.
83
+ */
75
84
  dark: boolean;
85
+ /**
86
+ * Whether the diff should keep Pierre's own syntax palette.
87
+ */
88
+ pierreTheme: boolean;
76
89
  /**
77
90
  * Rendermime registry used by the notebook view to render outputs and
78
91
  * markdown cells. May be `null` in stripped-down hosts; the notebook
79
92
  * diff falls back to a textual representation in that case.
80
93
  */
81
94
  rendermime: IRenderMimeRegistry | null;
82
- /** Current rendered-vs-JSON choice for notebook diffs (host-controlled). */
95
+ /**
96
+ * Current rendered-vs-JSON choice for notebook diffs (host-controlled).
97
+ */
83
98
  notebookViewMode: NotebookDiffViewMode;
99
+ /**
100
+ * Split vs unified layout for the textual/code file diff (host-controlled).
101
+ */
102
+ diffStyle: DiffStyle;
84
103
  /**
85
104
  * Called whenever the availability of a rendered notebook view changes,
86
105
  * so the host can show/hide its Notebook/JSON toolbar toggle.
87
106
  */
88
107
  onNotebookAvailabilityChange?: (available: boolean) => void;
108
+ /**
109
+ * Called whenever the textual/code file diff (the only view the split vs
110
+ * unified choice affects) becomes the active view, so the host can
111
+ * show/hide its Split/Unified toolbar toggle. False for image, binary,
112
+ * rendered-notebook, loading and error states.
113
+ */
114
+ onFileDiffActiveChange?: (active: boolean) => void;
89
115
  /**
90
116
  * Called after each (re)computation of the diff with the number of hunks
91
117
  * (or `null` when no textual diff exists). Hosts use this to implement
@@ -94,12 +120,13 @@ export interface IDiffSurfaceProps {
94
120
  onMetadataChange?: (info: {
95
121
  hunkCount: number | null;
96
122
  }) => void;
97
- /** Optional per-hunk discard wiring; omit for a read-only diff. */
123
+ /**
124
+ * Optional per-hunk discard wiring; omit for a read-only diff.
125
+ */
98
126
  hunkDiscard?: IHunkDiscard;
99
127
  }
100
128
  /**
101
- * The shared, content-source-agnostic diff renderer. See the module
102
- * docstring for the division of labor between this surface and its hosts.
129
+ * Shared renderer for text, notebook and image diffs.
103
130
  */
104
131
  export declare function DiffSurface(props: IDiffSurfaceProps): React.ReactElement;
105
132
  /**
@@ -113,3 +140,15 @@ export declare function NotebookViewModeControl(props: {
113
140
  available: boolean;
114
141
  onChange: (mode: NotebookDiffViewMode) => void;
115
142
  }): React.ReactElement;
143
+ /**
144
+ * Segmented Split/Unified selector. Mirrors {@link NotebookViewModeControl}:
145
+ * hosts mount it into their own toolbar and drive its value/visibility from
146
+ * the same state they pass to {@link DiffSurface}. Only meaningful while the
147
+ * textual/code file diff is the active view, so `available` mirrors the
148
+ * surface's `onFileDiffActiveChange`.
149
+ */
150
+ export declare function DiffStyleControl(props: {
151
+ diffStyle: DiffStyle;
152
+ available: boolean;
153
+ onChange: (style: DiffStyle) => void;
154
+ }): React.ReactElement;
@@ -4,21 +4,7 @@ import { FileDiff } from '@pierre/diffs/react';
4
4
  import { diffAcceptRejectHunk, parseDiffFromFile } from '@pierre/diffs';
5
5
  import { ImageDiffView, imageDataType } from './imageDiff';
6
6
  import { buildNotebookDiff, NotebookDiffView } from './notebookDiff';
7
- /**
8
- * Shared diff rendering surface used by both diff hosts in this extension:
9
- *
10
- * - the launcher dashboard's own `xtralab:git:open-diff` command
11
- * (via {@link DiffContentWidget} in `diffWidget.tsx`), and
12
- * - the `jupyterlab-git` diff providers registered into the upstream git
13
- * panel (via `XtralabDiffWidget` in `diffProvider.tsx`).
14
- *
15
- * The surface is deliberately content-source agnostic: callers resolve the
16
- * old/new text however they like (xtralab's REST helper, or a
17
- * `Git.Diff.IModel`'s `content()` getters) and hand the resolved strings in.
18
- * Everything visual — the `@pierre/diffs` split view, the cell-by-cell
19
- * notebook view, the draggable column resizer, and the per-hunk discard
20
- * affordance — lives here so the two hosts stay pixel-identical.
21
- */
7
+ import { resolveDiffTheme } from './diffTheme';
22
8
  /**
23
9
  * The CSS class added to the diff main-area widget. The selectors that
24
10
  * style the embedded `@pierre/diffs` viewer hang off this class.
@@ -79,6 +65,27 @@ function writeStoredSplitRatio(ratio) {
79
65
  // See readStoredSplitRatio — best-effort persistence.
80
66
  }
81
67
  }
68
+ const DIFF_STYLE_STORAGE_KEY = 'xtralab:diff-style';
69
+ export function readStoredDiffStyle() {
70
+ try {
71
+ const raw = window.localStorage.getItem(DIFF_STYLE_STORAGE_KEY);
72
+ if (raw === 'split' || raw === 'unified') {
73
+ return raw;
74
+ }
75
+ }
76
+ catch (_a) {
77
+ // See readStoredSplitRatio — privacy-mode / sandboxed contexts can throw.
78
+ }
79
+ return 'split';
80
+ }
81
+ export function writeStoredDiffStyle(style) {
82
+ try {
83
+ window.localStorage.setItem(DIFF_STYLE_STORAGE_KEY, style);
84
+ }
85
+ catch (_a) {
86
+ // Best-effort.
87
+ }
88
+ }
82
89
  const NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY = 'xtralab:notebook-diff-view-mode';
83
90
  export function readStoredNotebookViewMode() {
84
91
  try {
@@ -125,11 +132,18 @@ export function isDarkTheme(themeManager) {
125
132
  return document.body.dataset.jpThemeLight === 'false';
126
133
  }
127
134
  /**
128
- * The shared, content-source-agnostic diff renderer. See the module
129
- * docstring for the division of labor between this surface and its hosts.
135
+ * Whether the active JupyterLab theme should keep Pierre diff highlighting.
136
+ */
137
+ export function isPierreTheme(themeManager) {
138
+ var _a;
139
+ const theme = (_a = themeManager === null || themeManager === void 0 ? void 0 : themeManager.theme) !== null && _a !== void 0 ? _a : null;
140
+ return theme !== null && theme.toLowerCase().includes('pierre');
141
+ }
142
+ /**
143
+ * Shared renderer for text, notebook and image diffs.
130
144
  */
131
145
  export function DiffSurface(props) {
132
- const { loading, error, isBinary, oldText, newText, newName, oldName, dark, rendermime, notebookViewMode, onNotebookAvailabilityChange, onMetadataChange, hunkDiscard } = props;
146
+ const { loading, error, isBinary, oldText, newText, newName, oldName, dark, pierreTheme, rendermime, notebookViewMode, diffStyle, onNotebookAvailabilityChange, onFileDiffActiveChange, onMetadataChange, hunkDiscard } = props;
133
147
  const hasContent = !loading && error === null && !isBinary;
134
148
  // Raster images take a dedicated `<img>`-based view instead of a
135
149
  // text/notebook diff; the host passes the two sides as base64 strings in
@@ -171,6 +185,19 @@ export function DiffSurface(props) {
171
185
  hunkCount: metadata !== null ? metadata.hunks.length : null
172
186
  });
173
187
  }, [onMetadataChange, metadata]);
188
+ // Decide which view to render. The notebook view requires a successful
189
+ // nbformat parse; if that failed, we only show the raw JSON file diff.
190
+ // Computed before the early returns below so the file-diff-active effect
191
+ // can depend on it without violating the rules of hooks.
192
+ const hasNotebookView = notebookDiff !== null;
193
+ const showNotebookView = hasNotebookView && notebookViewMode === 'notebook';
194
+ const showFileDiff = !showNotebookView && metadata !== null;
195
+ // The split vs unified choice only affects the textual/code file diff, so
196
+ // tell the host whether that view is active to drive its Split/Unified
197
+ // toolbar toggle.
198
+ React.useEffect(() => {
199
+ onFileDiffActiveChange === null || onFileDiffActiveChange === void 0 ? void 0 : onFileDiffActiveChange(showFileDiff);
200
+ }, [onFileDiffActiveChange, showFileDiff]);
174
201
  // Split ratio for the diff columns: fraction of width given to the
175
202
  // deletions (left) pane. Persisted across sessions so the user only
176
203
  // dials in their layout once.
@@ -283,11 +310,6 @@ export function DiffSurface(props) {
283
310
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-content" },
284
311
  React.createElement(ImageDiffView, { reference: oldText, challenger: newText, fileType: imageType })));
285
312
  }
286
- // Decide which view to render. The notebook view requires a successful
287
- // nbformat parse; if that failed, we only show the raw JSON file diff.
288
- const hasNotebookView = notebookDiff !== null;
289
- const showNotebookView = hasNotebookView && notebookViewMode === 'notebook';
290
- const showFileDiff = !showNotebookView && metadata !== null;
291
313
  if (!showNotebookView && !showFileDiff) {
292
314
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, "No content to diff."));
293
315
  }
@@ -301,7 +323,7 @@ export function DiffSurface(props) {
301
323
  };
302
324
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-content" },
303
325
  React.createElement("div", { ref: wrapperRef, className: "jp-xtralab-DiffWidget-body" },
304
- React.createElement("div", { className: "jp-xtralab-DiffWidget-scroll" }, showNotebookView && notebookDiff !== null ? (React.createElement(NotebookDiffView, { diff: notebookDiff, dark: dark, rendermime: rendermime })) : showFileDiff && metadata !== null ? (React.createElement(FileDiff, { fileDiff: metadata, lineAnnotations: lineAnnotations, renderAnnotation: renderAnnotation, style: hostStyle,
326
+ React.createElement("div", { className: "jp-xtralab-DiffWidget-scroll" }, showNotebookView && notebookDiff !== null ? (React.createElement(NotebookDiffView, { diff: notebookDiff, dark: dark, pierreTheme: pierreTheme, rendermime: rendermime })) : showFileDiff && metadata !== null ? (React.createElement(FileDiff, { fileDiff: metadata, lineAnnotations: lineAnnotations, renderAnnotation: renderAnnotation, style: hostStyle,
305
327
  // Disable the worker pool: JupyterLab's webpack federation
306
328
  // pipeline doesn't ship the library's
307
329
  // `@pierre/diffs/worker/worker.js` file at a URL the worker
@@ -309,12 +331,12 @@ export function DiffSurface(props) {
309
331
  // crashes on instantiation. Running on the main thread is
310
332
  // fine for the small files git diffs typically operate on.
311
333
  disableWorkerPool: true, options: {
312
- diffStyle: 'split',
334
+ diffStyle,
313
335
  // Drop the file header — the tab title already shows the
314
336
  // file name and the panel header carries the change
315
337
  // context.
316
338
  disableFileHeader: true,
317
- theme: dark ? 'pierre-dark' : 'pierre-light',
339
+ theme: resolveDiffTheme(dark, pierreTheme),
318
340
  themeType: dark ? 'dark' : 'light',
319
341
  // Inject the column-resize override into the shadow root
320
342
  // via the library's `@layer unsafe` channel. Keeping the
@@ -323,7 +345,7 @@ export function DiffSurface(props) {
323
345
  // changes.
324
346
  unsafeCSS: SPLIT_RESIZE_CSS
325
347
  } })) : null),
326
- showFileDiff ? (React.createElement("div", { className: "jp-xtralab-DiffWidget-resizer", style: { left: `${leftPercent}%` }, role: "separator", "aria-orientation": "vertical", "aria-valuenow": Math.round(leftPercent), "aria-valuemin": Math.round(MIN_SPLIT_RATIO * 100), "aria-valuemax": Math.round(MAX_SPLIT_RATIO * 100), title: "Drag to resize the diff panes (double-click to reset)", onPointerDown: handleResizerPointerDown, onDoubleClick: handleResizerDoubleClick })) : null)));
348
+ showFileDiff && diffStyle === 'split' ? (React.createElement("div", { className: "jp-xtralab-DiffWidget-resizer", style: { left: `${leftPercent}%` }, role: "separator", "aria-orientation": "vertical", "aria-valuenow": Math.round(leftPercent), "aria-valuemin": Math.round(MIN_SPLIT_RATIO * 100), "aria-valuemax": Math.round(MAX_SPLIT_RATIO * 100), title: "Drag to resize the diff panes (double-click to reset)", onPointerDown: handleResizerPointerDown, onDoubleClick: handleResizerDoubleClick })) : null)));
327
349
  }
328
350
  /**
329
351
  * Segmented Notebook/JSON selector. Hosts mount this into whatever toolbar
@@ -340,3 +362,38 @@ export function NotebookViewModeControl(props) {
340
362
  React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'notebook', "data-active": mode === 'notebook', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('notebook') }, "Notebook"),
341
363
  React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'json', "data-active": mode === 'json', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('json') }, "JSON")));
342
364
  }
365
+ /**
366
+ * Split / unified glyphs, inlined from the `@pierre/diffs` icon sprite
367
+ * (`diffs-icon-diff-split` / `diffs-icon-diff-unified`) so the toggle looks
368
+ * like the one on the library's own docs site without depending on the
369
+ * sprite sheet being injected into the document. `currentColor` lets the
370
+ * segmented-button styling drive the fill (including the active state).
371
+ */
372
+ function DiffSplitIcon() {
373
+ return (React.createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true" },
374
+ React.createElement("path", { d: "M14 0H8.5v16H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2m-1.5 6.5v1h1a.5.5 0 0 1 0 1h-1v1a.5.5 0 0 1-1 0v-1h-1a.5.5 0 0 1 0-1h1v-1a.5.5 0 0 1 1 0" }),
375
+ React.createElement("path", { d: "M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h5.5V0zm.5 7.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1", opacity: ".3" })));
376
+ }
377
+ function DiffUnifiedIcon() {
378
+ return (React.createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true" },
379
+ React.createElement("path", { fillRule: "evenodd", d: "M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8.5h16zm-8-4a.5.5 0 0 0-.5.5v1h-1a.5.5 0 0 0 0 1h1v1a.5.5 0 0 0 1 0v-1h1a.5.5 0 0 0 0-1h-1v-1A.5.5 0 0 0 8 10", clipRule: "evenodd" }),
380
+ React.createElement("path", { fillRule: "evenodd", d: "M14 0a2 2 0 0 1 2 2v5.5H0V2a2 2 0 0 1 2-2zM6.5 3.5a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z", clipRule: "evenodd", opacity: ".4" })));
381
+ }
382
+ /**
383
+ * Segmented Split/Unified selector. Mirrors {@link NotebookViewModeControl}:
384
+ * hosts mount it into their own toolbar and drive its value/visibility from
385
+ * the same state they pass to {@link DiffSurface}. Only meaningful while the
386
+ * textual/code file diff is the active view, so `available` mirrors the
387
+ * surface's `onFileDiffActiveChange`.
388
+ */
389
+ export function DiffStyleControl(props) {
390
+ const { diffStyle, available, onChange } = props;
391
+ if (!available) {
392
+ return React.createElement(React.Fragment, null);
393
+ }
394
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": "Diff view style" },
395
+ React.createElement("button", { type: "button", role: "tab", "aria-selected": diffStyle === 'split', "data-active": diffStyle === 'split', className: "jp-xtralab-DiffWidget-segmentedButton jp-xtralab-DiffWidget-segmentedButton-icon", title: "Split (side-by-side) view", "aria-label": "Split view", onClick: () => onChange('split') },
396
+ React.createElement(DiffSplitIcon, null)),
397
+ React.createElement("button", { type: "button", role: "tab", "aria-selected": diffStyle === 'unified', "data-active": diffStyle === 'unified', className: "jp-xtralab-DiffWidget-segmentedButton jp-xtralab-DiffWidget-segmentedButton-icon", title: "Unified (inline) view", "aria-label": "Unified view", onClick: () => onChange('unified') },
398
+ React.createElement(DiffUnifiedIcon, null))));
399
+ }
@@ -0,0 +1,9 @@
1
+ import { type DiffsThemeNames } from '@pierre/diffs';
2
+ /**
3
+ * Shiki CSS-variable theme used for non-Pierre JupyterLab themes.
4
+ */
5
+ export declare const JUPYTERLAB_DIFF_THEME: DiffsThemeNames;
6
+ /**
7
+ * Choose Pierre's palette or the JupyterLab CSS-variable theme.
8
+ */
9
+ export declare function resolveDiffTheme(dark: boolean, pierreTheme: boolean): DiffsThemeNames;
@@ -0,0 +1,31 @@
1
+ import { registerCustomCSSVariableTheme } from '@pierre/diffs';
2
+ /**
3
+ * Shiki CSS-variable theme used for non-Pierre JupyterLab themes.
4
+ */
5
+ export const JUPYTERLAB_DIFF_THEME = 'jupyterlab';
6
+ /**
7
+ * Register a Shiki theme whose token colors read JupyterLab's editor
8
+ * variables. The values are CSS variables, so theme changes recolor the diff.
9
+ */
10
+ registerCustomCSSVariableTheme(JUPYTERLAB_DIFF_THEME, {
11
+ foreground: 'var(--jp-content-font-color1)',
12
+ background: 'var(--jp-layout-color1)',
13
+ 'token-keyword': 'var(--jp-mirror-editor-keyword-color)',
14
+ 'token-string': 'var(--jp-mirror-editor-string-color)',
15
+ 'token-string-expression': 'var(--jp-mirror-editor-string-2-color)',
16
+ 'token-comment': 'var(--jp-mirror-editor-comment-color)',
17
+ 'token-constant': 'var(--jp-mirror-editor-number-color)',
18
+ 'token-function': 'var(--jp-mirror-editor-def-color)',
19
+ 'token-parameter': 'var(--jp-mirror-editor-variable-color)',
20
+ 'token-punctuation': 'var(--jp-mirror-editor-punctuation-color)',
21
+ 'token-link': 'var(--jp-mirror-editor-link-color)'
22
+ });
23
+ /**
24
+ * Choose Pierre's palette or the JupyterLab CSS-variable theme.
25
+ */
26
+ export function resolveDiffTheme(dark, pierreTheme) {
27
+ if (pierreTheme) {
28
+ return dark ? 'pierre-dark' : 'pierre-light';
29
+ }
30
+ return JUPYTERLAB_DIFF_THEME;
31
+ }
@@ -1,114 +1,96 @@
1
1
  import * as React from 'react';
2
- import { IThemeManager, MainAreaWidget } from '@jupyterlab/apputils';
2
+ import { IThemeManager } from '@jupyterlab/apputils';
3
+ import { Git } from '@jupyterlab/git';
3
4
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
5
  import { Contents } from '@jupyterlab/services';
5
6
  import { ReactWidget } from '@jupyterlab/ui-components';
6
7
  import { ISignal } from '@lumino/signaling';
7
- import { DIFF_WIDGET_CSS_CLASS, type NotebookDiffViewMode } from './diffSurface';
8
- import { GitReference, IFileChange } from './tokens';
8
+ import { Widget } from '@lumino/widgets';
9
+ import { DIFF_WIDGET_CSS_CLASS, type DiffStyle, type NotebookDiffViewMode } from './diffSurface';
9
10
  export { DIFF_WIDGET_CSS_CLASS };
10
- export declare const PREVIEW_DIFF_WIDGET_ID = "xtralab:diff:preview";
11
- export declare function pinnedDiffWidgetId(change: IFileChange): string;
12
11
  /**
13
- * Build the deterministic widget id used for a diff against a particular
14
- * file/group pair. Reused across plugin and command code so reopening a
15
- * diff for the same file reveals the existing tab instead of opening a
16
- * second one.
12
+ * `Git.Diff.IModel` plus optional xtralab-only metadata.
17
13
  */
18
- export declare function diffWidgetId(change: IFileChange): string;
14
+ export interface IXtralabDiffModel extends Git.Diff.IModel {
15
+ isBinary?: boolean;
16
+ canDiscard?: boolean;
17
+ oldFilename?: string;
18
+ }
19
19
  /**
20
- * Resolve the pair of git references whose `content` should appear on the
21
- * left and right side of the diff for a given file change. Mirrors VS
22
- * Code's source control diff: staged-side compares INDEX vs HEAD,
23
- * unstaged-side compares WORKING vs INDEX, and untracked files have no
24
- * "previous" version to compare against.
20
+ * Application-level services the diff model does not carry.
25
21
  */
26
- export declare function resolveReferences(change: IFileChange): {
27
- oldRef: GitReference | null;
28
- newRef: GitReference | null;
29
- };
30
- export interface IGitDiffWidgetOptions {
31
- repoPath: string;
32
- change: IFileChange;
33
- themeManager: IThemeManager | null;
22
+ export interface IXtralabDiffContext {
34
23
  contentsManager: Contents.IManager;
24
+ rendermime: IRenderMimeRegistry | null;
25
+ themeManager: IThemeManager | null;
26
+ }
27
+ /**
28
+ * Shared diff widget used by the launcher and by `jupyterlab-git`.
29
+ */
30
+ export declare class XtralabDiffWidget extends ReactWidget implements Git.Diff.IDiffWidget {
31
+ constructor(model: Git.Diff.IModel, context: IXtralabDiffContext);
32
+ get model(): Git.Diff.IModel;
35
33
  /**
36
- * Rendermime registry used by the notebook diff path to render outputs
37
- * and markdown cells. May be `null` in stripped-down hosts; the notebook
38
- * diff falls back to a textual representation in that case.
34
+ * Swap the rendered model when the launcher reuses its preview tab.
39
35
  */
40
- rendermime: IRenderMimeRegistry | null;
36
+ setModel(model: IXtralabDiffModel): void;
41
37
  /**
42
- * Called after a hunk-level operation mutates the working tree, so the
43
- * surrounding plugin can refresh the changes panel.
38
+ * This renderer has no merge editor, so it never blocks mark-as-resolved.
44
39
  */
45
- onChanged: () => void;
40
+ get isFileResolved(): boolean;
46
41
  /**
47
- * Called when a post-discard reload finds no hunks left in the diff.
48
- * Wired by {@link createGitDiffWidget} to close the host
49
- * `MainAreaWidget` — there is nothing left to view, so the empty pane
50
- * would just be visual noise.
42
+ * Return the content jupyterlab-git should save for mark-as-resolved.
51
43
  */
52
- onEmpty?: () => void;
53
- onPinned?: (widget: MainAreaWidget<DiffContentWidget>) => void;
54
- pinned?: boolean;
55
- }
56
- /**
57
- * Build a `MainAreaWidget`-hosted diff viewer for a single file change.
58
- * Wrapping the `ReactWidget` in a `MainAreaWidget` is what gives it the
59
- * usual document chrome — toolbar slot, focus tracker integration, and
60
- * spinner-while-loading hooks — and matches the pattern used for other
61
- * read-only main-area widgets in the JupyterLab ecosystem.
62
- *
63
- * This is the launcher dashboard's own diff path
64
- * (`xtralab:git:open-diff`), kept independent of `jupyterlab-git`. The
65
- * actual diff rendering is delegated to the shared {@link DiffSurface};
66
- * `jupyterlab-git`'s panel reaches the same surface through the providers
67
- * registered in `diffProvider.tsx`.
68
- */
69
- export declare function createGitDiffWidget(options: IGitDiffWidgetOptions): MainAreaWidget<DiffContentWidget>;
70
- export declare function updateGitDiffWidget(widget: MainAreaWidget<DiffContentWidget>, change: IFileChange): void;
71
- /**
72
- * The actual React-rendered region of the diff viewer. Held in its own
73
- * `ReactWidget` so the surrounding `MainAreaWidget` can manage chrome
74
- * without React having to know about it.
75
- *
76
- * Owns the persistent UI state that the surrounding `MainAreaWidget`
77
- * toolbar needs to read and write (`notebookViewMode` and whether a
78
- * notebook view is even available for the current file). Storing this on
79
- * the widget — and exposing change signals — lets the toolbar widget and
80
- * the React content stay in sync without either side knowing about the
81
- * other.
82
- */
83
- export declare class DiffContentWidget extends ReactWidget {
84
- constructor(options: IGitDiffWidgetOptions);
85
- /** The change this widget renders, used by callers to identify the widget. */
86
- get change(): IFileChange;
87
- /** The user's current rendered-vs-JSON view choice for notebook diffs. */
44
+ getResolvedFile(): Promise<Partial<Contents.IModel>>;
45
+ /**
46
+ * Re-pull both sides of the model and re-render.
47
+ */
48
+ refresh(): Promise<void>;
88
49
  get notebookViewMode(): NotebookDiffViewMode;
89
- /** Update the notebook view mode and persist it across sessions. */
90
50
  setNotebookViewMode(mode: NotebookDiffViewMode): void;
91
- /** Emits whenever {@link notebookViewMode} changes. */
92
51
  get notebookViewModeChanged(): ISignal<this, NotebookDiffViewMode>;
93
- /**
94
- * Whether a rendered notebook view is currently available. Set by the
95
- * React content once it has parsed the diff; the toolbar reads this to
96
- * decide whether to show the view-mode selector.
97
- */
98
52
  get hasNotebookView(): boolean;
99
53
  setHasNotebookView(value: boolean): void;
100
- /** Emits whenever {@link hasNotebookView} changes. */
101
54
  get hasNotebookViewChanged(): ISignal<this, boolean>;
102
- get pinned(): boolean;
103
- pin(): void;
104
- get pinnedChanged(): ISignal<this, boolean>;
105
- setChange(change: IFileChange): void;
55
+ get diffStyle(): DiffStyle;
56
+ setDiffStyle(style: DiffStyle): void;
57
+ get diffStyleChanged(): ISignal<this, DiffStyle>;
58
+ /**
59
+ * Whether the Split/Unified toolbar selector currently applies.
60
+ */
61
+ get fileDiffActive(): boolean;
62
+ setFileDiffActive(value: boolean): void;
63
+ get fileDiffActiveChanged(): ISignal<this, boolean>;
64
+ /**
65
+ * Emitted when a post-discard reload leaves the diff with no hunks.
66
+ */
67
+ get emptied(): ISignal<this, void>;
68
+ notifyEmptied(): void;
69
+ settleRefresh(): void;
70
+ get reloadNonce(): number;
71
+ get context(): IXtralabDiffContext;
106
72
  protected render(): React.ReactElement;
107
- private _options;
73
+ private _model;
74
+ private _context;
75
+ private _reloadNonce;
76
+ private _pendingRefresh;
108
77
  private _notebookViewMode;
109
78
  private _notebookViewModeChanged;
110
79
  private _hasNotebookView;
111
80
  private _hasNotebookViewChanged;
112
- private _pinned;
113
- private _pinnedChanged;
81
+ private _diffStyle;
82
+ private _diffStyleChanged;
83
+ private _fileDiffActive;
84
+ private _fileDiffActiveChanged;
85
+ private _emptied;
86
+ }
87
+ /**
88
+ * Structural toolbar type shared across host package boundaries.
89
+ */
90
+ interface IDiffToolbar {
91
+ addItem(name: string, widget: Widget): boolean;
114
92
  }
93
+ /**
94
+ * Mount the shared diff toolbar controls into a host-owned toolbar.
95
+ */
96
+ export declare function addDiffToolbarItems(toolbar: IDiffToolbar, widget: XtralabDiffWidget): void;