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
@@ -5,17 +5,11 @@ import { MessageLoop } from '@lumino/messaging';
5
5
  import { Widget } from '@lumino/widgets';
6
6
  import { FileDiff } from '@pierre/diffs/react';
7
7
  import { parseDiffFromFile } from '@pierre/diffs';
8
- import { resolveDiffTheme } from './diffTheme';
9
- /**
10
- * The CSS class added to the root of the notebook diff. Selectors that
11
- * style cell sections, headers, and the per-cell diff blocks hang off this
12
- * class.
13
- */
8
+ import { DIFF_SCROLLBAR_CSS, resolveDiffTheme } from './diffTheme';
14
9
  const NOTEBOOK_DIFF_CSS_CLASS = 'jp-xtralab-NotebookDiff';
15
10
  /**
16
- * Parse notebook JSON. Returns `null` if the text doesn't parse or doesn't
17
- * look like a notebook (missing `cells` array). The caller falls back to a
18
- * raw text diff so a malformed file is still inspectable.
11
+ * Parse notebook JSON. Returns `null` when the text is not a notebook so
12
+ * the caller can fall back to a raw text diff.
19
13
  */
20
14
  function parseNotebook(text) {
21
15
  if (text.length === 0) {
@@ -47,12 +41,6 @@ function emptyNotebook() {
47
41
  nbformat_minor: 5
48
42
  };
49
43
  }
50
- /**
51
- * nbformat allows multiline string fields (`source`, stream `text`,
52
- * `text/plain` outputs, …) to be either a single string or an array of
53
- * strings. Normalize to one string so equality checks and diffs see the
54
- * same shape regardless of how the writer chose to serialize it.
55
- */
56
44
  function joinMultiline(value) {
57
45
  if (value === undefined) {
58
46
  return '';
@@ -63,11 +51,9 @@ function cellSource(cell) {
63
51
  return joinMultiline(cell.source);
64
52
  }
65
53
  /**
66
- * Canonical text representation of a cell's outputs, used both for equality
67
- * checks and for the per-cell output diff. Stream and `text/plain` data
68
- * round-trip verbatim; richer mime types (image/png, text/html, …) collapse
69
- * to a `<mime-type>` placeholder so diffing image bytes doesn't drown the
70
- * panel in base64 noise.
54
+ * Canonical text form of a cell's outputs, used for equality checks and
55
+ * diffs. Rich mime types collapse to a `<mime-type>` placeholder so base64
56
+ * payloads don't drown the diff.
71
57
  */
72
58
  function canonicalOutputs(cell) {
73
59
  var _a;
@@ -106,10 +92,6 @@ function formatOutput(output) {
106
92
  case 'error': {
107
93
  const ename = (_d = output.ename) !== null && _d !== void 0 ? _d : 'Error';
108
94
  const evalue = (_e = output.evalue) !== null && _e !== void 0 ? _e : '';
109
- // Tracebacks contain ANSI escape codes by default. Strip them so the
110
- // diff stays readable — coloring information has no analog in a plain
111
- // text diff, and the escapes themselves would inflate every traceback
112
- // line into a fake change.
113
95
  const traceback = ((_f = output.traceback) !== null && _f !== void 0 ? _f : []).map(stripAnsi).join('\n');
114
96
  return `[error]\n${ename}: ${evalue}\n${traceback}`;
115
97
  }
@@ -117,20 +99,15 @@ function formatOutput(output) {
117
99
  return `[${output.output_type}]`;
118
100
  }
119
101
  }
120
- // Matching ANSI escape codes inherently requires a control character in
121
- // the pattern, which is what no-control-regex flags. Disable the rule on
122
- // this single literal — silently emitting un-stripped escapes would just
123
- // reintroduce the very noise stripAnsi exists to remove.
102
+ // Matching ANSI escapes requires a control character in the pattern.
124
103
  // eslint-disable-next-line no-control-regex
125
104
  const ANSI_ESCAPE_RE = /\x1B\[[0-9;]*[A-Za-z]/g;
126
105
  function stripAnsi(value) {
127
106
  return value.replace(ANSI_ESCAPE_RE, '');
128
107
  }
129
108
  /**
130
- * Stable JSON of the cell's `metadata` object. Keys are sorted recursively
131
- * so a writer that re-orders the metadata object doesn't surface as a diff.
132
- * An empty metadata object returns the empty string so freshly-added cells
133
- * that carry the default `{}` don't produce a `+{}` placeholder section.
109
+ * Stable sorted JSON of the cell's `metadata`; empty metadata returns ''
110
+ * so cells carrying the default `{}` don't produce a `+{}` diff section.
134
111
  */
135
112
  function canonicalMetadata(cell) {
136
113
  var _a;
@@ -140,11 +117,6 @@ function canonicalMetadata(cell) {
140
117
  }
141
118
  return stableStringify(md);
142
119
  }
143
- /**
144
- * Stable JSON of the notebook-level metadata bundle (kernelspec, language
145
- * info, nbformat). We diff the bundle as a single block so the user sees
146
- * all environment-level changes in one place.
147
- */
148
120
  function canonicalNotebookMetadata(notebook) {
149
121
  return stableStringify({
150
122
  metadata: notebook.metadata,
@@ -166,11 +138,8 @@ function sortKeysReplacer(key, value) {
166
138
  return value;
167
139
  }
168
140
  /**
169
- * Equality check used to classify matched cells as `unchanged` vs
170
- * `modified`. Compares the three things we actually render — type, source,
171
- * outputs, metadata — using the same canonical representations the diff
172
- * itself sees, so an "equal" verdict here implies all per-cell diff blocks
173
- * would be empty.
141
+ * Classify matched cells as `unchanged` vs `modified` using the same
142
+ * canonical forms the diff renders, so "equal" implies empty per-cell diffs.
174
143
  */
175
144
  function cellsAreEqual(a, b) {
176
145
  return (a.cell_type === b.cell_type &&
@@ -179,16 +148,9 @@ function cellsAreEqual(a, b) {
179
148
  canonicalMetadata(a) === canonicalMetadata(b));
180
149
  }
181
150
  /**
182
- * Align cells between two notebook revisions. Cells that carry a stable id
183
- * (nbformat ≥ 4.5) match on their id; remaining cells fall back to
184
- * positional matching against the next un-consumed unidentified old cell so
185
- * older notebooks still produce a sensible diff.
186
- *
187
- * Output ordering: entries follow new-file order, with cells that exist
188
- * only in the old revision appended at the end as `removed`. We don't try
189
- * to interleave removals at their original position — for typical notebook
190
- * edits this is more readable than guessing alignment between unrelated
191
- * insertions and deletions.
151
+ * Align cells between two revisions: stable ids (nbformat ≥ 4.5) match by
152
+ * id, the rest positionally. Entries follow new-file order with old-only
153
+ * cells appended as `removed`.
192
154
  */
193
155
  function alignNotebookCells(oldCells, newCells) {
194
156
  const oldById = new Map();
@@ -199,8 +161,6 @@ function alignNotebookCells(oldCells, newCells) {
199
161
  });
200
162
  const consumed = new Set();
201
163
  const entries = [];
202
- // Cursor used for positional matching of unidentified cells. Only advances
203
- // forward so we don't double-match a single old cell.
204
164
  let positionalCursor = 0;
205
165
  for (let newIndex = 0; newIndex < newCells.length; newIndex++) {
206
166
  const newCell = newCells[newIndex];
@@ -251,13 +211,10 @@ function alignNotebookCells(oldCells, newCells) {
251
211
  return entries;
252
212
  }
253
213
  /**
254
- * Build a complete notebook diff from the textual contents of two
255
- * revisions. Returns `null` if either side fails to parse as a notebook;
256
- * the caller is expected to fall back to a raw text diff in that case.
257
- *
258
- * An empty string on either side is treated as an empty notebook (no
259
- * cells, default metadata) so a freshly-added or deleted notebook still
260
- * produces a sensible cell-by-cell diff.
214
+ * Build a notebook diff from the text of two revisions. Returns `null` when
215
+ * either side fails to parse (caller falls back to a raw text diff); an
216
+ * empty string counts as an empty notebook so added/deleted notebooks still
217
+ * diff cell by cell.
261
218
  */
262
219
  export function buildNotebookDiff(options) {
263
220
  var _a;
@@ -291,10 +248,8 @@ function detectLanguage(notebook) {
291
248
  return undefined;
292
249
  }
293
250
  /**
294
- * Pick a filename whose extension drives `@pierre/diffs`'s syntax
295
- * highlighter. The library never reads the file from disk — `name` only
296
- * influences token-level rendering, so any plausible extension that maps
297
- * to the right language is fine.
251
+ * Pick a filename whose extension drives the `@pierre/diffs` highlighter;
252
+ * the library never reads the file, only the extension matters.
298
253
  */
299
254
  function cellFilename(cell, language) {
300
255
  if (cell.cell_type === 'markdown') {
@@ -325,37 +280,9 @@ function cellFilename(cell, language) {
325
280
  }
326
281
  }
327
282
  /**
328
- * Build the FileContents pair the diff library wants. `kind` controls the
329
- * filename so each per-cell sub-diff (source, metadata) gets the right
330
- * extension and therefore the right highlighter.
331
- */
332
- function pierreFiles(kind, oldText, newText, oldCell, newCell, language) {
333
- let name;
334
- if (kind === 'metadata') {
335
- name = 'cell-metadata.json';
336
- }
337
- else {
338
- // Use the new cell's type/language when available; fall back to the old
339
- // side so a deleted cell still gets the right highlighter.
340
- const reference = newCell !== null && newCell !== void 0 ? newCell : oldCell;
341
- name = reference !== null ? cellFilename(reference, language) : 'cell.txt';
342
- }
343
- return {
344
- oldFile: { name, contents: oldText },
345
- newFile: { name, contents: newText }
346
- };
347
- }
348
- /**
349
- * Compute the source / metadata sub-diffs for a single cell entry.
350
- * Returns the empty list for `unchanged` entries so the caller can collapse
351
- * them; for `added` / `removed` entries the populated side is paired with
352
- * an empty counterpart so the library treats every line as an addition or
353
- * deletion respectively.
354
- *
355
- * Outputs are deliberately *not* included here — they're rendered through
356
- * rendermime side-by-side in {@link OutputsSection} so images render as
357
- * images, HTML as HTML, etc. The canonical text form survives for equality
358
- * (it drives the `unchanged` classification) but not for display.
283
+ * Source / metadata sub-diffs for one cell entry; empty for `unchanged`.
284
+ * Outputs are excluded on purpose — they render through rendermime in
285
+ * {@link OutputsSection}; their canonical text form only drives equality.
359
286
  */
360
287
  function buildCellSubDiffs(entry, language) {
361
288
  if (entry.kind === 'unchanged') {
@@ -363,6 +290,7 @@ function buildCellSubDiffs(entry, language) {
363
290
  }
364
291
  const oldCell = entry.kind === 'added' ? null : entry.oldCell;
365
292
  const newCell = entry.kind === 'removed' ? null : entry.newCell;
293
+ const sourceName = cellFilename(entry.kind === 'removed' ? entry.oldCell : entry.newCell, language);
366
294
  const oldSource = oldCell !== null ? cellSource(oldCell) : '';
367
295
  const newSource = newCell !== null ? cellSource(newCell) : '';
368
296
  const oldMd = oldCell !== null ? canonicalMetadata(oldCell) : '';
@@ -376,35 +304,26 @@ function buildCellSubDiffs(entry, language) {
376
304
  if (section.oldText === section.newText) {
377
305
  continue;
378
306
  }
379
- const { oldFile, newFile } = pierreFiles(section.kind, section.oldText, section.newText, oldCell, newCell, language);
307
+ const name = section.kind === 'metadata' ? 'cell-metadata.json' : sourceName;
380
308
  subdiffs.push({
381
309
  kind: section.kind,
382
- metadata: parseDiffFromFile(oldFile, newFile)
310
+ metadata: parseDiffFromFile(oldCell === null ? null : { name, contents: section.oldText }, newCell === null ? null : { name, contents: section.newText })
383
311
  });
384
312
  }
385
313
  return subdiffs;
386
314
  }
387
- /**
388
- * Common diff library options shared across every per-cell sub-diff and
389
- * the notebook-level metadata diff. Split mode matches the file-diff path
390
- * and gives the user the same left=old / right=new mental model inside
391
- * each cell — cells stack vertically but each cell internally reads
392
- * side-by-side, which matches how nbdime renders.
393
- */
394
315
  function diffLibraryOptions(theme, dark) {
395
316
  return {
396
317
  diffStyle: 'split',
397
318
  disableFileHeader: true,
398
319
  theme,
399
- themeType: dark ? 'dark' : 'light'
320
+ themeType: dark ? 'dark' : 'light',
321
+ unsafeCSS: DIFF_SCROLLBAR_CSS
400
322
  };
401
323
  }
402
324
  /**
403
- * Mount a Lumino {@link Widget} inside a React tree. The widget is the
404
- * source of truth for its DOM — React just owns the host element and
405
- * Lumino owns the content the widget paints into it. Detach + dispose
406
- * happens on unmount or when a new widget arrives, so the parent doesn't
407
- * need to manage the widget's lifecycle separately.
325
+ * Mount a Lumino {@link Widget} inside a React tree; detach + dispose
326
+ * happens on unmount or when a new widget arrives.
408
327
  */
409
328
  function LuminoWidget(props) {
410
329
  const { widget, className } = props;
@@ -418,31 +337,14 @@ function LuminoWidget(props) {
418
337
  Widget.attach(widget, host);
419
338
  }
420
339
  catch (err) {
421
- // Lumino refuses to attach a widget whose host isn't connected to
422
- // the document, or that's already attached elsewhere. Either way
423
- // there's nothing useful to do here — log and bail so the parent
424
- // tree still mounts.
340
+ // Lumino refuses to attach when the host is not in the document or
341
+ // the widget is attached elsewhere; log and let the tree mount.
425
342
  console.warn('xtralab: Widget.attach failed', err);
426
343
  return;
427
344
  }
428
345
  return () => {
429
- // Lumino's `Widget.detach` throws "Widget is not attached" when
430
- // either the `IsAttached` flag is false *or* the node is no longer
431
- // connected to the document. During a parent React unmount the
432
- // host element gets removed from the DOM before this cleanup
433
- // runs, so `node.isConnected` is already `false` while the
434
- // `IsAttached` flag is still set from our earlier `Widget.attach`
435
- // call. Calling `Widget.detach` in that state would throw, and —
436
- // worse — `widget.dispose()` re-enters the same detach branch
437
- // internally (its `else if (this.isAttached)` guard does not
438
- // check the DOM connection), so leaving the flag stale produces
439
- // the noisy "Widget is not attached" warning out of `dispose`.
440
- //
441
- // Resolve the race by driving Lumino's detach lifecycle by hand
442
- // when the host has already been torn down: send `BeforeDetach`
443
- // and `AfterDetach` so the layout cleanup hooks run and the flag
444
- // clears, then dispose. When the host is still connected the
445
- // ordinary `Widget.detach` path handles both steps for us.
346
+ // On parent unmount React removes the host first; detach (and dispose,
347
+ // which re-enters it) throws on a disconnected node, so message by hand.
446
348
  if (widget.isAttached) {
447
349
  if (widget.node.isConnected) {
448
350
  try {
@@ -475,14 +377,8 @@ function LuminoWidget(props) {
475
377
  return React.createElement("div", { ref: ref, className: className });
476
378
  }
477
379
  /**
478
- * Render a sequence of nbformat outputs through JupyterLab's rendermime,
479
- * exactly the way a live notebook would. Wrapped here so the side-by-side
480
- * cell layout can drop a fully-rendered output column on either side
481
- * without re-implementing rich mime rendering.
482
- *
483
- * The output area is created `trusted` because the source git ref is on
484
- * the user's machine (working tree / index / HEAD). A diff viewer that
485
- * sandboxed its own user's outputs would just be inconvenient.
380
+ * Render nbformat outputs through rendermime like a live notebook would.
381
+ * Trusted: the source git ref already lives on the user's machine.
486
382
  */
487
383
  function OutputsPreview(props) {
488
384
  const { outputs, rendermime, className } = props;
@@ -496,11 +392,8 @@ function OutputsPreview(props) {
496
392
  return React.createElement(LuminoWidget, { widget: widget, className: className });
497
393
  }
498
394
  /**
499
- * Render markdown source through rendermime so the cell preview matches
500
- * what JupyterLab would render in a live notebook (LaTeX, syntax-highlighted
501
- * code fences, sanitized HTML, …). Used for both the rendered preview that
502
- * sits next to a markdown cell's source diff, and for unchanged markdown
503
- * cells when the user expands them.
395
+ * Render markdown source through rendermime so the preview matches a live
396
+ * notebook (LaTeX, code fences, sanitized HTML).
504
397
  */
505
398
  function MarkdownPreview(props) {
506
399
  const { source, rendermime, className } = props;
@@ -517,11 +410,8 @@ function MarkdownPreview(props) {
517
410
  return React.createElement(LuminoWidget, { widget: renderer, className: className });
518
411
  }
519
412
  /**
520
- * Two-column "old | new" layout used by both {@link OutputsSection} and
521
- * {@link MarkdownPreviewSection}. The visible side(s) depend on the cell
522
- * kind: modified shows both, added shows only new, removed shows only old,
523
- * unchanged collapses to a single full-width pane (since both sides
524
- * render the same content).
413
+ * Two-column "old | new" layout; collapses to a single full-width pane
414
+ * when only one side has content.
525
415
  */
526
416
  function SideBySidePanes(props) {
527
417
  const { label, oldNode, newNode, hasOld, hasNew } = props;
@@ -536,11 +426,8 @@ function SideBySidePanes(props) {
536
426
  both || hasNew ? (React.createElement("div", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-sidePane`, "data-side": "new" }, newNode)) : null)));
537
427
  }
538
428
  /**
539
- * Rendered outputs section for a cell. Renders old / new outputs through
540
- * rendermime in two columns; for added or removed cells only the populated
541
- * side is shown. Returns `null` (so the section is hidden entirely) when
542
- * neither side has any outputs, or when the same set of outputs appears
543
- * on both sides and the host doesn't want the duplicate render.
429
+ * Rendered outputs for a cell, old / new through rendermime; `null` when
430
+ * neither side has outputs.
544
431
  */
545
432
  function OutputsSection(props) {
546
433
  var _a, _b;
@@ -554,11 +441,6 @@ function OutputsSection(props) {
554
441
  if (!hasOld && !hasNew) {
555
442
  return null;
556
443
  }
557
- // For added / removed cells the parent already lives in a single outer
558
- // column — rendering the populated side full width within that column
559
- // is the right move. SideBySidePanes itself collapses to a single grid
560
- // column when only one side has content, so we can use it for both
561
- // cases by feeding it only the side(s) we want.
562
444
  const showOld = placement !== 'right' && hasOld;
563
445
  const showNew = placement !== 'left' && hasNew;
564
446
  if (rendermime === null) {
@@ -567,9 +449,7 @@ function OutputsSection(props) {
567
449
  return (React.createElement(SideBySidePanes, { label: trans.__('Outputs'), hasOld: showOld, hasNew: showNew, oldNode: React.createElement(OutputsPreview, { outputs: oldOutputs, rendermime: rendermime }), newNode: React.createElement(OutputsPreview, { outputs: newOutputs, rendermime: rendermime }) }));
568
450
  }
569
451
  /**
570
- * Rendered markdown preview section. Sits next to (and below) the markdown
571
- * source diff so the user sees both the source-level changes and how the
572
- * rendered cell looks. Skipped for non-markdown cells.
452
+ * Rendered markdown preview beside the source diff; skipped for non-markdown cells.
573
453
  */
574
454
  function MarkdownPreviewSection(props) {
575
455
  const { entry, rendermime, placement, trans } = props;
@@ -587,14 +467,9 @@ function MarkdownPreviewSection(props) {
587
467
  return (React.createElement(SideBySidePanes, { label: trans.__('Rendered'), hasOld: showOld, hasNew: showNew, oldNode: React.createElement(MarkdownPreview, { source: oldSource, rendermime: rendermime }), newNode: React.createElement(MarkdownPreview, { source: newSource, rendermime: rendermime }) }));
588
468
  }
589
469
  /**
590
- * The notebook-level diff view. Lays out cells in a 2-column grid where
591
- * the left column tracks the *old* notebook and the right column tracks
592
- * the *new* one — modified and unchanged cells span both columns (their
593
- * internal split-mode FileDiff aligns with the outer columns), added
594
- * cells live in the right column with an empty placeholder on the left,
595
- * and removed cells live in the left column with an empty placeholder on
596
- * the right. The diff library's red/green tint within each cell makes
597
- * the orientation clear without a separate column header.
470
+ * Notebook-level diff view: a 2-column grid where the left column tracks
471
+ * the old notebook and the right the new one — modified/unchanged cells
472
+ * span both, added cells sit right, removed cells sit left.
598
473
  */
599
474
  export function NotebookDiffView(props) {
600
475
  const { diff, dark, pierreTheme, rendermime, trans } = props;
@@ -606,12 +481,8 @@ export function NotebookDiffView(props) {
606
481
  diff.notebookMetadataDiff !== null ? (React.createElement(NotebookMetadataBlock, { metadata: diff.notebookMetadataDiff, theme: theme, dark: dark, trans: trans })) : null));
607
482
  }
608
483
  /**
609
- * Place a single cell entry into the outer 2-column grid. Modified /
610
- * unchanged entries span the full row; added entries take the right
611
- * column with an empty placeholder on the left; removed entries take the
612
- * left column with an empty placeholder on the right. The placeholders
613
- * are visible (subtle dashed outline) so the user can see *where* a
614
- * cell was inserted or removed relative to the other side.
484
+ * Place one cell entry into the 2-column grid, with a visible placeholder
485
+ * marking where a cell was inserted or removed on the other side.
615
486
  */
616
487
  function CellEntryRow(props) {
617
488
  const { entry, trans } = props;
@@ -672,16 +543,12 @@ function cellEntryKey(entry) {
672
543
  function CellDiffBlock(props) {
673
544
  var _a, _b;
674
545
  const { entry, language, theme, dark, rendermime, placement, trans } = props;
675
- // Unchanged cells start collapsed — the user opted out of seeing those
676
- // sections by virtue of them being unchanged. A toggle lets them peek if
677
- // they want.
678
546
  const [collapsed, setCollapsed] = React.useState(entry.kind === 'unchanged');
679
547
  const subdiffs = React.useMemo(() => (collapsed ? [] : buildCellSubDiffs(entry, language)), [collapsed, entry, language]);
680
548
  const cellType = referenceCellType(entry);
681
549
  const indexLabel = cellIndexLabel(entry);
682
550
  const isCodeCell = cellType === 'code';
683
- // Output rendering is only meaningful for code cells. Markdown / raw cells
684
- // never carry outputs in nbformat.
551
+ // Only code cells carry outputs in nbformat.
685
552
  const showOutputs = isCodeCell && entry.kind !== 'unchanged';
686
553
  return (React.createElement("section", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-cell`, "data-kind": entry.kind, "data-cell-type": cellType, "data-placement": placement },
687
554
  React.createElement("header", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-cellHeader`, onClick: () => setCollapsed(c => !c), role: "button", tabIndex: 0, "aria-expanded": !collapsed, onKeyDown: event => {
@@ -708,19 +575,14 @@ function CellDiffBlock(props) {
708
575
  }
709
576
  function CellSubDiff(props) {
710
577
  const { kind, metadata, theme, dark, placement, trans } = props;
711
- // For modified / unchanged cells the diff spans both outer columns, and
712
- // split mode lets it visually align with the outer old | new lanes. For
713
- // added / removed cells the diff sits inside a single outer column, so
714
- // unified mode keeps the content readable instead of leaving half the
715
- // column empty.
578
+ // Full-width cells align split mode with the outer old|new lanes;
579
+ // single-column cells read better unified.
716
580
  const diffStyle = placement === 'full' ? 'split' : 'unified';
717
581
  return (React.createElement("div", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-subdiff`, "data-section": kind },
718
582
  React.createElement("div", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-subdiffLabel` }, subdiffLabel(kind, trans)),
719
583
  React.createElement(FileDiff, { fileDiff: metadata,
720
- // See diffWidget.tsx — the worker bootstrap can't resolve through
721
- // JupyterLab's federation pipeline, so every diff in this extension
722
- // runs on the main thread. Cell diffs are small enough that this is
723
- // not a performance concern.
584
+ // The worker bootstrap can't resolve through JupyterLab's federation
585
+ // pipeline (see diffSurface.tsx); run on the main thread.
724
586
  disableWorkerPool: true, options: { ...diffLibraryOptions(theme, dark), diffStyle } })));
725
587
  }
726
588
  function NotebookMetadataBlock(props) {
@@ -734,12 +596,8 @@ function NotebookMetadataBlock(props) {
734
596
  React.createElement(CellSubDiff, { kind: "metadata", metadata: metadata, theme: theme, dark: dark, placement: "full", trans: trans }))));
735
597
  }
736
598
  /**
737
- * Body for an unchanged cell when the user has expanded it. The cell's
738
- * content matches on both sides, so we render once full-width: markdown
739
- * cells go through rendermime (if available) so they look like the live
740
- * notebook would render them, code cells fall through to a verbatim
741
- * pre-formatted block, and any code outputs render through rendermime
742
- * below the source.
599
+ * Expanded body of an unchanged cell, rendered once full-width: markdown
600
+ * through rendermime, code verbatim with outputs below.
743
601
  */
744
602
  function UnchangedCellBody(props) {
745
603
  var _a;
@@ -1,39 +1,74 @@
1
1
  /**
2
- * Types shared by the git plugin. Mirror the response shapes returned by the
3
- * `jupyterlab_git` server extension's REST API so the frontend can stay
4
- * tightly coupled to a single version of those endpoints.
2
+ * Types shared by the git plugin, mirroring the response shapes of the
3
+ * `jupyterlab_git` server extension's REST API.
5
4
  */
6
5
  /**
7
- * A single entry in the `files` array returned by `POST /git/<path>/status`.
8
- * `x` and `y` are the porcelain-format index/worktree status codes (see
9
- * `git status --porcelain`); `to` is the file's current path relative to the
10
- * git repository root, `from` is the source path for renames (and equal to
11
- * `to` for any other status).
6
+ * One entry of the `files` array from `POST /git/<path>/status`. `x`/`y` are
7
+ * the porcelain index/worktree codes; `to` is the current repo-relative path
8
+ * and `from` the rename source (equal to `to` for any other status).
12
9
  */
13
10
  export interface IGitStatusFile {
11
+ /**
12
+ * The porcelain status code for the index side.
13
+ */
14
14
  x: string;
15
+ /**
16
+ * The porcelain status code for the worktree side.
17
+ */
15
18
  y: string;
19
+ /**
20
+ * The current repo-relative path of the file.
21
+ */
16
22
  to: string;
23
+ /**
24
+ * The rename source path; equal to `to` for any other status.
25
+ */
17
26
  from: string;
27
+ /**
28
+ * Whether the file content is binary; `null` when undetermined.
29
+ */
18
30
  is_binary: boolean | null;
19
31
  }
20
32
  /**
21
33
  * Response shape of `POST /git/<path>/status`.
22
34
  */
23
35
  export interface IGitStatusResult {
36
+ /**
37
+ * The return code of the git command.
38
+ */
24
39
  code: number;
40
+ /**
41
+ * The current branch name; `null` when it is not available.
42
+ */
25
43
  branch: string | null;
44
+ /**
45
+ * The upstream remote branch; `null` when none is set.
46
+ */
26
47
  remote: string | null;
48
+ /**
49
+ * The number of commits ahead of the upstream branch.
50
+ */
27
51
  ahead: number;
52
+ /**
53
+ * The number of commits behind the upstream branch.
54
+ */
28
55
  behind: number;
56
+ /**
57
+ * The changed files reported by `git status`.
58
+ */
29
59
  files: IGitStatusFile[];
60
+ /**
61
+ * The in-progress repository state (merge, rebase, ...), when reported.
62
+ */
30
63
  state?: number;
64
+ /**
65
+ * An error message, present when the command fails.
66
+ */
31
67
  message?: string;
32
68
  }
33
69
  /**
34
- * Reference accepted by `POST /git/<path>/content` to identify which version
35
- * of a file to fetch. `WORKING` is the on-disk copy, `INDEX` is the staged
36
- * copy, and a `git` value is any commit-ish (`HEAD`, a SHA, …).
70
+ * Reference accepted by `POST /git/<path>/content`: `WORKING` is the on-disk
71
+ * copy, `INDEX` the staged copy, `git` any commit-ish.
37
72
  */
38
73
  export type GitReference = {
39
74
  special: 'WORKING' | 'INDEX' | 'BASE';
@@ -41,41 +76,48 @@ export type GitReference = {
41
76
  git: string;
42
77
  };
43
78
  /**
44
- * Response shape of `POST /git/<path>/content`. The server only returns text
45
- * content here; binary files are reported as binary in the status response,
46
- * and we surface them in the UI without attempting to render their diff.
79
+ * Response shape of `POST /git/<path>/content`.
47
80
  */
48
81
  export interface IGitContentResult {
82
+ /**
83
+ * The return code of the git command.
84
+ */
49
85
  code: number;
86
+ /**
87
+ * The file content at the requested reference.
88
+ */
50
89
  content: string;
90
+ /**
91
+ * An error message, present when the command fails.
92
+ */
51
93
  message?: string;
52
94
  }
53
- /**
54
- * Where a file's change lives relative to the index.
55
- *
56
- * - `staged` → present in the index, may differ from HEAD
57
- * - `unstaged` → present in the worktree, differs from the index
58
- *
59
- * A single file can appear in both groups when the worktree has further
60
- * changes on top of an already-staged version. The panel models that as two
61
- * separate entries, one per group.
62
- */
63
95
  type FileChangeGroup = 'staged' | 'unstaged';
64
- /**
65
- * The user-facing status of a file change. Drives the single-letter badge
66
- * (M/A/D/R/U/?) shown next to each entry in the panel.
67
- */
68
96
  export type FileChangeStatus = 'modified' | 'added' | 'deleted' | 'renamed' | 'untracked' | 'unmerged' | 'typechange' | 'unknown';
69
97
  /**
70
- * One row in the changes panel. `path` is the file's path relative to the
71
- * git repository root. `from` is non-`undefined` only for renames, in which
72
- * case it carries the original path.
98
+ * One row in the changes panel. `path` is repo-relative; `from` is set only
99
+ * for renames and carries the original path.
73
100
  */
74
101
  export interface IFileChange {
102
+ /**
103
+ * The repo-relative path of the file.
104
+ */
75
105
  path: string;
106
+ /**
107
+ * The rename source path, set only for renames.
108
+ */
76
109
  from?: string;
110
+ /**
111
+ * Whether the change is staged or unstaged.
112
+ */
77
113
  group: FileChangeGroup;
114
+ /**
115
+ * The change status derived from the porcelain codes.
116
+ */
78
117
  status: FileChangeStatus;
118
+ /**
119
+ * Whether the file content is binary; `null` when undetermined.
120
+ */
79
121
  isBinary: boolean | null;
80
122
  }
81
123
  export {};
package/lib/git/tokens.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Types shared by the git plugin. Mirror the response shapes returned by the
3
- * `jupyterlab_git` server extension's REST API so the frontend can stay
4
- * tightly coupled to a single version of those endpoints.
2
+ * Types shared by the git plugin, mirroring the response shapes of the
3
+ * `jupyterlab_git` server extension's REST API.
5
4
  */
6
5
  export {};
@@ -1,15 +1,9 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  /**
3
- * Contribute `xtralab:highlight-lines` and `xtralab:clear-highlights`.
4
- *
5
- * The built-in command surface can open a file (`docmanager:open`) and move
6
- * the cursor to a line (`fileeditor:go-to-line`), but it cannot persistently
7
- * highlight a span of lines — `documentsearch:start` only marks text matches
8
- * and hijacks the find box. This plugin fills that gap with a CodeMirror line
9
- * decoration, so an agent can point at "lines 31–43 of src/index.ts" while it
10
- * narrates a walkthrough. `xtralab:highlight-lines` opens the file when needed,
11
- * scrolls the range into view, and replaces any previous highlight in that
12
- * editor; `xtralab:clear-highlights` removes every highlight.
3
+ * Contributes `xtralab:highlight-lines` and `xtralab:clear-highlights`,
4
+ * filling the persistent span-highlight gap in the core command surface
5
+ * (`documentsearch:start` only marks text matches and hijacks the find box)
6
+ * so an agent can point at lines while narrating a walkthrough.
13
7
  */
14
8
  declare const plugin: JupyterFrontEndPlugin<void>;
15
9
  export default plugin;