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.
Files changed (237) hide show
  1. package/README.md +65 -30
  2. package/lib/about/icons.d.ts +0 -5
  3. package/lib/about/icons.js +0 -5
  4. package/lib/about/index.d.ts +4 -8
  5. package/lib/about/index.js +6 -17
  6. package/lib/agentSessions.d.ts +9 -29
  7. package/lib/agentSessions.js +2 -7
  8. package/lib/askAgent/editorSelection.d.ts +20 -26
  9. package/lib/askAgent/editorSelection.js +11 -26
  10. package/lib/askAgent/icons.d.ts +2 -3
  11. package/lib/askAgent/icons.js +2 -3
  12. package/lib/askAgent/index.d.ts +4 -26
  13. package/lib/askAgent/index.js +54 -158
  14. package/lib/askAgent/popup.d.ts +12 -16
  15. package/lib/askAgent/popup.js +18 -37
  16. package/lib/askAgent/prompt.d.ts +3 -5
  17. package/lib/askAgent/prompt.js +6 -19
  18. package/lib/askAgent/queue.d.ts +11 -21
  19. package/lib/askAgent/queue.js +9 -19
  20. package/lib/askAgent/queuePanel.d.ts +13 -18
  21. package/lib/askAgent/queuePanel.js +15 -37
  22. package/lib/askAgent/targetPicker.d.ts +2 -9
  23. package/lib/askAgent/targetPicker.js +6 -11
  24. package/lib/askAgent/tokens.d.ts +32 -46
  25. package/lib/askAgent/tokens.js +4 -6
  26. package/lib/commandBar/index.d.ts +4 -13
  27. package/lib/commandBar/index.js +21 -39
  28. package/lib/customPanel/index.js +15 -0
  29. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  30. package/lib/editorBreadcrumbs/index.js +10 -6
  31. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  32. package/lib/editorBreadcrumbs/widget.js +11 -17
  33. package/lib/editorIndent/index.d.ts +4 -12
  34. package/lib/editorIndent/index.js +8 -23
  35. package/lib/fileBrowser/commands.d.ts +29 -19
  36. package/lib/fileBrowser/commands.js +83 -97
  37. package/lib/fileBrowser/contents.d.ts +3 -0
  38. package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
  39. package/lib/fileBrowser/dragAndDrop.js +2 -7
  40. package/lib/fileBrowser/fileBrowser.d.ts +19 -9
  41. package/lib/fileBrowser/fileBrowser.js +70 -222
  42. package/lib/fileBrowser/gitStatus.d.ts +16 -10
  43. package/lib/fileBrowser/gitStatus.js +31 -30
  44. package/lib/fileBrowser/gitignore.d.ts +4 -17
  45. package/lib/fileBrowser/gitignore.js +4 -25
  46. package/lib/fileBrowser/icons.d.ts +7 -17
  47. package/lib/fileBrowser/icons.js +25 -80
  48. package/lib/fileBrowser/index.js +2 -4
  49. package/lib/fileBrowser/widget.d.ts +141 -48
  50. package/lib/fileBrowser/widget.js +102 -4
  51. package/lib/fileTypeIcons/index.js +0 -10
  52. package/lib/git/api.d.ts +5 -13
  53. package/lib/git/api.js +10 -36
  54. package/lib/git/askRequest.d.ts +4 -8
  55. package/lib/git/askRequest.js +6 -20
  56. package/lib/git/commands.d.ts +45 -5
  57. package/lib/git/commands.js +20 -13
  58. package/lib/git/diffModel.js +4 -0
  59. package/lib/git/diffPreferences.d.ts +90 -0
  60. package/lib/git/diffPreferences.js +107 -0
  61. package/lib/git/diffProvider.js +10 -2
  62. package/lib/git/diffSurface.d.ts +64 -63
  63. package/lib/git/diffSurface.js +72 -213
  64. package/lib/git/diffTheme.d.ts +7 -0
  65. package/lib/git/diffTheme.js +23 -0
  66. package/lib/git/diffWidget.d.ts +70 -14
  67. package/lib/git/diffWidget.js +100 -94
  68. package/lib/git/imageDiff.d.ts +14 -6
  69. package/lib/git/imageDiff.js +19 -75
  70. package/lib/git/index.d.ts +0 -3
  71. package/lib/git/index.js +44 -10
  72. package/lib/git/notebookDiff.d.ts +110 -33
  73. package/lib/git/notebookDiff.js +55 -197
  74. package/lib/git/tokens.d.ts +73 -31
  75. package/lib/git/tokens.js +2 -3
  76. package/lib/highlight/index.d.ts +4 -10
  77. package/lib/highlight/index.js +10 -41
  78. package/lib/index.d.ts +0 -9
  79. package/lib/index.js +0 -9
  80. package/lib/launcher/agents.d.ts +55 -51
  81. package/lib/launcher/agents.js +15 -57
  82. package/lib/launcher/availability.d.ts +3 -9
  83. package/lib/launcher/availability.js +3 -9
  84. package/lib/launcher/commands.d.ts +7 -32
  85. package/lib/launcher/commands.js +14 -45
  86. package/lib/launcher/dashboard.d.ts +19 -24
  87. package/lib/launcher/dashboard.js +21 -67
  88. package/lib/launcher/editorRegistry.d.ts +10 -22
  89. package/lib/launcher/editorRegistry.js +14 -18
  90. package/lib/launcher/editors.d.ts +39 -45
  91. package/lib/launcher/editors.js +8 -34
  92. package/lib/launcher/icons.d.ts +0 -12
  93. package/lib/launcher/icons.js +5 -39
  94. package/lib/launcher/index.js +12 -55
  95. package/lib/launcher/invocation.d.ts +4 -10
  96. package/lib/launcher/invocation.js +7 -22
  97. package/lib/launcher/registry.d.ts +9 -7
  98. package/lib/launcher/registry.js +9 -7
  99. package/lib/launcher/tokens.d.ts +10 -22
  100. package/lib/launcher/tokens.js +5 -8
  101. package/lib/menuBar/index.d.ts +3 -10
  102. package/lib/menuBar/index.js +11 -28
  103. package/lib/menus/index.d.ts +3 -9
  104. package/lib/menus/index.js +14 -30
  105. package/lib/omnibox/files.d.ts +2 -4
  106. package/lib/omnibox/files.js +5 -11
  107. package/lib/omnibox/index.d.ts +4 -14
  108. package/lib/omnibox/index.js +9 -27
  109. package/lib/omnibox/model.d.ts +38 -10
  110. package/lib/omnibox/model.js +15 -33
  111. package/lib/omnibox/recents.d.ts +15 -17
  112. package/lib/omnibox/recents.js +12 -17
  113. package/lib/omnibox/tokens.d.ts +5 -10
  114. package/lib/omnibox/tokens.js +2 -6
  115. package/lib/omnibox/widget.d.ts +11 -7
  116. package/lib/omnibox/widget.js +8 -16
  117. package/lib/searchReplace/index.d.ts +4 -20
  118. package/lib/searchReplace/index.js +7 -27
  119. package/lib/showOutput/index.d.ts +3 -12
  120. package/lib/showOutput/index.js +6 -22
  121. package/lib/sidebar/index.d.ts +4 -7
  122. package/lib/sidebar/index.js +5 -12
  123. package/lib/terminalNotifications/index.d.ts +4 -10
  124. package/lib/terminalNotifications/index.js +6 -20
  125. package/lib/terminals/agentTerminals.d.ts +12 -5
  126. package/lib/terminals/agentTerminals.js +25 -44
  127. package/lib/terminals/detection.d.ts +4 -8
  128. package/lib/terminals/detection.js +4 -8
  129. package/lib/terminals/index.d.ts +0 -35
  130. package/lib/terminals/index.js +12 -101
  131. package/lib/terminals/model.d.ts +64 -139
  132. package/lib/terminals/model.js +80 -220
  133. package/lib/terminals/tokens.d.ts +11 -22
  134. package/lib/terminals/tokens.js +2 -2
  135. package/lib/terminals/widget.d.ts +43 -13
  136. package/lib/terminals/widget.js +33 -18
  137. package/lib/topBar/icons.d.ts +3 -8
  138. package/lib/topBar/icons.js +3 -8
  139. package/lib/topBar/index.d.ts +4 -11
  140. package/lib/topBar/index.js +10 -38
  141. package/lib/walkthrough/index.d.ts +4 -9
  142. package/lib/walkthrough/index.js +6 -20
  143. package/lib/walkthrough/panel.d.ts +40 -9
  144. package/lib/walkthrough/panel.js +4 -10
  145. package/package.json +5 -3
  146. package/schema/git-diff-preferences.json +42 -0
  147. package/src/about/icons.ts +0 -5
  148. package/src/about/index.tsx +6 -17
  149. package/src/agentSessions.ts +9 -29
  150. package/src/askAgent/editorSelection.ts +22 -29
  151. package/src/askAgent/icons.ts +2 -3
  152. package/src/askAgent/index.ts +54 -158
  153. package/src/askAgent/popup.tsx +23 -45
  154. package/src/askAgent/prompt.ts +6 -19
  155. package/src/askAgent/queue.ts +13 -24
  156. package/src/askAgent/queuePanel.tsx +21 -46
  157. package/src/askAgent/targetPicker.tsx +5 -15
  158. package/src/askAgent/tokens.ts +32 -46
  159. package/src/commandBar/index.ts +24 -39
  160. package/src/customPanel/index.ts +15 -0
  161. package/src/editorBreadcrumbs/index.ts +10 -6
  162. package/src/editorBreadcrumbs/widget.ts +23 -17
  163. package/src/editorIndent/index.ts +17 -23
  164. package/src/fileBrowser/commands.ts +119 -96
  165. package/src/fileBrowser/contents.ts +3 -0
  166. package/src/fileBrowser/dragAndDrop.ts +17 -7
  167. package/src/fileBrowser/fileBrowser.tsx +86 -230
  168. package/src/fileBrowser/gitStatus.ts +36 -33
  169. package/src/fileBrowser/gitignore.ts +4 -25
  170. package/src/fileBrowser/icons.ts +25 -80
  171. package/src/fileBrowser/index.ts +2 -4
  172. package/src/fileBrowser/widget.tsx +171 -50
  173. package/src/fileTypeIcons/index.ts +0 -10
  174. package/src/git/api.ts +10 -36
  175. package/src/git/askRequest.ts +6 -20
  176. package/src/git/commands.ts +83 -11
  177. package/src/git/diffModel.ts +25 -0
  178. package/src/git/diffPreferences.ts +192 -0
  179. package/src/git/diffProvider.tsx +11 -2
  180. package/src/git/diffSurface.tsx +165 -286
  181. package/src/git/diffTheme.ts +24 -0
  182. package/src/git/diffWidget.tsx +194 -134
  183. package/src/git/imageDiff.tsx +56 -84
  184. package/src/git/index.ts +54 -10
  185. package/src/git/notebookDiff.tsx +214 -240
  186. package/src/git/tokens.ts +73 -31
  187. package/src/highlight/index.ts +10 -41
  188. package/src/index.ts +0 -9
  189. package/src/launcher/agents.ts +64 -95
  190. package/src/launcher/availability.ts +3 -9
  191. package/src/launcher/commands.ts +14 -51
  192. package/src/launcher/dashboard.tsx +49 -88
  193. package/src/launcher/editorRegistry.ts +21 -29
  194. package/src/launcher/editors.ts +39 -58
  195. package/src/launcher/icons.ts +5 -39
  196. package/src/launcher/index.ts +12 -55
  197. package/src/launcher/invocation.ts +7 -22
  198. package/src/launcher/registry.ts +9 -7
  199. package/src/launcher/tokens.ts +10 -22
  200. package/src/menuBar/index.ts +11 -28
  201. package/src/menus/index.ts +14 -30
  202. package/src/omnibox/files.ts +10 -14
  203. package/src/omnibox/index.ts +9 -27
  204. package/src/omnibox/model.ts +50 -38
  205. package/src/omnibox/recents.ts +17 -22
  206. package/src/omnibox/tokens.ts +5 -10
  207. package/src/omnibox/widget.tsx +14 -19
  208. package/src/searchReplace/index.ts +7 -27
  209. package/src/showOutput/index.ts +6 -22
  210. package/src/sidebar/index.ts +20 -12
  211. package/src/terminalNotifications/index.ts +42 -25
  212. package/src/terminals/agentTerminals.ts +25 -49
  213. package/src/terminals/detection.ts +4 -8
  214. package/src/terminals/index.ts +12 -101
  215. package/src/terminals/model.ts +130 -246
  216. package/src/terminals/tokens.ts +11 -22
  217. package/src/terminals/widget.tsx +48 -31
  218. package/src/topBar/icons.ts +3 -8
  219. package/src/topBar/index.ts +21 -44
  220. package/src/walkthrough/index.ts +6 -20
  221. package/src/walkthrough/panel.ts +40 -10
  222. package/style/about.css +2 -7
  223. package/style/askAgent.css +8 -40
  224. package/style/base.css +9 -45
  225. package/style/chrome.css +108 -308
  226. package/style/commandBar.css +4 -20
  227. package/style/customPanel.css +1 -4
  228. package/style/git.css +26 -186
  229. package/style/highlight.css +1 -3
  230. package/style/launcher.css +6 -70
  231. package/style/omnibox.css +2 -14
  232. package/style/showOutput.css +0 -6
  233. package/style/sidebar.css +29 -9
  234. package/style/tabs.css +5 -13
  235. package/style/terminals.css +6 -53
  236. package/style/topBar.css +2 -22
  237. package/style/walkthrough.css +0 -8
@@ -10,6 +10,7 @@ import { ISignal, Signal } from '@lumino/signaling';
10
10
  import type { IAskAgent } from '../askAgent/tokens';
11
11
  import { getTreeIcon } from '../fileBrowser/icons';
12
12
  import { fileChangeToDiffModel } from './diffModel';
13
+ import type { IDiffPreferences } from './diffPreferences';
13
14
  import {
14
15
  DIFF_WIDGET_CSS_CLASS,
15
16
  XtralabDiffWidget,
@@ -28,16 +29,25 @@ export namespace CommandIDs {
28
29
  * Argument shapes used by {@link CommandIDs.openDiff}.
29
30
  */
30
31
  export namespace CommandArguments {
32
+ /**
33
+ * The arguments for the open-diff command.
34
+ */
31
35
  export interface IOpenDiff {
36
+ /**
37
+ * The server-relative path of the repository containing the change.
38
+ */
32
39
  repoPath: string;
40
+ /**
41
+ * The file change to show.
42
+ */
33
43
  change: IFileChange;
44
+ /**
45
+ * Whether to open the diff as a pinned tab instead of the shared preview.
46
+ */
34
47
  pin?: boolean;
35
48
  }
36
49
  }
37
50
 
38
- /**
39
- * Widget id of the single, reused preview diff tab.
40
- */
41
51
  export const PREVIEW_DIFF_WIDGET_ID = 'xtralab:diff:preview';
42
52
 
43
53
  /**
@@ -70,14 +80,23 @@ export class DiffMainAreaWidget extends MainAreaWidget<XtralabDiffWidget> {
70
80
  this._trans = trans;
71
81
  }
72
82
 
83
+ /**
84
+ * The file change currently displayed.
85
+ */
73
86
  get change(): IFileChange {
74
87
  return this._change;
75
88
  }
76
89
 
90
+ /**
91
+ * Whether the widget is pinned rather than the shared preview.
92
+ */
77
93
  get pinned(): boolean {
78
94
  return this._pinned;
79
95
  }
80
96
 
97
+ /**
98
+ * Pin the widget; a no-op when already pinned.
99
+ */
81
100
  pin(): void {
82
101
  if (this._pinned) {
83
102
  return;
@@ -86,10 +105,16 @@ export class DiffMainAreaWidget extends MainAreaWidget<XtralabDiffWidget> {
86
105
  this._pinnedChanged.emit(true);
87
106
  }
88
107
 
108
+ /**
109
+ * A signal emitted when the widget becomes pinned.
110
+ */
89
111
  get pinnedChanged(): ISignal<this, boolean> {
90
112
  return this._pinnedChanged;
91
113
  }
92
114
 
115
+ /**
116
+ * Display a new file change and update the tab title, caption, and icon.
117
+ */
93
118
  setChange(repoPath: string, change: IFileChange): void {
94
119
  this._change = change;
95
120
  this.content.setModel(fileChangeToDiffModel(repoPath, change));
@@ -104,21 +129,52 @@ export class DiffMainAreaWidget extends MainAreaWidget<XtralabDiffWidget> {
104
129
  private _pinnedChanged = new Signal<this, boolean>(this);
105
130
  }
106
131
 
132
+ /**
133
+ * The options used to create a diff main area widget.
134
+ */
107
135
  interface ICreateDiffWidgetOptions {
136
+ /**
137
+ * The server-relative path of the repository containing the change.
138
+ */
108
139
  repoPath: string;
140
+ /**
141
+ * The file change to display.
142
+ */
109
143
  change: IFileChange;
144
+ /**
145
+ * Theme manager the diff view follows; may be `null`.
146
+ */
110
147
  themeManager: IThemeManager | null;
148
+ /**
149
+ * Contents manager used to write hunk-discard results.
150
+ */
111
151
  contentsManager: Contents.IManager;
152
+ /**
153
+ * Rendermime registry used by notebook diffs; may be `null`.
154
+ */
112
155
  rendermime: IRenderMimeRegistry | null;
156
+ /**
157
+ * Ask-agent popup for prompting an agent about diff lines; may be `null`.
158
+ */
113
159
  askAgent: IAskAgent | null;
160
+ /**
161
+ * The display choices shared by all diff views.
162
+ */
163
+ preferences: IDiffPreferences;
164
+ /**
165
+ * Translation bundle for user-facing strings.
166
+ */
114
167
  trans: TranslationBundle;
168
+ /**
169
+ * Whether the widget opens pinned instead of as the shared preview.
170
+ */
115
171
  pinned?: boolean;
172
+ /**
173
+ * Callback invoked when the widget becomes pinned.
174
+ */
116
175
  onPinned?: (widget: DiffMainAreaWidget) => void;
117
176
  }
118
177
 
119
- /**
120
- * Build the launcher's diff tab around the shared diff widget.
121
- */
122
178
  function createDiffWidget(
123
179
  options: ICreateDiffWidgetOptions
124
180
  ): DiffMainAreaWidget {
@@ -129,12 +185,13 @@ function createDiffWidget(
129
185
  contentsManager,
130
186
  rendermime,
131
187
  askAgent,
188
+ preferences,
132
189
  trans
133
190
  } = options;
134
191
  const pinned = options.pinned === true;
135
192
  const content = new XtralabDiffWidget(
136
193
  fileChangeToDiffModel(repoPath, change),
137
- { contentsManager, rendermime, themeManager, askAgent, trans }
194
+ { contentsManager, rendermime, themeManager, askAgent, preferences, trans }
138
195
  );
139
196
  const widget = new DiffMainAreaWidget({ content }, change, pinned, trans);
140
197
  widget.id = pinned ? pinnedDiffWidgetId(change) : PREVIEW_DIFF_WIDGET_ID;
@@ -158,7 +215,6 @@ function createDiffWidget(
158
215
  widget.toolbar.addItem('pin', pinButton);
159
216
  addDiffToolbarItems(widget.toolbar, content);
160
217
 
161
- // Keep chrome visible only while there is a relevant toolbar control.
162
218
  const syncToolbarVisibility = (): void => {
163
219
  if (widget.isDisposed) {
164
220
  return;
@@ -186,7 +242,6 @@ function createDiffWidget(
186
242
  }
187
243
  syncToolbarVisibility();
188
244
  };
189
- // Auto-close once a discard leaves nothing to show.
190
245
  const onEmptied = (): void => {
191
246
  if (!widget.isDisposed) {
192
247
  widget.close();
@@ -206,8 +261,17 @@ function createDiffWidget(
206
261
  return widget;
207
262
  }
208
263
 
264
+ /**
265
+ * The options for {@link registerGitCommands}.
266
+ */
209
267
  interface IRegisterGitCommandsOptions {
268
+ /**
269
+ * The JupyterLab application, providing the commands registry and shell.
270
+ */
210
271
  app: JupyterFrontEnd;
272
+ /**
273
+ * Theme manager the diff views follow; may be `null`.
274
+ */
211
275
  themeManager: IThemeManager | null;
212
276
  /**
213
277
  * Contents manager used to write hunk-discard results.
@@ -221,6 +285,10 @@ interface IRegisterGitCommandsOptions {
221
285
  * Ask-agent popup for prompting an agent about diff lines; may be `null`.
222
286
  */
223
287
  askAgent: IAskAgent | null;
288
+ /**
289
+ * The display choices shared by all diff views.
290
+ */
291
+ preferences: IDiffPreferences;
224
292
  /**
225
293
  * Translation bundle for user-facing strings.
226
294
  */
@@ -233,12 +301,14 @@ interface IRegisterGitCommandsOptions {
233
301
  * Look up an already-open diff widget for a file change.
234
302
  */
235
303
  findDiff(change: IFileChange, pin?: boolean): DiffMainAreaWidget | undefined;
304
+ /**
305
+ * Callback invoked when a preview diff widget becomes pinned.
306
+ */
236
307
  onPinned(widget: DiffMainAreaWidget): void;
237
308
  }
238
309
 
239
310
  /**
240
- * Register the launcher's git diff command on the application command
241
- * registry.
311
+ * Register the launcher's git diff command.
242
312
  */
243
313
  export function registerGitCommands(
244
314
  options: IRegisterGitCommandsOptions
@@ -249,6 +319,7 @@ export function registerGitCommands(
249
319
  contentsManager,
250
320
  rendermime,
251
321
  askAgent,
322
+ preferences,
252
323
  trans,
253
324
  trackDiff,
254
325
  findDiff,
@@ -280,6 +351,7 @@ export function registerGitCommands(
280
351
  contentsManager,
281
352
  rendermime,
282
353
  askAgent,
354
+ preferences,
283
355
  trans,
284
356
  pinned: pin,
285
357
  onPinned
@@ -80,14 +80,39 @@ class FileChangeDiffModel implements IXtralabDiffModel {
80
80
  };
81
81
  }
82
82
 
83
+ /**
84
+ * A signal emitted when the reference or challenger changes; this static
85
+ * model never emits it.
86
+ */
83
87
  readonly changed: ISignal<Git.Diff.IModel, Git.Diff.IModelChange> =
84
88
  new Signal<Git.Diff.IModel, Git.Diff.IModelChange>(this);
89
+ /**
90
+ * The reference (old) side of the diff.
91
+ */
85
92
  reference: Git.Diff.IContent;
93
+ /**
94
+ * The challenger (new) side of the diff.
95
+ */
86
96
  challenger: Git.Diff.IContent;
97
+ /**
98
+ * Repo-relative path of the changed file.
99
+ */
87
100
  readonly filename: string;
101
+ /**
102
+ * The rename source path; `undefined` for non-renames.
103
+ */
88
104
  readonly oldFilename: string | undefined;
105
+ /**
106
+ * Path of the git repository, relative to the server root.
107
+ */
89
108
  readonly repositoryPath: string;
109
+ /**
110
+ * Whether the file content is binary.
111
+ */
90
112
  readonly isBinary: boolean;
113
+ /**
114
+ * Whether hunks can be discarded through a working-tree save.
115
+ */
91
116
  readonly canDiscard: boolean;
92
117
  }
93
118
 
@@ -0,0 +1,192 @@
1
+ import * as React from 'react';
2
+
3
+ import type { ISettingRegistry } from '@jupyterlab/settingregistry';
4
+ import type { IStateDB } from '@jupyterlab/statedb';
5
+ import { Token } from '@lumino/coreutils';
6
+ import { ISignal, Signal } from '@lumino/signaling';
7
+
8
+ export type DiffStyle = 'split' | 'unified';
9
+
10
+ export type DiffStyleControlMode = 'buttons' | 'toggle';
11
+
12
+ export type NotebookDiffViewMode = 'notebook' | 'json';
13
+
14
+ export type ImageDiffViewMode = '2-up' | 'swipe' | 'onion';
15
+
16
+ export const MIN_SPLIT_RATIO = 0.1;
17
+ export const MAX_SPLIT_RATIO = 0.9;
18
+ export const DEFAULT_SPLIT_RATIO = 0.5;
19
+
20
+ const SPLIT_RATIO_STATE_KEY = 'xtralab:git-diff:split-ratio';
21
+
22
+ /**
23
+ * Display choices shared by every diff view. The toggles live in the
24
+ * plugin settings; the split width lives in the state database.
25
+ */
26
+ export interface IDiffPreferences {
27
+ /**
28
+ * The current values; a new object after each change.
29
+ */
30
+ readonly values: IDiffPreferences.IValues;
31
+ /**
32
+ * A signal emitted when any value changes.
33
+ */
34
+ readonly changed: ISignal<IDiffPreferences, void>;
35
+ /**
36
+ * Change some values and persist them.
37
+ */
38
+ update(values: Partial<IDiffPreferences.IValues>): void;
39
+ }
40
+
41
+ export namespace IDiffPreferences {
42
+ /**
43
+ * The diff display values.
44
+ */
45
+ export interface IValues {
46
+ /**
47
+ * Split or unified layout for textual file diffs.
48
+ */
49
+ diffStyle: DiffStyle;
50
+ /**
51
+ * Two toolbar buttons for the layout, or one button that toggles it.
52
+ */
53
+ diffStyleControl: DiffStyleControlMode;
54
+ /**
55
+ * Whether long lines wrap instead of scrolling horizontally.
56
+ */
57
+ lineWrap: boolean;
58
+ /**
59
+ * Rendered or JSON view for notebook diffs.
60
+ */
61
+ notebookViewMode: NotebookDiffViewMode;
62
+ /**
63
+ * Comparison mode for image diffs.
64
+ */
65
+ imageViewMode: ImageDiffViewMode;
66
+ /**
67
+ * Width of the left pane in split view, as a fraction of the diff width.
68
+ */
69
+ splitRatio: number;
70
+ }
71
+ }
72
+
73
+ export const IDiffPreferences = new Token<IDiffPreferences>(
74
+ 'xtralab:IDiffPreferences',
75
+ 'Display choices shared by the git diff views.'
76
+ );
77
+
78
+ type SettingKey = Exclude<keyof IDiffPreferences.IValues, 'splitRatio'>;
79
+
80
+ const SETTING_KEYS: SettingKey[] = [
81
+ 'diffStyle',
82
+ 'diffStyleControl',
83
+ 'lineWrap',
84
+ 'notebookViewMode',
85
+ 'imageViewMode'
86
+ ];
87
+
88
+ /**
89
+ * {@link IDiffPreferences} backed by the settings registry and the state
90
+ * database. Without them it keeps the values in memory only.
91
+ */
92
+ export class DiffPreferences implements IDiffPreferences {
93
+ get values(): IDiffPreferences.IValues {
94
+ return this._values;
95
+ }
96
+
97
+ get changed(): ISignal<this, void> {
98
+ return this._changed;
99
+ }
100
+
101
+ update(values: Partial<IDiffPreferences.IValues>): void {
102
+ if (!this._apply(values)) {
103
+ return;
104
+ }
105
+ for (const key of SETTING_KEYS) {
106
+ if (key in values) {
107
+ this._settings?.set(key, this._values[key]).catch(reason => {
108
+ console.error(`xtralab: failed to save the diff ${key}`, reason);
109
+ });
110
+ }
111
+ }
112
+ if ('splitRatio' in values) {
113
+ this._state
114
+ ?.save(SPLIT_RATIO_STATE_KEY, this._values.splitRatio)
115
+ .catch(reason => {
116
+ console.error('xtralab: failed to save the diff split', reason);
117
+ });
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Read the toggles from the plugin settings and follow their changes.
123
+ */
124
+ connectSettings(settings: ISettingRegistry.ISettings): void {
125
+ this._settings = settings;
126
+ const sync = (): void => {
127
+ this._apply(settings.composite as Partial<IDiffPreferences.IValues>);
128
+ };
129
+ sync();
130
+ settings.changed.connect(sync);
131
+ }
132
+
133
+ /**
134
+ * Restore the split width from the state database and save it there.
135
+ */
136
+ async connectState(state: IStateDB): Promise<void> {
137
+ this._state = state;
138
+ const ratio = await state.fetch(SPLIT_RATIO_STATE_KEY);
139
+ if (
140
+ typeof ratio === 'number' &&
141
+ ratio >= MIN_SPLIT_RATIO &&
142
+ ratio <= MAX_SPLIT_RATIO
143
+ ) {
144
+ this._apply({ splitRatio: ratio });
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Merge values without persisting them; return whether one changed.
150
+ */
151
+ private _apply(values: Partial<IDiffPreferences.IValues>): boolean {
152
+ const next = { ...this._values, ...values };
153
+ const keys = Object.keys(next) as (keyof IDiffPreferences.IValues)[];
154
+ if (keys.every(key => next[key] === this._values[key])) {
155
+ return false;
156
+ }
157
+ this._values = next;
158
+ this._changed.emit();
159
+ return true;
160
+ }
161
+
162
+ private _values: IDiffPreferences.IValues = {
163
+ diffStyle: 'split',
164
+ diffStyleControl: 'buttons',
165
+ lineWrap: false,
166
+ notebookViewMode: 'notebook',
167
+ imageViewMode: '2-up',
168
+ splitRatio: DEFAULT_SPLIT_RATIO
169
+ };
170
+ private _changed = new Signal<this, void>(this);
171
+ private _settings: ISettingRegistry.ISettings | null = null;
172
+ private _state: IStateDB | null = null;
173
+ }
174
+
175
+ /**
176
+ * Re-render on every preference change and return the current values.
177
+ */
178
+ export function useDiffPreferences(
179
+ preferences: IDiffPreferences
180
+ ): IDiffPreferences.IValues {
181
+ const subscribe = React.useCallback(
182
+ (onChange: () => void) => {
183
+ const slot = (): void => onChange();
184
+ preferences.changed.connect(slot);
185
+ return () => {
186
+ preferences.changed.disconnect(slot);
187
+ };
188
+ },
189
+ [preferences]
190
+ );
191
+ return React.useSyncExternalStore(subscribe, () => preferences.values);
192
+ }
@@ -9,6 +9,7 @@ import { Git, IGitExtension } from '@jupyterlab/git';
9
9
 
10
10
  import { IAskAgent } from '../askAgent/tokens';
11
11
 
12
+ import { DiffPreferences, IDiffPreferences } from './diffPreferences';
12
13
  import {
13
14
  IXtralabDiffContext,
14
15
  XtralabDiffWidget,
@@ -37,14 +38,21 @@ const diffProviderPlugin: JupyterFrontEndPlugin<void> = {
37
38
  "Replaces jupyterlab-git's notebook, text and image diff plugins with xtralab's renderer.",
38
39
  autoStart: true,
39
40
  requires: [IGitExtension],
40
- optional: [IRenderMimeRegistry, IThemeManager, ITranslator, IAskAgent],
41
+ optional: [
42
+ IRenderMimeRegistry,
43
+ IThemeManager,
44
+ ITranslator,
45
+ IAskAgent,
46
+ IDiffPreferences
47
+ ],
41
48
  activate: (
42
49
  app: JupyterFrontEnd,
43
50
  gitExtension: IGitExtension,
44
51
  rendermime: IRenderMimeRegistry | null,
45
52
  themeManager: IThemeManager | null,
46
53
  translator: ITranslator | null,
47
- askAgent: IAskAgent | null
54
+ askAgent: IAskAgent | null,
55
+ preferences: IDiffPreferences | null
48
56
  ): void => {
49
57
  const trans = (translator ?? nullTranslator).load('jupyterlab');
50
58
  const factory = makeXtralabDiffFactory({
@@ -52,6 +60,7 @@ const diffProviderPlugin: JupyterFrontEndPlugin<void> = {
52
60
  rendermime,
53
61
  themeManager,
54
62
  askAgent,
63
+ preferences: preferences ?? new DiffPreferences(),
55
64
  trans
56
65
  });
57
66