xtralab 0.15.0 → 0.15.2
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 +65 -30
- package/lib/about/icons.d.ts +0 -5
- package/lib/about/icons.js +0 -5
- package/lib/about/index.d.ts +4 -8
- package/lib/about/index.js +6 -17
- package/lib/agentSessions.d.ts +9 -29
- package/lib/agentSessions.js +2 -7
- package/lib/askAgent/editorSelection.d.ts +20 -26
- package/lib/askAgent/editorSelection.js +11 -26
- package/lib/askAgent/icons.d.ts +2 -3
- package/lib/askAgent/icons.js +2 -3
- package/lib/askAgent/index.d.ts +4 -26
- package/lib/askAgent/index.js +54 -158
- package/lib/askAgent/popup.d.ts +12 -16
- package/lib/askAgent/popup.js +18 -37
- package/lib/askAgent/prompt.d.ts +3 -5
- package/lib/askAgent/prompt.js +6 -19
- package/lib/askAgent/queue.d.ts +11 -21
- package/lib/askAgent/queue.js +9 -19
- package/lib/askAgent/queuePanel.d.ts +13 -18
- package/lib/askAgent/queuePanel.js +15 -37
- package/lib/askAgent/targetPicker.d.ts +2 -9
- package/lib/askAgent/targetPicker.js +6 -11
- package/lib/askAgent/tokens.d.ts +32 -46
- package/lib/askAgent/tokens.js +4 -6
- package/lib/commandBar/index.d.ts +4 -13
- package/lib/commandBar/index.js +21 -39
- package/lib/customPanel/index.js +15 -0
- package/lib/editorBreadcrumbs/index.d.ts +3 -6
- package/lib/editorBreadcrumbs/index.js +10 -6
- package/lib/editorBreadcrumbs/widget.d.ts +17 -4
- package/lib/editorBreadcrumbs/widget.js +11 -17
- package/lib/editorIndent/index.d.ts +4 -12
- package/lib/editorIndent/index.js +8 -23
- package/lib/fileBrowser/commands.d.ts +29 -19
- package/lib/fileBrowser/commands.js +83 -97
- package/lib/fileBrowser/contents.d.ts +3 -0
- package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
- package/lib/fileBrowser/dragAndDrop.js +2 -7
- package/lib/fileBrowser/fileBrowser.d.ts +19 -9
- package/lib/fileBrowser/fileBrowser.js +70 -222
- package/lib/fileBrowser/gitStatus.d.ts +16 -10
- package/lib/fileBrowser/gitStatus.js +31 -30
- package/lib/fileBrowser/gitignore.d.ts +4 -17
- package/lib/fileBrowser/gitignore.js +4 -25
- package/lib/fileBrowser/icons.d.ts +7 -17
- package/lib/fileBrowser/icons.js +25 -80
- package/lib/fileBrowser/index.js +2 -4
- package/lib/fileBrowser/widget.d.ts +141 -48
- package/lib/fileBrowser/widget.js +102 -4
- package/lib/fileTypeIcons/index.js +0 -10
- package/lib/git/api.d.ts +5 -13
- package/lib/git/api.js +10 -36
- package/lib/git/askRequest.d.ts +4 -8
- package/lib/git/askRequest.js +6 -20
- package/lib/git/commands.d.ts +45 -5
- package/lib/git/commands.js +20 -13
- package/lib/git/diffModel.js +4 -0
- package/lib/git/diffPreferences.d.ts +90 -0
- package/lib/git/diffPreferences.js +107 -0
- package/lib/git/diffProvider.js +10 -2
- package/lib/git/diffSurface.d.ts +64 -63
- package/lib/git/diffSurface.js +72 -213
- package/lib/git/diffTheme.d.ts +7 -0
- package/lib/git/diffTheme.js +23 -0
- package/lib/git/diffWidget.d.ts +70 -14
- package/lib/git/diffWidget.js +100 -94
- package/lib/git/imageDiff.d.ts +14 -6
- package/lib/git/imageDiff.js +19 -75
- package/lib/git/index.d.ts +0 -3
- package/lib/git/index.js +44 -10
- package/lib/git/notebookDiff.d.ts +110 -33
- package/lib/git/notebookDiff.js +55 -197
- package/lib/git/tokens.d.ts +73 -31
- package/lib/git/tokens.js +2 -3
- package/lib/highlight/index.d.ts +4 -10
- package/lib/highlight/index.js +10 -41
- package/lib/index.d.ts +0 -9
- package/lib/index.js +0 -9
- package/lib/launcher/agents.d.ts +55 -51
- package/lib/launcher/agents.js +15 -57
- package/lib/launcher/availability.d.ts +3 -9
- package/lib/launcher/availability.js +3 -9
- package/lib/launcher/commands.d.ts +7 -32
- package/lib/launcher/commands.js +14 -45
- package/lib/launcher/dashboard.d.ts +19 -24
- package/lib/launcher/dashboard.js +21 -67
- package/lib/launcher/editorRegistry.d.ts +10 -22
- package/lib/launcher/editorRegistry.js +14 -18
- package/lib/launcher/editors.d.ts +39 -45
- package/lib/launcher/editors.js +8 -34
- package/lib/launcher/icons.d.ts +0 -12
- package/lib/launcher/icons.js +5 -39
- package/lib/launcher/index.js +12 -55
- package/lib/launcher/invocation.d.ts +4 -10
- package/lib/launcher/invocation.js +7 -22
- package/lib/launcher/registry.d.ts +9 -7
- package/lib/launcher/registry.js +9 -7
- package/lib/launcher/tokens.d.ts +10 -22
- package/lib/launcher/tokens.js +5 -8
- package/lib/menuBar/index.d.ts +3 -10
- package/lib/menuBar/index.js +11 -28
- package/lib/menus/index.d.ts +3 -9
- package/lib/menus/index.js +14 -30
- package/lib/omnibox/files.d.ts +2 -4
- package/lib/omnibox/files.js +5 -11
- package/lib/omnibox/index.d.ts +4 -14
- package/lib/omnibox/index.js +9 -27
- package/lib/omnibox/model.d.ts +38 -10
- package/lib/omnibox/model.js +15 -33
- package/lib/omnibox/recents.d.ts +15 -17
- package/lib/omnibox/recents.js +12 -17
- package/lib/omnibox/tokens.d.ts +5 -10
- package/lib/omnibox/tokens.js +2 -6
- package/lib/omnibox/widget.d.ts +11 -7
- package/lib/omnibox/widget.js +8 -16
- package/lib/searchReplace/index.d.ts +4 -20
- package/lib/searchReplace/index.js +7 -27
- package/lib/showOutput/index.d.ts +3 -12
- package/lib/showOutput/index.js +6 -22
- package/lib/sidebar/index.d.ts +4 -7
- package/lib/sidebar/index.js +5 -12
- package/lib/terminalNotifications/index.d.ts +4 -10
- package/lib/terminalNotifications/index.js +6 -20
- package/lib/terminals/agentTerminals.d.ts +12 -5
- package/lib/terminals/agentTerminals.js +25 -44
- package/lib/terminals/detection.d.ts +4 -8
- package/lib/terminals/detection.js +4 -8
- package/lib/terminals/index.d.ts +0 -35
- package/lib/terminals/index.js +12 -101
- package/lib/terminals/model.d.ts +64 -139
- package/lib/terminals/model.js +80 -220
- package/lib/terminals/tokens.d.ts +11 -22
- package/lib/terminals/tokens.js +2 -2
- package/lib/terminals/widget.d.ts +43 -13
- package/lib/terminals/widget.js +33 -18
- package/lib/topBar/icons.d.ts +3 -8
- package/lib/topBar/icons.js +3 -8
- package/lib/topBar/index.d.ts +4 -11
- package/lib/topBar/index.js +10 -38
- package/lib/walkthrough/index.d.ts +4 -9
- package/lib/walkthrough/index.js +6 -20
- package/lib/walkthrough/panel.d.ts +40 -9
- package/lib/walkthrough/panel.js +4 -10
- package/package.json +5 -3
- package/schema/git-diff-preferences.json +42 -0
- package/src/about/icons.ts +0 -5
- package/src/about/index.tsx +6 -17
- package/src/agentSessions.ts +9 -29
- package/src/askAgent/editorSelection.ts +22 -29
- package/src/askAgent/icons.ts +2 -3
- package/src/askAgent/index.ts +54 -158
- package/src/askAgent/popup.tsx +23 -45
- package/src/askAgent/prompt.ts +6 -19
- package/src/askAgent/queue.ts +13 -24
- package/src/askAgent/queuePanel.tsx +21 -46
- package/src/askAgent/targetPicker.tsx +5 -15
- package/src/askAgent/tokens.ts +32 -46
- package/src/commandBar/index.ts +24 -39
- package/src/customPanel/index.ts +15 -0
- package/src/editorBreadcrumbs/index.ts +10 -6
- package/src/editorBreadcrumbs/widget.ts +23 -17
- package/src/editorIndent/index.ts +17 -23
- package/src/fileBrowser/commands.ts +119 -96
- package/src/fileBrowser/contents.ts +3 -0
- package/src/fileBrowser/dragAndDrop.ts +17 -7
- package/src/fileBrowser/fileBrowser.tsx +86 -230
- package/src/fileBrowser/gitStatus.ts +36 -33
- package/src/fileBrowser/gitignore.ts +4 -25
- package/src/fileBrowser/icons.ts +25 -80
- package/src/fileBrowser/index.ts +2 -4
- package/src/fileBrowser/widget.tsx +171 -50
- package/src/fileTypeIcons/index.ts +0 -10
- package/src/git/api.ts +10 -36
- package/src/git/askRequest.ts +6 -20
- package/src/git/commands.ts +83 -11
- package/src/git/diffModel.ts +25 -0
- package/src/git/diffPreferences.ts +192 -0
- package/src/git/diffProvider.tsx +11 -2
- package/src/git/diffSurface.tsx +165 -286
- package/src/git/diffTheme.ts +24 -0
- package/src/git/diffWidget.tsx +194 -134
- package/src/git/imageDiff.tsx +56 -84
- package/src/git/index.ts +54 -10
- package/src/git/notebookDiff.tsx +214 -240
- package/src/git/tokens.ts +73 -31
- package/src/highlight/index.ts +10 -41
- package/src/index.ts +0 -9
- package/src/launcher/agents.ts +64 -95
- package/src/launcher/availability.ts +3 -9
- package/src/launcher/commands.ts +14 -51
- package/src/launcher/dashboard.tsx +49 -88
- package/src/launcher/editorRegistry.ts +21 -29
- package/src/launcher/editors.ts +39 -58
- package/src/launcher/icons.ts +5 -39
- package/src/launcher/index.ts +12 -55
- package/src/launcher/invocation.ts +7 -22
- package/src/launcher/registry.ts +9 -7
- package/src/launcher/tokens.ts +10 -22
- package/src/menuBar/index.ts +11 -28
- package/src/menus/index.ts +14 -30
- package/src/omnibox/files.ts +10 -14
- package/src/omnibox/index.ts +9 -27
- package/src/omnibox/model.ts +50 -38
- package/src/omnibox/recents.ts +17 -22
- package/src/omnibox/tokens.ts +5 -10
- package/src/omnibox/widget.tsx +14 -19
- package/src/searchReplace/index.ts +7 -27
- package/src/showOutput/index.ts +6 -22
- package/src/sidebar/index.ts +20 -12
- package/src/terminalNotifications/index.ts +42 -25
- package/src/terminals/agentTerminals.ts +25 -49
- package/src/terminals/detection.ts +4 -8
- package/src/terminals/index.ts +12 -101
- package/src/terminals/model.ts +130 -246
- package/src/terminals/tokens.ts +11 -22
- package/src/terminals/widget.tsx +48 -31
- package/src/topBar/icons.ts +3 -8
- package/src/topBar/index.ts +21 -44
- package/src/walkthrough/index.ts +6 -20
- package/src/walkthrough/panel.ts +40 -10
- package/style/about.css +2 -7
- package/style/askAgent.css +8 -40
- package/style/base.css +9 -45
- package/style/chrome.css +108 -308
- package/style/commandBar.css +4 -20
- package/style/customPanel.css +1 -4
- package/style/git.css +26 -186
- package/style/highlight.css +1 -3
- package/style/launcher.css +6 -70
- package/style/omnibox.css +2 -14
- package/style/showOutput.css +0 -6
- package/style/sidebar.css +29 -9
- package/style/tabs.css +5 -13
- package/style/terminals.css +6 -53
- package/style/topBar.css +2 -22
- package/style/walkthrough.css +0 -8
package/src/git/diffSurface.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
|
5
5
|
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
6
6
|
import { undoIcon } from '@jupyterlab/ui-components';
|
|
7
7
|
import { FileDiff } from '@pierre/diffs/react';
|
|
8
|
+
import { IconDiffSplit, IconDiffUnified, IconWordWrap } from '@pierre/icons';
|
|
8
9
|
import {
|
|
9
10
|
diffAcceptRejectHunk,
|
|
10
11
|
parseDiffFromFile,
|
|
@@ -20,161 +21,52 @@ import {
|
|
|
20
21
|
NotebookDiffView,
|
|
21
22
|
type INotebookDiffResult
|
|
22
23
|
} from './notebookDiff';
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_SPLIT_RATIO,
|
|
26
|
+
MAX_SPLIT_RATIO,
|
|
27
|
+
MIN_SPLIT_RATIO,
|
|
28
|
+
type DiffStyle,
|
|
29
|
+
type DiffStyleControlMode,
|
|
30
|
+
type ImageDiffViewMode,
|
|
31
|
+
type NotebookDiffViewMode
|
|
32
|
+
} from './diffPreferences';
|
|
33
|
+
import { DIFF_SCROLLBAR_CSS, resolveDiffTheme } from './diffTheme';
|
|
24
34
|
|
|
25
|
-
/**
|
|
26
|
-
* The CSS class added to the diff main-area widget. The selectors that
|
|
27
|
-
* style the embedded `@pierre/diffs` viewer hang off this class.
|
|
28
|
-
*/
|
|
29
35
|
export const DIFF_WIDGET_CSS_CLASS = 'jp-xtralab-DiffWidget';
|
|
30
36
|
|
|
31
37
|
/**
|
|
32
|
-
* `
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
const SPLIT_RATIO_STORAGE_KEY = 'xtralab:diff-split-ratio';
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Bounds on the left-pane fraction. Keeping a few percent on either side
|
|
40
|
-
* guarantees both panes remain visible — dragging to 0/100 would hide a
|
|
41
|
-
* column and is almost never what the user wants.
|
|
42
|
-
*/
|
|
43
|
-
const MIN_SPLIT_RATIO = 0.1;
|
|
44
|
-
const MAX_SPLIT_RATIO = 0.9;
|
|
45
|
-
const DEFAULT_SPLIT_RATIO = 0.5;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* CSS injected via the `@pierre/diffs` `unsafeCSS` option. The library
|
|
49
|
-
* places it inside the shadow root under `@layer unsafe`, which beats
|
|
50
|
-
* the library's own `@layer base` rule that hardcodes
|
|
51
|
-
* `grid-template-columns: 1fr 1fr` on the split-mode container.
|
|
52
|
-
*
|
|
53
|
-
* The override reads from `--xtralab-split-cols`, a custom property we set
|
|
54
|
-
* on the host element via the `style` prop. Custom properties inherit
|
|
55
|
-
* through the shadow DOM boundary, so adjusting it on the host instantly
|
|
56
|
-
* resizes the columns without re-rendering the diff.
|
|
38
|
+
* Injected via the `unsafeCSS` option into the shadow root's `@layer
|
|
39
|
+
* unsafe`, beating the library's base rule that hardcodes `1fr 1fr`. Reads
|
|
40
|
+
* `--xtralab-split-cols` set on the host — custom properties cross the
|
|
41
|
+
* shadow boundary, so resizing needs no re-render.
|
|
57
42
|
*/
|
|
58
43
|
const SPLIT_RESIZE_CSS = `pre[data-diff-type="split"][data-overflow="scroll"] {
|
|
59
44
|
grid-template-columns: var(--xtralab-split-cols, 1fr 1fr);
|
|
60
45
|
}`;
|
|
61
46
|
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const raw = window.localStorage.getItem(SPLIT_RATIO_STORAGE_KEY);
|
|
65
|
-
if (raw === null) {
|
|
66
|
-
return DEFAULT_SPLIT_RATIO;
|
|
67
|
-
}
|
|
68
|
-
const parsed = Number.parseFloat(raw);
|
|
69
|
-
if (
|
|
70
|
-
Number.isFinite(parsed) &&
|
|
71
|
-
parsed >= MIN_SPLIT_RATIO &&
|
|
72
|
-
parsed <= MAX_SPLIT_RATIO
|
|
73
|
-
) {
|
|
74
|
-
return parsed;
|
|
75
|
-
}
|
|
76
|
-
} catch {
|
|
77
|
-
// localStorage may throw in privacy mode or sandboxed contexts. Falling
|
|
78
|
-
// back to the default is fine — the user can resize again on this run.
|
|
79
|
-
}
|
|
80
|
-
return DEFAULT_SPLIT_RATIO;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function writeStoredSplitRatio(ratio: number): void {
|
|
84
|
-
try {
|
|
85
|
-
window.localStorage.setItem(SPLIT_RATIO_STORAGE_KEY, ratio.toString());
|
|
86
|
-
} catch {
|
|
87
|
-
// See readStoredSplitRatio — best-effort persistence.
|
|
88
|
-
}
|
|
89
|
-
}
|
|
47
|
+
const DIFF_SURFACE_CSS = `${SPLIT_RESIZE_CSS}\n${DIFF_SCROLLBAR_CSS}`;
|
|
90
48
|
|
|
91
49
|
/**
|
|
92
|
-
*
|
|
93
|
-
* `diffStyle` option. `split` is the side-by-side old|new view; `unified`
|
|
94
|
-
* is the single-column inline view (what diffs.com calls "stacked").
|
|
95
|
-
*/
|
|
96
|
-
export type DiffStyle = 'split' | 'unified';
|
|
97
|
-
|
|
98
|
-
const DIFF_STYLE_STORAGE_KEY = 'xtralab:diff-style';
|
|
99
|
-
|
|
100
|
-
export function readStoredDiffStyle(): DiffStyle {
|
|
101
|
-
try {
|
|
102
|
-
const raw = window.localStorage.getItem(DIFF_STYLE_STORAGE_KEY);
|
|
103
|
-
if (raw === 'split' || raw === 'unified') {
|
|
104
|
-
return raw;
|
|
105
|
-
}
|
|
106
|
-
} catch {
|
|
107
|
-
// See readStoredSplitRatio — privacy-mode / sandboxed contexts can throw.
|
|
108
|
-
}
|
|
109
|
-
return 'split';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function writeStoredDiffStyle(style: DiffStyle): void {
|
|
113
|
-
try {
|
|
114
|
-
window.localStorage.setItem(DIFF_STYLE_STORAGE_KEY, style);
|
|
115
|
-
} catch {
|
|
116
|
-
// Best-effort.
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* View modes available for `.ipynb` diffs. `notebook` is the cell-by-cell
|
|
122
|
-
* rendered view; `json` is the raw nbformat JSON file diff, for inspecting
|
|
123
|
-
* exactly which bytes changed.
|
|
124
|
-
*/
|
|
125
|
-
export type NotebookDiffViewMode = 'notebook' | 'json';
|
|
126
|
-
|
|
127
|
-
const NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY = 'xtralab:notebook-diff-view-mode';
|
|
128
|
-
|
|
129
|
-
export function readStoredNotebookViewMode(): NotebookDiffViewMode {
|
|
130
|
-
try {
|
|
131
|
-
const raw = window.localStorage.getItem(
|
|
132
|
-
NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY
|
|
133
|
-
);
|
|
134
|
-
if (raw === 'json' || raw === 'notebook') {
|
|
135
|
-
return raw;
|
|
136
|
-
}
|
|
137
|
-
} catch {
|
|
138
|
-
// See readStoredSplitRatio — privacy-mode / sandboxed contexts can
|
|
139
|
-
// throw on access. The notebook view is the better default anyway.
|
|
140
|
-
}
|
|
141
|
-
return 'notebook';
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function writeStoredNotebookViewMode(mode: NotebookDiffViewMode): void {
|
|
145
|
-
try {
|
|
146
|
-
window.localStorage.setItem(NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY, mode);
|
|
147
|
-
} catch {
|
|
148
|
-
// Best-effort.
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Annotation payload threaded through the diff library back into the
|
|
154
|
-
* `renderAnnotation` callback. Carries the index of the hunk a particular
|
|
155
|
-
* action button targets.
|
|
50
|
+
* Annotation payload threaded back into `renderAnnotation`; carries the target hunk index.
|
|
156
51
|
*/
|
|
157
52
|
interface IHunkActionAnnotation {
|
|
53
|
+
/**
|
|
54
|
+
* The index of the hunk the action targets.
|
|
55
|
+
*/
|
|
158
56
|
hunkIndex: number;
|
|
159
57
|
}
|
|
160
58
|
|
|
161
59
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* another `.ipynb`-named file for a notebook is harmless because
|
|
165
|
-
* {@link buildNotebookDiff} validates the JSON shape and we fall back to
|
|
166
|
-
* the file diff if parsing fails.
|
|
60
|
+
* Extension-only check; a false positive is harmless because
|
|
61
|
+
* {@link buildNotebookDiff} validates the JSON and the file diff is the fallback.
|
|
167
62
|
*/
|
|
168
63
|
function isNotebookPath(path: string): boolean {
|
|
169
64
|
return path.toLowerCase().endsWith('.ipynb');
|
|
170
65
|
}
|
|
171
66
|
|
|
172
67
|
/**
|
|
173
|
-
*
|
|
174
|
-
* `
|
|
175
|
-
* `data-jp-theme-light` attribute that JupyterLab sets on `<body>` so the
|
|
176
|
-
* component still works in stripped-down hosts where the token isn't
|
|
177
|
-
* provided (lite, certain notebook configurations, …).
|
|
68
|
+
* Dark-theme check via `IThemeManager`, falling back to the
|
|
69
|
+
* `data-jp-theme-light` body attribute for hosts without the token.
|
|
178
70
|
*/
|
|
179
71
|
export function isDarkTheme(themeManager: IThemeManager | null): boolean {
|
|
180
72
|
if (themeManager !== null && themeManager.theme !== null) {
|
|
@@ -192,19 +84,27 @@ export function isPierreTheme(themeManager: IThemeManager | null): boolean {
|
|
|
192
84
|
}
|
|
193
85
|
|
|
194
86
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* reverts that hunk and hands the rebuilt full-file text back to the host
|
|
198
|
-
* via {@link IHunkDiscard.save}. The host owns the actual write (it knows
|
|
199
|
-
* the on-disk path) and triggers any follow-up refresh in
|
|
200
|
-
* {@link IHunkDiscard.onAfterSave}.
|
|
87
|
+
* Per-hunk discard wiring: discarding rebuilds the full file text and the
|
|
88
|
+
* host owns the write via `save`, then refreshes in `onAfterSave`.
|
|
201
89
|
*/
|
|
202
90
|
interface IHunkDiscard {
|
|
91
|
+
/**
|
|
92
|
+
* Whether hunk discarding is available.
|
|
93
|
+
*/
|
|
203
94
|
enabled: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Persist the full post-discard file text.
|
|
97
|
+
*/
|
|
204
98
|
save: (fullText: string) => Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Called after a successful save so the host can refresh the diff.
|
|
101
|
+
*/
|
|
205
102
|
onAfterSave: () => void;
|
|
206
103
|
}
|
|
207
104
|
|
|
105
|
+
/**
|
|
106
|
+
* The props for the {@link DiffSurface} component.
|
|
107
|
+
*/
|
|
208
108
|
interface IDiffSurfaceProps {
|
|
209
109
|
/**
|
|
210
110
|
* Whether the host is still resolving the file contents.
|
|
@@ -227,13 +127,11 @@ interface IDiffSurfaceProps {
|
|
|
227
127
|
*/
|
|
228
128
|
newText: string;
|
|
229
129
|
/**
|
|
230
|
-
* New-side path
|
|
231
|
-
* name on the additions side.
|
|
130
|
+
* New-side path; drives notebook detection and the highlighting filename.
|
|
232
131
|
*/
|
|
233
132
|
newName: string;
|
|
234
133
|
/**
|
|
235
|
-
* Old-side path
|
|
236
|
-
* to {@link newName} when the host does not track a previous path.
|
|
134
|
+
* Old-side path; differs from {@link newName} only for renames.
|
|
237
135
|
*/
|
|
238
136
|
oldName?: string;
|
|
239
137
|
/**
|
|
@@ -245,35 +143,48 @@ interface IDiffSurfaceProps {
|
|
|
245
143
|
*/
|
|
246
144
|
pierreTheme: boolean;
|
|
247
145
|
/**
|
|
248
|
-
*
|
|
249
|
-
* markdown cells. May be `null` in stripped-down hosts; the notebook
|
|
250
|
-
* diff falls back to a textual representation in that case.
|
|
146
|
+
* Used by the notebook view; `null` in stripped-down hosts, which forces a textual fallback.
|
|
251
147
|
*/
|
|
252
148
|
rendermime: IRenderMimeRegistry | null;
|
|
253
149
|
/**
|
|
254
|
-
*
|
|
150
|
+
* Rendered-vs-JSON choice for notebook diffs (host-controlled).
|
|
255
151
|
*/
|
|
256
152
|
notebookViewMode: NotebookDiffViewMode;
|
|
257
153
|
/**
|
|
258
|
-
* Split vs unified layout for the textual
|
|
154
|
+
* Split vs unified layout for the textual file diff (host-controlled).
|
|
259
155
|
*/
|
|
260
156
|
diffStyle: DiffStyle;
|
|
261
157
|
/**
|
|
262
|
-
*
|
|
263
|
-
|
|
158
|
+
* Whether long lines wrap instead of scrolling horizontally (host-controlled).
|
|
159
|
+
*/
|
|
160
|
+
lineWrap: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Left pane width in split view, as a fraction of the diff width (host-controlled).
|
|
163
|
+
*/
|
|
164
|
+
splitRatio: number;
|
|
165
|
+
/**
|
|
166
|
+
* Called when the user finishes resizing the split panes.
|
|
167
|
+
*/
|
|
168
|
+
onSplitRatioChange: (ratio: number) => void;
|
|
169
|
+
/**
|
|
170
|
+
* 2-up, swipe or onion view for image diffs (host-controlled).
|
|
171
|
+
*/
|
|
172
|
+
imageViewMode: ImageDiffViewMode;
|
|
173
|
+
/**
|
|
174
|
+
* Called when the user picks another image view mode.
|
|
175
|
+
*/
|
|
176
|
+
onImageViewModeChange: (mode: ImageDiffViewMode) => void;
|
|
177
|
+
/**
|
|
178
|
+
* Fires when rendered-notebook availability changes so the host can toggle its Notebook/JSON control.
|
|
264
179
|
*/
|
|
265
180
|
onNotebookAvailabilityChange?: (available: boolean) => void;
|
|
266
181
|
/**
|
|
267
|
-
*
|
|
268
|
-
* unified choice affects) becomes the active view, so the host can
|
|
269
|
-
* show/hide its Split/Unified toolbar toggle. False for image, binary,
|
|
270
|
-
* rendered-notebook, loading and error states.
|
|
182
|
+
* Fires when the textual file diff becomes (in)active — the only view the split/unified choice affects.
|
|
271
183
|
*/
|
|
272
184
|
onFileDiffActiveChange?: (active: boolean) => void;
|
|
273
185
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
* post-discard behaviors such as auto-closing an emptied diff.
|
|
186
|
+
* Fires after each diff computation with the hunk count (`null` when no
|
|
187
|
+
* textual diff); hosts use it to auto-close emptied diffs.
|
|
277
188
|
*/
|
|
278
189
|
onMetadataChange?: (info: { hunkCount: number | null }) => void;
|
|
279
190
|
/**
|
|
@@ -281,11 +192,8 @@ interface IDiffSurfaceProps {
|
|
|
281
192
|
*/
|
|
282
193
|
hunkDiscard?: IHunkDiscard;
|
|
283
194
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* button appears on the hovered/selected lines. Clicking that button
|
|
287
|
-
* invokes this callback with the selected range and the button's viewport
|
|
288
|
-
* rectangle (to anchor a popup to), `null` when it cannot be measured.
|
|
195
|
+
* Enables line selection and the gutter "+" button; called with the
|
|
196
|
+
* selected range and the button's viewport rect (`null` when unmeasurable).
|
|
289
197
|
*/
|
|
290
198
|
onLineAsk?: (range: SelectedLineRange, anchor: DOMRect | null) => void;
|
|
291
199
|
/**
|
|
@@ -311,6 +219,11 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
311
219
|
rendermime,
|
|
312
220
|
notebookViewMode,
|
|
313
221
|
diffStyle,
|
|
222
|
+
lineWrap,
|
|
223
|
+
splitRatio,
|
|
224
|
+
onSplitRatioChange,
|
|
225
|
+
imageViewMode,
|
|
226
|
+
onImageViewModeChange,
|
|
314
227
|
onNotebookAvailabilityChange,
|
|
315
228
|
onFileDiffActiveChange,
|
|
316
229
|
onMetadataChange,
|
|
@@ -321,15 +234,11 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
321
234
|
|
|
322
235
|
const hasContent = !loading && error === null && !isBinary;
|
|
323
236
|
|
|
324
|
-
//
|
|
325
|
-
// text/notebook diff; the host passes the two sides as base64 strings in
|
|
326
|
-
// `oldText`/`newText` (the git server base64-encodes binary content).
|
|
237
|
+
// For raster images the host passes both sides as base64 in oldText/newText.
|
|
327
238
|
const imageType = React.useMemo(() => imageDataType(newName), [newName]);
|
|
328
239
|
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
// {@link diffAcceptRejectHunk} when the user discards a hunk — so hunk
|
|
332
|
-
// indexes line up between what the user clicked and what we mutate.
|
|
240
|
+
// Owning the metadata here keeps hunk indexes aligned between FileDiff
|
|
241
|
+
// and diffAcceptRejectHunk on discard.
|
|
333
242
|
const metadata = React.useMemo<FileDiffMetadata | null>(() => {
|
|
334
243
|
if (!hasContent || imageType !== null) {
|
|
335
244
|
return null;
|
|
@@ -342,10 +251,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
342
251
|
return parseDiffFromFile(oldFile, newFile);
|
|
343
252
|
}, [hasContent, imageType, oldText, newText, oldName, newName]);
|
|
344
253
|
|
|
345
|
-
// Cell-by-cell notebook diff for `.ipynb` files. When non-null the
|
|
346
|
-
// renderer uses a notebook-aware view instead of the line-oriented file
|
|
347
|
-
// diff — kept distinct from {@link metadata} so a notebook whose JSON we
|
|
348
|
-
// cannot parse can fall back to the file diff path.
|
|
349
254
|
const notebookDiff = React.useMemo<INotebookDiffResult | null>(() => {
|
|
350
255
|
if (!hasContent || imageType !== null || !isNotebookPath(newName)) {
|
|
351
256
|
return null;
|
|
@@ -353,43 +258,33 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
353
258
|
return buildNotebookDiff({ oldText, newText });
|
|
354
259
|
}, [hasContent, imageType, oldText, newText, newName]);
|
|
355
260
|
|
|
356
|
-
// Tell the host whether a rendered notebook view is currently available
|
|
357
|
-
// so it can show/hide its Notebook/JSON toggle.
|
|
358
261
|
React.useEffect(() => {
|
|
359
262
|
onNotebookAvailabilityChange?.(notebookDiff !== null);
|
|
360
263
|
}, [onNotebookAvailabilityChange, notebookDiff]);
|
|
361
264
|
|
|
362
|
-
// Report the diff shape so hosts can react (e.g. auto-close on empty).
|
|
363
265
|
React.useEffect(() => {
|
|
364
266
|
onMetadataChange?.({
|
|
365
267
|
hunkCount: metadata !== null ? metadata.hunks.length : null
|
|
366
268
|
});
|
|
367
269
|
}, [onMetadataChange, metadata]);
|
|
368
270
|
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
// Computed before the early returns below so the file-diff-active effect
|
|
372
|
-
// can depend on it without violating the rules of hooks.
|
|
271
|
+
// Computed before the early returns so the hooks below can depend on
|
|
272
|
+
// them without breaking the rules of hooks.
|
|
373
273
|
const hasNotebookView = notebookDiff !== null;
|
|
374
274
|
const showNotebookView = hasNotebookView && notebookViewMode === 'notebook';
|
|
375
275
|
const showFileDiff = !showNotebookView && metadata !== null;
|
|
376
276
|
|
|
377
|
-
// The split vs unified choice only affects the textual/code file diff, so
|
|
378
|
-
// tell the host whether that view is active to drive its Split/Unified
|
|
379
|
-
// toolbar toggle.
|
|
380
277
|
React.useEffect(() => {
|
|
381
278
|
onFileDiffActiveChange?.(showFileDiff);
|
|
382
279
|
}, [onFileDiffActiveChange, showFileDiff]);
|
|
383
280
|
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
);
|
|
281
|
+
// Local while dragging; the host hears the ratio when a drag ends.
|
|
282
|
+
const [leftRatio, setLeftRatio] = React.useState<number>(splitRatio);
|
|
283
|
+
React.useEffect(() => {
|
|
284
|
+
setLeftRatio(splitRatio);
|
|
285
|
+
}, [splitRatio]);
|
|
390
286
|
const wrapperRef = React.useRef<HTMLDivElement | null>(null);
|
|
391
|
-
//
|
|
392
|
-
// attach on pointer-down don't capture a stale value across renders.
|
|
287
|
+
// Drag listeners read the live ratio from a ref to avoid stale captures.
|
|
393
288
|
const leftRatioRef = React.useRef(leftRatio);
|
|
394
289
|
React.useEffect(() => {
|
|
395
290
|
leftRatioRef.current = leftRatio;
|
|
@@ -405,8 +300,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
405
300
|
}
|
|
406
301
|
return metadata.hunks.map((hunk, hunkIndex) => ({
|
|
407
302
|
side: 'additions',
|
|
408
|
-
// The first line of each hunk in the new file. We anchor the button
|
|
409
|
-
// there so it sits at the top of the hunk's body.
|
|
410
303
|
lineNumber: hunk.additionStart,
|
|
411
304
|
metadata: { hunkIndex }
|
|
412
305
|
}));
|
|
@@ -418,12 +311,8 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
418
311
|
return;
|
|
419
312
|
}
|
|
420
313
|
const updated = diffAcceptRejectHunk(metadata, hunkIndex, 'reject');
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
// file contents with `/(?<=\n)/` (a lookbehind that keeps the
|
|
424
|
-
// trailing `\n` on each line), so the entries already carry their
|
|
425
|
-
// own line endings — joining with `''` rebuilds the original text
|
|
426
|
-
// verbatim, while joining with `'\n'` would double every newline.
|
|
314
|
+
// additionLines holds the full new file with the hunk reverted; lines keep
|
|
315
|
+
// their trailing `\n` (lookbehind split), so join('') rebuilds it verbatim.
|
|
427
316
|
const text = updated.additionLines.join('');
|
|
428
317
|
try {
|
|
429
318
|
await hunkDiscard.save(text);
|
|
@@ -469,9 +358,7 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
469
358
|
if (onLineAsk === undefined) {
|
|
470
359
|
return;
|
|
471
360
|
}
|
|
472
|
-
// Anchor to the gutter "+" button
|
|
473
|
-
// the `@pierre/diffs` shadow root (which is open), parked on the
|
|
474
|
-
// hovered/selected line's number element.
|
|
361
|
+
// Anchor to the just-clicked gutter "+" button inside the library's open shadow root.
|
|
475
362
|
const slot = wrapperRef.current
|
|
476
363
|
?.querySelector('diffs-container')
|
|
477
364
|
?.shadowRoot?.querySelector('[data-gutter-utility-slot]');
|
|
@@ -485,8 +372,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
485
372
|
|
|
486
373
|
const handleResizerPointerDown = React.useCallback(
|
|
487
374
|
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
488
|
-
// Only react to primary-button drags. Right-click / middle-click on
|
|
489
|
-
// the handle should pass through to the browser default.
|
|
490
375
|
if (event.button !== 0) {
|
|
491
376
|
return;
|
|
492
377
|
}
|
|
@@ -519,21 +404,19 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
519
404
|
handle.removeEventListener('pointermove', onPointerMove);
|
|
520
405
|
handle.removeEventListener('pointerup', onPointerEnd);
|
|
521
406
|
handle.removeEventListener('pointercancel', onPointerEnd);
|
|
522
|
-
|
|
523
|
-
// burst of writes for every intermediate position.
|
|
524
|
-
writeStoredSplitRatio(leftRatioRef.current);
|
|
407
|
+
onSplitRatioChange(leftRatioRef.current);
|
|
525
408
|
};
|
|
526
409
|
handle.addEventListener('pointermove', onPointerMove);
|
|
527
410
|
handle.addEventListener('pointerup', onPointerEnd);
|
|
528
411
|
handle.addEventListener('pointercancel', onPointerEnd);
|
|
529
412
|
},
|
|
530
|
-
[]
|
|
413
|
+
[onSplitRatioChange]
|
|
531
414
|
);
|
|
532
415
|
|
|
533
416
|
const handleResizerDoubleClick = React.useCallback(() => {
|
|
534
417
|
setLeftRatio(DEFAULT_SPLIT_RATIO);
|
|
535
|
-
|
|
536
|
-
}, []);
|
|
418
|
+
onSplitRatioChange(DEFAULT_SPLIT_RATIO);
|
|
419
|
+
}, [onSplitRatioChange]);
|
|
537
420
|
|
|
538
421
|
const handleResizerKeyDown = React.useCallback(
|
|
539
422
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
@@ -562,9 +445,9 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
562
445
|
Math.min(MAX_SPLIT_RATIO, next)
|
|
563
446
|
);
|
|
564
447
|
setLeftRatio(clamped);
|
|
565
|
-
|
|
448
|
+
onSplitRatioChange(clamped);
|
|
566
449
|
},
|
|
567
|
-
[]
|
|
450
|
+
[onSplitRatioChange]
|
|
568
451
|
);
|
|
569
452
|
|
|
570
453
|
if (loading) {
|
|
@@ -589,15 +472,14 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
589
472
|
);
|
|
590
473
|
}
|
|
591
474
|
if (imageType !== null) {
|
|
592
|
-
// `oldText`/`newText` carry the two revisions as base64 here. The
|
|
593
|
-
// image view owns its own layout and mode selector, so it does not
|
|
594
|
-
// use the split resizer or the notebook/JSON toggle.
|
|
595
475
|
return (
|
|
596
476
|
<div className="jp-xtralab-DiffWidget-content">
|
|
597
477
|
<ImageDiffView
|
|
598
478
|
reference={oldText}
|
|
599
479
|
challenger={newText}
|
|
600
480
|
fileType={imageType}
|
|
481
|
+
mode={imageViewMode}
|
|
482
|
+
onModeChange={onImageViewModeChange}
|
|
601
483
|
trans={trans}
|
|
602
484
|
/>
|
|
603
485
|
</div>
|
|
@@ -611,11 +493,10 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
611
493
|
);
|
|
612
494
|
}
|
|
613
495
|
|
|
614
|
-
// Custom property set on the diffs-container host. The library's
|
|
615
|
-
// shadow-root rule consumes it via `var(--xtralab-split-cols, …)` (see
|
|
616
|
-
// SPLIT_RESIZE_CSS) and recomputes the column tracks instantly. Only
|
|
617
|
-
// relevant in the file-diff path; the notebook view doesn't use it.
|
|
618
496
|
const leftPercent = leftRatio * 100;
|
|
497
|
+
// The wrapped split grid sizes its gutter tracks to their content, so a
|
|
498
|
+
// percentage split cannot line up with the handle.
|
|
499
|
+
const showResizer = showFileDiff && diffStyle === 'split' && !lineWrap;
|
|
619
500
|
const hostStyle = {
|
|
620
501
|
'--xtralab-split-cols': `${leftPercent}% ${100 - leftPercent}%`
|
|
621
502
|
} as React.CSSProperties;
|
|
@@ -638,30 +519,17 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
638
519
|
lineAnnotations={lineAnnotations}
|
|
639
520
|
renderAnnotation={renderAnnotation}
|
|
640
521
|
style={hostStyle}
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
// `@pierre/diffs/worker/worker.js` file at a URL the worker
|
|
644
|
-
// bootstrap can resolve from, so leaving the pool enabled
|
|
645
|
-
// crashes on instantiation. Running on the main thread is
|
|
646
|
-
// fine for the small files git diffs typically operate on.
|
|
522
|
+
// JupyterLab's federation pipeline can't serve the worker bundle
|
|
523
|
+
// at a resolvable URL, so the pool crashes; run on the main thread.
|
|
647
524
|
disableWorkerPool={true}
|
|
648
525
|
options={{
|
|
649
526
|
diffStyle,
|
|
650
|
-
|
|
651
|
-
// file name and the panel header carries the change
|
|
652
|
-
// context.
|
|
527
|
+
overflow: lineWrap ? 'wrap' : 'scroll',
|
|
653
528
|
disableFileHeader: true,
|
|
654
529
|
theme: resolveDiffTheme(dark, pierreTheme),
|
|
655
530
|
themeType: dark ? 'dark' : 'light',
|
|
656
|
-
//
|
|
657
|
-
|
|
658
|
-
// string constant lets the library short-circuit the
|
|
659
|
-
// re-render path it uses when this option actually
|
|
660
|
-
// changes.
|
|
661
|
-
unsafeCSS: SPLIT_RESIZE_CSS,
|
|
662
|
-
// Line selection + the gutter "+" button feed the
|
|
663
|
-
// ask-agent popup; only wired up when a handler exists so
|
|
664
|
-
// a plain diff keeps its passive gutter.
|
|
531
|
+
// Constant string lets the library skip its unsafeCSS re-render path.
|
|
532
|
+
unsafeCSS: DIFF_SURFACE_CSS,
|
|
665
533
|
...(onLineAsk !== undefined
|
|
666
534
|
? {
|
|
667
535
|
enableLineSelection: true,
|
|
@@ -673,7 +541,7 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
673
541
|
/>
|
|
674
542
|
) : null}
|
|
675
543
|
</div>
|
|
676
|
-
{
|
|
544
|
+
{showResizer ? (
|
|
677
545
|
<div
|
|
678
546
|
className="jp-xtralab-DiffWidget-resizer"
|
|
679
547
|
style={{ left: `${leftPercent}%` }}
|
|
@@ -698,10 +566,8 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
|
|
|
698
566
|
}
|
|
699
567
|
|
|
700
568
|
/**
|
|
701
|
-
* Segmented Notebook/JSON selector. Hosts mount
|
|
702
|
-
*
|
|
703
|
-
* `jupyterlab-git`-provided diff toolbar) and drive its value/visibility
|
|
704
|
-
* from the same state they pass to {@link DiffSurface}.
|
|
569
|
+
* Segmented Notebook/JSON selector. Hosts mount it into their own toolbar
|
|
570
|
+
* and drive value/visibility from the state they pass to {@link DiffSurface}.
|
|
705
571
|
*/
|
|
706
572
|
export function NotebookViewModeControl(props: {
|
|
707
573
|
mode: NotebookDiffViewMode;
|
|
@@ -744,59 +610,41 @@ export function NotebookViewModeControl(props: {
|
|
|
744
610
|
}
|
|
745
611
|
|
|
746
612
|
/**
|
|
747
|
-
* Split
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
* sprite sheet being injected into the document. `currentColor` lets the
|
|
751
|
-
* segmented-button styling drive the fill (including the active state).
|
|
752
|
-
*/
|
|
753
|
-
function DiffSplitIcon(): React.ReactElement {
|
|
754
|
-
return (
|
|
755
|
-
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
756
|
-
<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" />
|
|
757
|
-
<path
|
|
758
|
-
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"
|
|
759
|
-
opacity=".3"
|
|
760
|
-
/>
|
|
761
|
-
</svg>
|
|
762
|
-
);
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
function DiffUnifiedIcon(): React.ReactElement {
|
|
766
|
-
return (
|
|
767
|
-
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
768
|
-
<path
|
|
769
|
-
fillRule="evenodd"
|
|
770
|
-
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"
|
|
771
|
-
clipRule="evenodd"
|
|
772
|
-
/>
|
|
773
|
-
<path
|
|
774
|
-
fillRule="evenodd"
|
|
775
|
-
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"
|
|
776
|
-
clipRule="evenodd"
|
|
777
|
-
opacity=".4"
|
|
778
|
-
/>
|
|
779
|
-
</svg>
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
/**
|
|
784
|
-
* Segmented Split/Unified selector. Mirrors {@link NotebookViewModeControl}:
|
|
785
|
-
* hosts mount it into their own toolbar and drive its value/visibility from
|
|
786
|
-
* the same state they pass to {@link DiffSurface}. Only meaningful while the
|
|
787
|
-
* textual/code file diff is the active view, so `available` mirrors the
|
|
788
|
-
* surface's `onFileDiffActiveChange`.
|
|
613
|
+
* Split/Unified selector: two segmented buttons, or with `control: 'toggle'`
|
|
614
|
+
* one button that shows the layout it switches to.
|
|
615
|
+
* `available` mirrors the surface's `onFileDiffActiveChange`.
|
|
789
616
|
*/
|
|
790
617
|
export function DiffStyleControl(props: {
|
|
791
618
|
diffStyle: DiffStyle;
|
|
619
|
+
control: DiffStyleControlMode;
|
|
792
620
|
available: boolean;
|
|
793
621
|
onChange: (style: DiffStyle) => void;
|
|
794
622
|
trans: TranslationBundle;
|
|
795
623
|
}): React.ReactElement {
|
|
796
|
-
const { diffStyle, available, onChange, trans } = props;
|
|
624
|
+
const { diffStyle, control, available, onChange, trans } = props;
|
|
797
625
|
if (!available) {
|
|
798
626
|
return <></>;
|
|
799
627
|
}
|
|
628
|
+
if (control === 'toggle') {
|
|
629
|
+
const next: DiffStyle = diffStyle === 'split' ? 'unified' : 'split';
|
|
630
|
+
const label =
|
|
631
|
+
next === 'split'
|
|
632
|
+
? trans.__('Switch to split view')
|
|
633
|
+
: trans.__('Switch to unified view');
|
|
634
|
+
return (
|
|
635
|
+
<div className="jp-xtralab-DiffWidget-segmented">
|
|
636
|
+
<button
|
|
637
|
+
type="button"
|
|
638
|
+
className="jp-xtralab-DiffWidget-segmentedButton jp-xtralab-DiffWidget-segmentedButton-icon"
|
|
639
|
+
title={label}
|
|
640
|
+
aria-label={label}
|
|
641
|
+
onClick={() => onChange(next)}
|
|
642
|
+
>
|
|
643
|
+
{next === 'split' ? <IconDiffSplit /> : <IconDiffUnified />}
|
|
644
|
+
</button>
|
|
645
|
+
</div>
|
|
646
|
+
);
|
|
647
|
+
}
|
|
800
648
|
return (
|
|
801
649
|
<div
|
|
802
650
|
className="jp-xtralab-DiffWidget-segmented"
|
|
@@ -813,7 +661,7 @@ export function DiffStyleControl(props: {
|
|
|
813
661
|
aria-label={trans.__('Split view')}
|
|
814
662
|
onClick={() => onChange('split')}
|
|
815
663
|
>
|
|
816
|
-
<
|
|
664
|
+
<IconDiffSplit />
|
|
817
665
|
</button>
|
|
818
666
|
<button
|
|
819
667
|
type="button"
|
|
@@ -825,7 +673,38 @@ export function DiffStyleControl(props: {
|
|
|
825
673
|
aria-label={trans.__('Unified view')}
|
|
826
674
|
onClick={() => onChange('unified')}
|
|
827
675
|
>
|
|
828
|
-
<
|
|
676
|
+
<IconDiffUnified />
|
|
677
|
+
</button>
|
|
678
|
+
</div>
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Line-wrap toggle styled as a one-button segmented control.
|
|
684
|
+
* `available` mirrors the surface's `onFileDiffActiveChange`.
|
|
685
|
+
*/
|
|
686
|
+
export function LineWrapControl(props: {
|
|
687
|
+
lineWrap: boolean;
|
|
688
|
+
available: boolean;
|
|
689
|
+
onChange: (wrap: boolean) => void;
|
|
690
|
+
trans: TranslationBundle;
|
|
691
|
+
}): React.ReactElement {
|
|
692
|
+
const { lineWrap, available, onChange, trans } = props;
|
|
693
|
+
if (!available) {
|
|
694
|
+
return <></>;
|
|
695
|
+
}
|
|
696
|
+
return (
|
|
697
|
+
<div className="jp-xtralab-DiffWidget-segmented">
|
|
698
|
+
<button
|
|
699
|
+
type="button"
|
|
700
|
+
aria-pressed={lineWrap}
|
|
701
|
+
data-active={lineWrap}
|
|
702
|
+
className="jp-xtralab-DiffWidget-segmentedButton jp-xtralab-DiffWidget-segmentedButton-icon"
|
|
703
|
+
title={trans.__('Wrap long lines')}
|
|
704
|
+
aria-label={trans.__('Wrap lines')}
|
|
705
|
+
onClick={() => onChange(!lineWrap)}
|
|
706
|
+
>
|
|
707
|
+
<IconWordWrap />
|
|
829
708
|
</button>
|
|
830
709
|
</div>
|
|
831
710
|
);
|