xtralab 0.15.0 → 0.15.1

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 (228) hide show
  1. package/README.md +10 -0
  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 +28 -19
  36. package/lib/fileBrowser/commands.js +39 -98
  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 +68 -216
  42. package/lib/fileBrowser/gitStatus.d.ts +4 -9
  43. package/lib/fileBrowser/gitStatus.js +7 -17
  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 +126 -48
  50. package/lib/fileBrowser/widget.js +89 -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 +40 -5
  57. package/lib/git/commands.js +16 -10
  58. package/lib/git/diffModel.js +4 -0
  59. package/lib/git/diffSurface.d.ts +41 -55
  60. package/lib/git/diffSurface.js +42 -117
  61. package/lib/git/diffWidget.d.ts +81 -2
  62. package/lib/git/diffWidget.js +62 -12
  63. package/lib/git/imageDiff.d.ts +5 -6
  64. package/lib/git/imageDiff.js +18 -48
  65. package/lib/git/index.d.ts +0 -3
  66. package/lib/git/index.js +3 -8
  67. package/lib/git/notebookDiff.d.ts +110 -33
  68. package/lib/git/notebookDiff.js +52 -195
  69. package/lib/git/tokens.d.ts +73 -31
  70. package/lib/git/tokens.js +2 -3
  71. package/lib/highlight/index.d.ts +4 -10
  72. package/lib/highlight/index.js +10 -41
  73. package/lib/index.d.ts +0 -9
  74. package/lib/index.js +0 -9
  75. package/lib/launcher/agents.d.ts +55 -51
  76. package/lib/launcher/agents.js +15 -57
  77. package/lib/launcher/availability.d.ts +3 -9
  78. package/lib/launcher/availability.js +3 -9
  79. package/lib/launcher/commands.d.ts +7 -32
  80. package/lib/launcher/commands.js +14 -45
  81. package/lib/launcher/dashboard.d.ts +19 -24
  82. package/lib/launcher/dashboard.js +21 -67
  83. package/lib/launcher/editorRegistry.d.ts +10 -22
  84. package/lib/launcher/editorRegistry.js +14 -18
  85. package/lib/launcher/editors.d.ts +39 -45
  86. package/lib/launcher/editors.js +8 -34
  87. package/lib/launcher/icons.d.ts +0 -12
  88. package/lib/launcher/icons.js +5 -39
  89. package/lib/launcher/index.js +12 -55
  90. package/lib/launcher/invocation.d.ts +4 -10
  91. package/lib/launcher/invocation.js +7 -22
  92. package/lib/launcher/registry.d.ts +9 -7
  93. package/lib/launcher/registry.js +9 -7
  94. package/lib/launcher/tokens.d.ts +10 -22
  95. package/lib/launcher/tokens.js +5 -8
  96. package/lib/menuBar/index.d.ts +3 -10
  97. package/lib/menuBar/index.js +11 -28
  98. package/lib/menus/index.d.ts +3 -9
  99. package/lib/menus/index.js +14 -30
  100. package/lib/omnibox/files.d.ts +2 -4
  101. package/lib/omnibox/files.js +5 -11
  102. package/lib/omnibox/index.d.ts +4 -14
  103. package/lib/omnibox/index.js +9 -27
  104. package/lib/omnibox/model.d.ts +38 -10
  105. package/lib/omnibox/model.js +15 -33
  106. package/lib/omnibox/recents.d.ts +15 -17
  107. package/lib/omnibox/recents.js +12 -17
  108. package/lib/omnibox/tokens.d.ts +5 -10
  109. package/lib/omnibox/tokens.js +2 -6
  110. package/lib/omnibox/widget.d.ts +11 -7
  111. package/lib/omnibox/widget.js +8 -16
  112. package/lib/searchReplace/index.d.ts +4 -20
  113. package/lib/searchReplace/index.js +7 -27
  114. package/lib/showOutput/index.d.ts +3 -12
  115. package/lib/showOutput/index.js +6 -22
  116. package/lib/sidebar/index.d.ts +4 -7
  117. package/lib/sidebar/index.js +5 -12
  118. package/lib/terminalNotifications/index.d.ts +4 -10
  119. package/lib/terminalNotifications/index.js +6 -20
  120. package/lib/terminals/agentTerminals.d.ts +12 -5
  121. package/lib/terminals/agentTerminals.js +25 -44
  122. package/lib/terminals/detection.d.ts +4 -8
  123. package/lib/terminals/detection.js +4 -8
  124. package/lib/terminals/index.d.ts +0 -35
  125. package/lib/terminals/index.js +12 -101
  126. package/lib/terminals/model.d.ts +64 -139
  127. package/lib/terminals/model.js +80 -220
  128. package/lib/terminals/tokens.d.ts +11 -22
  129. package/lib/terminals/tokens.js +2 -2
  130. package/lib/terminals/widget.d.ts +43 -13
  131. package/lib/terminals/widget.js +33 -18
  132. package/lib/topBar/icons.d.ts +3 -8
  133. package/lib/topBar/icons.js +3 -8
  134. package/lib/topBar/index.d.ts +4 -11
  135. package/lib/topBar/index.js +10 -38
  136. package/lib/walkthrough/index.d.ts +4 -9
  137. package/lib/walkthrough/index.js +6 -20
  138. package/lib/walkthrough/panel.d.ts +40 -9
  139. package/lib/walkthrough/panel.js +4 -10
  140. package/package.json +2 -2
  141. package/src/about/icons.ts +0 -5
  142. package/src/about/index.tsx +6 -17
  143. package/src/agentSessions.ts +9 -29
  144. package/src/askAgent/editorSelection.ts +22 -29
  145. package/src/askAgent/icons.ts +2 -3
  146. package/src/askAgent/index.ts +54 -158
  147. package/src/askAgent/popup.tsx +23 -45
  148. package/src/askAgent/prompt.ts +6 -19
  149. package/src/askAgent/queue.ts +13 -24
  150. package/src/askAgent/queuePanel.tsx +21 -46
  151. package/src/askAgent/targetPicker.tsx +5 -15
  152. package/src/askAgent/tokens.ts +32 -46
  153. package/src/commandBar/index.ts +24 -39
  154. package/src/customPanel/index.ts +15 -0
  155. package/src/editorBreadcrumbs/index.ts +10 -6
  156. package/src/editorBreadcrumbs/widget.ts +23 -17
  157. package/src/editorIndent/index.ts +17 -23
  158. package/src/fileBrowser/commands.ts +57 -98
  159. package/src/fileBrowser/contents.ts +3 -0
  160. package/src/fileBrowser/dragAndDrop.ts +17 -7
  161. package/src/fileBrowser/fileBrowser.tsx +84 -223
  162. package/src/fileBrowser/gitStatus.ts +7 -17
  163. package/src/fileBrowser/gitignore.ts +4 -25
  164. package/src/fileBrowser/icons.ts +25 -80
  165. package/src/fileBrowser/index.ts +2 -4
  166. package/src/fileBrowser/widget.tsx +149 -50
  167. package/src/fileTypeIcons/index.ts +0 -10
  168. package/src/git/api.ts +10 -36
  169. package/src/git/askRequest.ts +6 -20
  170. package/src/git/commands.ts +70 -10
  171. package/src/git/diffModel.ts +25 -0
  172. package/src/git/diffSurface.tsx +71 -159
  173. package/src/git/diffWidget.tsx +104 -14
  174. package/src/git/imageDiff.tsx +45 -51
  175. package/src/git/index.ts +3 -8
  176. package/src/git/notebookDiff.tsx +211 -238
  177. package/src/git/tokens.ts +73 -31
  178. package/src/highlight/index.ts +10 -41
  179. package/src/index.ts +0 -9
  180. package/src/launcher/agents.ts +64 -95
  181. package/src/launcher/availability.ts +3 -9
  182. package/src/launcher/commands.ts +14 -51
  183. package/src/launcher/dashboard.tsx +49 -88
  184. package/src/launcher/editorRegistry.ts +21 -29
  185. package/src/launcher/editors.ts +39 -58
  186. package/src/launcher/icons.ts +5 -39
  187. package/src/launcher/index.ts +12 -55
  188. package/src/launcher/invocation.ts +7 -22
  189. package/src/launcher/registry.ts +9 -7
  190. package/src/launcher/tokens.ts +10 -22
  191. package/src/menuBar/index.ts +11 -28
  192. package/src/menus/index.ts +14 -30
  193. package/src/omnibox/files.ts +10 -14
  194. package/src/omnibox/index.ts +9 -27
  195. package/src/omnibox/model.ts +50 -38
  196. package/src/omnibox/recents.ts +17 -22
  197. package/src/omnibox/tokens.ts +5 -10
  198. package/src/omnibox/widget.tsx +14 -19
  199. package/src/searchReplace/index.ts +7 -27
  200. package/src/showOutput/index.ts +6 -22
  201. package/src/sidebar/index.ts +20 -12
  202. package/src/terminalNotifications/index.ts +42 -25
  203. package/src/terminals/agentTerminals.ts +25 -49
  204. package/src/terminals/detection.ts +4 -8
  205. package/src/terminals/index.ts +12 -101
  206. package/src/terminals/model.ts +130 -246
  207. package/src/terminals/tokens.ts +11 -22
  208. package/src/terminals/widget.tsx +48 -31
  209. package/src/topBar/icons.ts +3 -8
  210. package/src/topBar/index.ts +21 -44
  211. package/src/walkthrough/index.ts +6 -20
  212. package/src/walkthrough/panel.ts +40 -10
  213. package/style/about.css +2 -7
  214. package/style/askAgent.css +8 -40
  215. package/style/base.css +9 -45
  216. package/style/chrome.css +108 -308
  217. package/style/commandBar.css +4 -20
  218. package/style/customPanel.css +1 -4
  219. package/style/git.css +22 -184
  220. package/style/highlight.css +1 -3
  221. package/style/launcher.css +6 -70
  222. package/style/omnibox.css +2 -14
  223. package/style/showOutput.css +0 -6
  224. package/style/sidebar.css +29 -9
  225. package/style/tabs.css +5 -13
  226. package/style/terminals.css +6 -53
  227. package/style/topBar.css +2 -22
  228. package/style/walkthrough.css +0 -8
@@ -6,16 +6,10 @@ import { Widget } from '@lumino/widgets';
6
6
  import { FileDiff } from '@pierre/diffs/react';
7
7
  import { parseDiffFromFile } from '@pierre/diffs';
8
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
- */
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,21 +304,14 @@ 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',
@@ -400,11 +321,8 @@ function diffLibraryOptions(theme, dark) {
400
321
  };
401
322
  }
402
323
  /**
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.
324
+ * Mount a Lumino {@link Widget} inside a React tree; detach + dispose
325
+ * happens on unmount or when a new widget arrives.
408
326
  */
409
327
  function LuminoWidget(props) {
410
328
  const { widget, className } = props;
@@ -418,31 +336,14 @@ function LuminoWidget(props) {
418
336
  Widget.attach(widget, host);
419
337
  }
420
338
  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.
339
+ // Lumino refuses to attach when the host is not in the document or
340
+ // the widget is attached elsewhere; log and let the tree mount.
425
341
  console.warn('xtralab: Widget.attach failed', err);
426
342
  return;
427
343
  }
428
344
  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.
345
+ // On parent unmount React removes the host first; detach (and dispose,
346
+ // which re-enters it) throws on a disconnected node, so message by hand.
446
347
  if (widget.isAttached) {
447
348
  if (widget.node.isConnected) {
448
349
  try {
@@ -475,14 +376,8 @@ function LuminoWidget(props) {
475
376
  return React.createElement("div", { ref: ref, className: className });
476
377
  }
477
378
  /**
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.
379
+ * Render nbformat outputs through rendermime like a live notebook would.
380
+ * Trusted: the source git ref already lives on the user's machine.
486
381
  */
487
382
  function OutputsPreview(props) {
488
383
  const { outputs, rendermime, className } = props;
@@ -496,11 +391,8 @@ function OutputsPreview(props) {
496
391
  return React.createElement(LuminoWidget, { widget: widget, className: className });
497
392
  }
498
393
  /**
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.
394
+ * Render markdown source through rendermime so the preview matches a live
395
+ * notebook (LaTeX, code fences, sanitized HTML).
504
396
  */
505
397
  function MarkdownPreview(props) {
506
398
  const { source, rendermime, className } = props;
@@ -517,11 +409,8 @@ function MarkdownPreview(props) {
517
409
  return React.createElement(LuminoWidget, { widget: renderer, className: className });
518
410
  }
519
411
  /**
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).
412
+ * Two-column "old | new" layout; collapses to a single full-width pane
413
+ * when only one side has content.
525
414
  */
526
415
  function SideBySidePanes(props) {
527
416
  const { label, oldNode, newNode, hasOld, hasNew } = props;
@@ -536,11 +425,8 @@ function SideBySidePanes(props) {
536
425
  both || hasNew ? (React.createElement("div", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-sidePane`, "data-side": "new" }, newNode)) : null)));
537
426
  }
538
427
  /**
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.
428
+ * Rendered outputs for a cell, old / new through rendermime; `null` when
429
+ * neither side has outputs.
544
430
  */
545
431
  function OutputsSection(props) {
546
432
  var _a, _b;
@@ -554,11 +440,6 @@ function OutputsSection(props) {
554
440
  if (!hasOld && !hasNew) {
555
441
  return null;
556
442
  }
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
443
  const showOld = placement !== 'right' && hasOld;
563
444
  const showNew = placement !== 'left' && hasNew;
564
445
  if (rendermime === null) {
@@ -567,9 +448,7 @@ function OutputsSection(props) {
567
448
  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
449
  }
569
450
  /**
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.
451
+ * Rendered markdown preview beside the source diff; skipped for non-markdown cells.
573
452
  */
574
453
  function MarkdownPreviewSection(props) {
575
454
  const { entry, rendermime, placement, trans } = props;
@@ -587,14 +466,9 @@ function MarkdownPreviewSection(props) {
587
466
  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
467
  }
589
468
  /**
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.
469
+ * Notebook-level diff view: a 2-column grid where the left column tracks
470
+ * the old notebook and the right the new one — modified/unchanged cells
471
+ * span both, added cells sit right, removed cells sit left.
598
472
  */
599
473
  export function NotebookDiffView(props) {
600
474
  const { diff, dark, pierreTheme, rendermime, trans } = props;
@@ -606,12 +480,8 @@ export function NotebookDiffView(props) {
606
480
  diff.notebookMetadataDiff !== null ? (React.createElement(NotebookMetadataBlock, { metadata: diff.notebookMetadataDiff, theme: theme, dark: dark, trans: trans })) : null));
607
481
  }
608
482
  /**
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.
483
+ * Place one cell entry into the 2-column grid, with a visible placeholder
484
+ * marking where a cell was inserted or removed on the other side.
615
485
  */
616
486
  function CellEntryRow(props) {
617
487
  const { entry, trans } = props;
@@ -672,16 +542,12 @@ function cellEntryKey(entry) {
672
542
  function CellDiffBlock(props) {
673
543
  var _a, _b;
674
544
  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
545
  const [collapsed, setCollapsed] = React.useState(entry.kind === 'unchanged');
679
546
  const subdiffs = React.useMemo(() => (collapsed ? [] : buildCellSubDiffs(entry, language)), [collapsed, entry, language]);
680
547
  const cellType = referenceCellType(entry);
681
548
  const indexLabel = cellIndexLabel(entry);
682
549
  const isCodeCell = cellType === 'code';
683
- // Output rendering is only meaningful for code cells. Markdown / raw cells
684
- // never carry outputs in nbformat.
550
+ // Only code cells carry outputs in nbformat.
685
551
  const showOutputs = isCodeCell && entry.kind !== 'unchanged';
686
552
  return (React.createElement("section", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-cell`, "data-kind": entry.kind, "data-cell-type": cellType, "data-placement": placement },
687
553
  React.createElement("header", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-cellHeader`, onClick: () => setCollapsed(c => !c), role: "button", tabIndex: 0, "aria-expanded": !collapsed, onKeyDown: event => {
@@ -708,19 +574,14 @@ function CellDiffBlock(props) {
708
574
  }
709
575
  function CellSubDiff(props) {
710
576
  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.
577
+ // Full-width cells align split mode with the outer old|new lanes;
578
+ // single-column cells read better unified.
716
579
  const diffStyle = placement === 'full' ? 'split' : 'unified';
717
580
  return (React.createElement("div", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-subdiff`, "data-section": kind },
718
581
  React.createElement("div", { className: `${NOTEBOOK_DIFF_CSS_CLASS}-subdiffLabel` }, subdiffLabel(kind, trans)),
719
582
  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.
583
+ // The worker bootstrap can't resolve through JupyterLab's federation
584
+ // pipeline (see diffSurface.tsx); run on the main thread.
724
585
  disableWorkerPool: true, options: { ...diffLibraryOptions(theme, dark), diffStyle } })));
725
586
  }
726
587
  function NotebookMetadataBlock(props) {
@@ -734,12 +595,8 @@ function NotebookMetadataBlock(props) {
734
595
  React.createElement(CellSubDiff, { kind: "metadata", metadata: metadata, theme: theme, dark: dark, placement: "full", trans: trans }))));
735
596
  }
736
597
  /**
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.
598
+ * Expanded body of an unchanged cell, rendered once full-width: markdown
599
+ * through rendermime, code verbatim with outputs below.
743
600
  */
744
601
  function UnchangedCellBody(props) {
745
602
  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;