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.
- package/README.md +18 -10
- package/lib/fileBrowser/commands.d.ts +7 -0
- package/lib/fileBrowser/commands.js +74 -1
- package/lib/fileBrowser/fileBrowser.js +4 -1
- package/lib/fileBrowser/icons.d.ts +5 -9
- package/lib/fileBrowser/icons.js +97 -13
- package/lib/fileBrowser/index.js +5 -3
- package/lib/fileTypeIcons/index.d.ts +6 -0
- package/lib/fileTypeIcons/index.js +144 -0
- package/lib/git/commands.d.ts +34 -33
- package/lib/git/commands.js +132 -10
- package/lib/git/diffModel.d.ts +14 -0
- package/lib/git/diffModel.js +75 -0
- package/lib/git/diffProvider.d.ts +0 -5
- package/lib/git/diffProvider.js +6 -284
- package/lib/git/diffSurface.d.ts +64 -25
- package/lib/git/diffSurface.js +84 -27
- package/lib/git/diffTheme.d.ts +9 -0
- package/lib/git/diffTheme.js +31 -0
- package/lib/git/diffWidget.d.ts +67 -85
- package/lib/git/diffWidget.js +242 -318
- package/lib/git/index.d.ts +1 -6
- package/lib/git/index.js +7 -23
- package/lib/git/notebookDiff.d.ts +6 -1
- package/lib/git/notebookDiff.js +4 -3
- package/lib/index.js +2 -0
- package/lib/terminals/model.d.ts +1 -1
- package/lib/terminals/model.js +1 -1
- package/lib/terminals/widget.d.ts +1 -1
- package/lib/terminals/widget.js +1 -1
- package/package.json +3 -3
- package/schema/plugin.json +6 -0
- package/src/fileBrowser/commands.ts +91 -2
- package/src/fileBrowser/fileBrowser.tsx +4 -1
- package/src/fileBrowser/icons.ts +103 -13
- package/src/fileBrowser/index.ts +9 -3
- package/src/fileTypeIcons/index.ts +174 -0
- package/src/git/commands.ts +177 -43
- package/src/git/diffModel.ts +102 -0
- package/src/git/diffProvider.tsx +8 -409
- package/src/git/diffSurface.tsx +187 -35
- package/src/git/diffTheme.ts +40 -0
- package/src/git/diffWidget.tsx +338 -393
- package/src/git/index.ts +14 -28
- package/src/git/notebookDiff.tsx +10 -3
- package/src/index.ts +2 -0
- package/src/terminals/model.ts +1 -1
- package/src/terminals/widget.tsx +1 -1
- package/style/base.css +1 -0
- package/style/git.css +40 -12
- package/style/index.css +1 -0
- package/style/index.js +1 -0
- package/style/launcher.css +46 -18
- package/style/sidebar.css +14 -0
package/lib/git/diffSurface.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Whether the host is still resolving the file contents.
|
|
53
|
+
*/
|
|
55
54
|
loading: boolean;
|
|
56
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Fatal error message to show instead of a diff, or `null`.
|
|
57
|
+
*/
|
|
57
58
|
error: string | null;
|
|
58
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Whether the file is binary (no textual diff is rendered).
|
|
61
|
+
*/
|
|
59
62
|
isBinary: boolean;
|
|
60
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Resolved old/reference text. Ignored while loading/binary/errored.
|
|
65
|
+
*/
|
|
61
66
|
oldText: string;
|
|
62
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Optional per-hunk discard wiring; omit for a read-only diff.
|
|
125
|
+
*/
|
|
98
126
|
hunkDiscard?: IHunkDiscard;
|
|
99
127
|
}
|
|
100
128
|
/**
|
|
101
|
-
*
|
|
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;
|
package/lib/git/diffSurface.js
CHANGED
|
@@ -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
|
-
*
|
|
129
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|
package/lib/git/diffWidget.d.ts
CHANGED
|
@@ -1,114 +1,96 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { IThemeManager
|
|
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 {
|
|
8
|
-
import {
|
|
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
|
-
*
|
|
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
|
|
14
|
+
export interface IXtralabDiffModel extends Git.Diff.IModel {
|
|
15
|
+
isBinary?: boolean;
|
|
16
|
+
canDiscard?: boolean;
|
|
17
|
+
oldFilename?: string;
|
|
18
|
+
}
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
36
|
+
setModel(model: IXtralabDiffModel): void;
|
|
41
37
|
/**
|
|
42
|
-
*
|
|
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
|
-
|
|
40
|
+
get isFileResolved(): boolean;
|
|
46
41
|
/**
|
|
47
|
-
*
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
get
|
|
105
|
-
|
|
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
|
|
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
|
|
113
|
-
private
|
|
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;
|