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
@@ -12,25 +12,13 @@ import { IOmnibox, OMNIBOX_OPEN_COMMAND } from '../omnibox/tokens';
12
12
 
13
13
  const PLUGIN_ID = 'xtralab:command-bar';
14
14
 
15
- /**
16
- * Factory name of JupyterLab's settings-driven top bar toolbar (`#jp-top-bar`,
17
- * built by `@jupyterlab/application-extension:top-bar`).
18
- */
19
15
  const TOPBAR_FACTORY = 'TopBar';
20
16
 
21
- /**
22
- * Name of the pill item within that toolbar. Its placement is declared under
23
- * `jupyter.lab.toolbars` in this plugin's settings schema
24
- * (schema/command-bar.json): the core toolbar ships a spacer at rank 50, so
25
- * the pill's higher rank lands it at the trailing end of the toolbar, next to
26
- * the right-sidebar toggle button.
27
- */
28
17
  const ITEM_NAME = 'omnibox';
29
18
 
30
19
  /**
31
- * A search-bar-styled launcher pill. It holds no text input of its own:
32
- * clicking it (or pressing Enter/Space while it is focused) runs `onActivate`,
33
- * which opens the omnibox.
20
+ * A search-bar-styled launcher pill with no input of its own: activating it
21
+ * runs `onActivate`, which opens the omnibox.
34
22
  */
35
23
  class CommandBar extends Widget {
36
24
  constructor(options: CommandBar.IOptions) {
@@ -45,19 +33,26 @@ class CommandBar extends Widget {
45
33
  this._onActivate = options.onActivate;
46
34
  }
47
35
 
36
+ /**
37
+ * Handle the DOM events for the command bar.
38
+ */
48
39
  handleEvent(event: Event): void {
49
40
  if (event.type === 'click') {
50
41
  this._onActivate();
51
42
  }
52
43
  }
53
44
 
45
+ /**
46
+ * A message handler invoked on an `'after-attach'` message.
47
+ */
54
48
  protected onAfterAttach(): void {
55
- // The click bubbles from the inner pill button up to the root node
56
- // listened on here; keyboard Enter/Space on the focused button raises the
57
- // same synthetic click.
49
+ // Keyboard Enter/Space on the focused button raises the same synthetic click.
58
50
  this.node.addEventListener('click', this);
59
51
  }
60
52
 
53
+ /**
54
+ * A message handler invoked on a `'before-detach'` message.
55
+ */
61
56
  protected onBeforeDetach(): void {
62
57
  this.node.removeEventListener('click', this);
63
58
  }
@@ -66,6 +61,9 @@ class CommandBar extends Widget {
66
61
  }
67
62
 
68
63
  namespace CommandBar {
64
+ /**
65
+ * The options used to create a `CommandBar`.
66
+ */
69
67
  export interface IOptions {
70
68
  /**
71
69
  * Placeholder-style text shown inside the pill.
@@ -85,15 +83,16 @@ namespace CommandBar {
85
83
  onActivate: () => void;
86
84
  }
87
85
 
86
+ /**
87
+ * Create the DOM node for a command bar.
88
+ */
88
89
  export function createNode(
89
90
  label: string,
90
91
  caption: string,
91
92
  shortcut?: string
92
93
  ): HTMLElement {
93
- // The root is a plain wrapper: as a toolbar item it receives the core
94
- // `.jp-Toolbar > .jp-Toolbar-item` sizing (full height, centered flex),
95
- // which would otherwise stretch the pill itself; the button inside keeps
96
- // its own compact pill height.
94
+ // Plain wrapper: the core `.jp-Toolbar-item` sizing would otherwise
95
+ // stretch the pill itself.
97
96
  const wrapper = document.createElement('div');
98
97
 
99
98
  const button = document.createElement('button');
@@ -118,7 +117,6 @@ namespace CommandBar {
118
117
  const hint = document.createElement('span');
119
118
  hint.className = 'jp-xtralab-CommandBar-shortcut';
120
119
  hint.textContent = shortcut;
121
- // Decorative: the button's aria-label already names the action.
122
120
  hint.setAttribute('aria-hidden', 'true');
123
121
  button.appendChild(hint);
124
122
  }
@@ -129,19 +127,10 @@ namespace CommandBar {
129
127
  }
130
128
 
131
129
  /**
132
- * Contribute a search-bar-styled command bar to JupyterLab's top bar toolbar.
133
- * Clicking it opens the omnibox — a launcher overlay that fuzzy-searches files
134
- * and commands and routes a typed prompt to an agent.
135
- *
136
- * The pill is a regular settings-driven toolbar item: this plugin registers a
137
- * widget factory for it on the `IToolbarWidgetRegistry` and declares its rank
138
- * in schema/command-bar.json, so users can move or disable it from the Top
139
- * Bar settings like any other toolbar item.
140
- *
141
- * The factory is only registered when the omnibox is available — the
142
- * `IOmnibox` token is provided by `xtralab:omnibox` — so the pill never
143
- * appears with nothing to open (without a factory, the toolbar item resolves
144
- * to an empty command button that renders nothing).
130
+ * Contribute a search-bar-styled pill to the top bar toolbar that opens the
131
+ * omnibox. It is a settings-driven toolbar item (factory here, rank in
132
+ * schema/command-bar.json) registered only when `IOmnibox` is provided —
133
+ * without a factory the item resolves to an empty button that renders nothing.
145
134
  */
146
135
  const plugin: JupyterFrontEndPlugin<void> = {
147
136
  id: PLUGIN_ID,
@@ -163,10 +152,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
163
152
  const trans = (translator ?? nullTranslator).load('jupyterlab');
164
153
 
165
154
  toolbarRegistry.addFactory(TOPBAR_FACTORY, ITEM_NAME, () => {
166
- // Show the omnibox's keyboard shortcut as a hint in the pill, derived
167
- // from the live binding (registered by xtralab:omnibox, which activates
168
- // first as the IOmnibox provider) so it stays correct and uses the
169
- // platform's modifier symbols. Empty when no binding is registered.
170
155
  const binding = app.commands.keyBindings.find(
171
156
  keyBinding => keyBinding.command === OMNIBOX_OPEN_COMMAND
172
157
  );
@@ -31,23 +31,38 @@ class CustomPanel extends SidePanel implements IMovableSectionDestination {
31
31
  this.addClass('jp-xtralab-CustomPanel');
32
32
  }
33
33
 
34
+ /**
35
+ * The accordion panel hosting the sections.
36
+ */
34
37
  get accordionPanel(): AccordionPanel {
35
38
  return this.content as AccordionPanel;
36
39
  }
37
40
 
41
+ /**
42
+ * The section widgets currently hosted by the panel.
43
+ */
38
44
  get sections(): ReadonlyArray<Widget> {
39
45
  return this.content.widgets;
40
46
  }
41
47
 
48
+ /**
49
+ * A signal emitted with the new count when the number of sections changes.
50
+ */
42
51
  get sectionCountChanged(): ISignal<this, number> {
43
52
  return this._sectionCountChanged;
44
53
  }
45
54
 
55
+ /**
56
+ * Add a section widget to the panel.
57
+ */
46
58
  addSection(widget: Widget): void {
47
59
  this.addWidget(widget);
48
60
  this._sectionCountChanged.emit(this.content.widgets.length);
49
61
  }
50
62
 
63
+ /**
64
+ * Remove a section widget from the panel, if it is hosted here.
65
+ */
51
66
  removeSectionWidget(widget: Widget): void {
52
67
  if (widget.parent !== this.content) {
53
68
  return;
@@ -21,6 +21,10 @@ const PLUGIN_ID = 'xtralab:editor-breadcrumbs';
21
21
  const EDITOR_FACTORY = 'Editor';
22
22
  const TOOLBAR_ITEM_NAME = 'xtralab-editor-breadcrumbs';
23
23
 
24
+ /**
25
+ * A widget extension that adds an `EditorBreadcrumbs` item to the toolbar of
26
+ * each new editor widget.
27
+ */
24
28
  class EditorBreadcrumbsExtension implements DocumentRegistry.IWidgetExtension<
25
29
  IDocumentWidget<Widget, DocumentRegistry.IModel>,
26
30
  DocumentRegistry.IModel
@@ -30,6 +34,9 @@ class EditorBreadcrumbsExtension implements DocumentRegistry.IWidgetExtension<
30
34
  this._trans = trans;
31
35
  }
32
36
 
37
+ /**
38
+ * Create the breadcrumbs toolbar item for a new editor widget.
39
+ */
33
40
  createNew(
34
41
  widget: IDocumentWidget<Widget, DocumentRegistry.IModel>,
35
42
  context: DocumentRegistry.IContext<DocumentRegistry.IModel>
@@ -56,12 +63,9 @@ class EditorBreadcrumbsExtension implements DocumentRegistry.IWidgetExtension<
56
63
  }
57
64
 
58
65
  /**
59
- * Adds a VS Code-style path breadcrumb to text editor toolbars. Each
60
- * segment is clickable: clicking dispatches `xtralab:reveal-path` so
61
- * any plugin that listens (today, the file browser) can surface the
62
- * underlying folder or file. The breadcrumbs plugin therefore does not
63
- * import the file browser at all — the JupyterLab command registry is
64
- * the only seam between them.
66
+ * Adds a clickable path breadcrumb to text editor toolbars. Clicks dispatch
67
+ * `xtralab:reveal-path`; the command registry is the only seam with the file
68
+ * browser.
65
69
  */
66
70
  const plugin: JupyterFrontEndPlugin<void> = {
67
71
  id: PLUGIN_ID,
@@ -15,15 +15,25 @@ const CURRENT_ITEM_CLASS = 'jp-mod-current';
15
15
  const SEPARATOR_CLASS = 'jp-BreadCrumbs-separator';
16
16
 
17
17
  /**
18
- * Command dispatched when the user clicks a breadcrumb segment.
19
- * Registered by the file browser plugin; the breadcrumbs only know
20
- * the name so they stay decoupled from the file browser internals.
18
+ * Registered by the file browser plugin; only the name is shared here.
21
19
  */
22
20
  const REVEAL_COMMAND = 'xtralab:reveal-path';
23
21
 
22
+ /**
23
+ * The options used to create an `EditorBreadcrumbs` widget.
24
+ */
24
25
  interface IEditorBreadcrumbsOptions {
26
+ /**
27
+ * The document context whose path is rendered.
28
+ */
25
29
  context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
30
+ /**
31
+ * The command registry used to dispatch the reveal command.
32
+ */
26
33
  commands: CommandRegistry;
34
+ /**
35
+ * The application translation bundle.
36
+ */
27
37
  trans: TranslationBundle;
28
38
  }
29
39
 
@@ -38,9 +48,8 @@ export class EditorBreadcrumbs extends Widget {
38
48
  this._commands = options.commands;
39
49
  this._trans = options.trans;
40
50
  this.addClass(EDITOR_BREADCRUMBS_CLASS);
41
- // The breadcrumbs stretch to fill the toolbar, so ReactiveToolbar's
42
- // overflow math must count them as a spacer (2px) — measured at their
43
- // stretched clientWidth they would evict every item into the popup.
51
+ // ReactiveToolbar must count the stretched breadcrumbs as a spacer (2px);
52
+ // measured at clientWidth they would evict every item into the popup.
44
53
  this.addClass(TOOLBAR_SPACER_CLASS);
45
54
  this.node.setAttribute('aria-label', this._trans.__('Editor file path'));
46
55
 
@@ -55,6 +64,9 @@ export class EditorBreadcrumbs extends Widget {
55
64
  this._render();
56
65
  }
57
66
 
67
+ /**
68
+ * Dispose of the resources held by the widget.
69
+ */
58
70
  dispose(): void {
59
71
  if (this.isDisposed) {
60
72
  return;
@@ -81,9 +93,7 @@ export class EditorBreadcrumbs extends Widget {
81
93
  parts.forEach((part, index) => {
82
94
  const isLast = index === parts.length - 1;
83
95
  const subPath = parts.slice(0, index + 1).join('/');
84
- // Canonical `@pierre/trees` paths: directories carry a trailing
85
- // slash, files do not. Every part except the last names a folder
86
- // along the way to the open file.
96
+ // Canonical @pierre/trees paths: directories carry a trailing slash.
87
97
  const canonical = isLast ? subPath : `${subPath}/`;
88
98
 
89
99
  const item = document.createElement('span');
@@ -117,19 +127,15 @@ export class EditorBreadcrumbs extends Widget {
117
127
  item.dataset.path = '/';
118
128
  item.tabIndex = 0;
119
129
  item.setAttribute('role', 'button');
120
- // Empty canonical path: the reveal command treats this as the
121
- // workspace-root gesture — surface the file browser, clear its
122
- // selection, and scroll back to the top. The tree has no row for
123
- // the workspace root itself.
130
+ // Empty path is the workspace-root gesture; the tree has no row for
131
+ // the root itself.
124
132
  this._attachReveal(item, '');
125
133
  return item;
126
134
  }
127
135
 
128
136
  /**
129
- * Bind click and keyboard activation on a crumb element to the
130
- * shared reveal command. Dispatch is best-effort: a failure to
131
- * resolve or execute the command is logged but never thrown into
132
- * the editor's toolbar.
137
+ * Bind click and keyboard activation on a crumb to the reveal command;
138
+ * dispatch failures are logged, never thrown into the toolbar.
133
139
  */
134
140
  private _attachReveal(element: HTMLElement, canonicalPath: string): void {
135
141
  const fire = (event: Event): void => {
@@ -14,10 +14,8 @@ import detectIndent from 'detect-indent';
14
14
  const PLUGIN_ID = 'xtralab:editor-indent';
15
15
 
16
16
  /**
17
- * Mime types whose newly-created (or too-short-to-detect) files should default
18
- * to a 2-space indent. Matches Prettier's defaults for web languages; mime
19
- * types not listed here inherit whatever the user has set in JupyterLab.
20
- * Covers .ts/.tsx/.jsx/.js variants registered by `@jupyterlab/codemirror`.
17
+ * 2-space fallback for new or undetectable files, matching Prettier's
18
+ * web-language defaults; unlisted mime types keep the user's setting.
21
19
  */
22
20
  const FALLBACK_INDENT_BY_MIME: Record<string, number> = {
23
21
  'application/ecmascript': 2,
@@ -38,15 +36,20 @@ const FALLBACK_INDENT_BY_MIME: Record<string, number> = {
38
36
  'text/yaml': 2
39
37
  };
40
38
 
39
+ /**
40
+ * The indentation resolved for a document.
41
+ */
41
42
  interface IResolvedIndent {
43
+ /**
44
+ * The string inserted per indent level (spaces or a tab), for `indentUnit`.
45
+ */
42
46
  unit: string;
47
+ /**
48
+ * The indent width in columns, for `EditorState.tabSize`.
49
+ */
43
50
  width: number;
44
51
  }
45
52
 
46
- /**
47
- * Run `detect-indent` against the file's content and convert its result into
48
- * something we can hand to CodeMirror.
49
- */
50
53
  function detectFromContent(text: string): IResolvedIndent | null {
51
54
  if (text.length === 0) {
52
55
  return null;
@@ -76,9 +79,8 @@ function resolveIndent(text: string, mimeType: string): IResolvedIndent | null {
76
79
  }
77
80
 
78
81
  function buildExtension(resolved: IResolvedIndent): Extension {
79
- // `Prec.highest` keeps these values first in their respective facets so
80
- // they beat both `defaultConfig` and per-editor `editorConfig.indentUnit`,
81
- // both of which JupyterLab registers at default precedence.
82
+ // `Prec.highest` beats both `defaultConfig` and per-editor
83
+ // `editorConfig.indentUnit`, which JupyterLab registers at default precedence.
82
84
  return Prec.highest([
83
85
  indentUnit.of(resolved.unit),
84
86
  EditorState.tabSize.of(resolved.width)
@@ -86,18 +88,10 @@ function buildExtension(resolved: IResolvedIndent): Extension {
86
88
  }
87
89
 
88
90
  /**
89
- * Sniff each opened document's leading whitespace and configure CodeMirror's
90
- * `indentUnit` / `tabSize` to match. JupyterLab only exposes a single global
91
- * indent setting, which means TypeScript files written with 4-space indent and
92
- * Python files written with 2-space indent both look wrong in the same editor;
93
- * detecting from the file's own content sidesteps that without forcing every
94
- * project to share the same convention.
95
- *
96
- * Detection runs at editor-construction time and again on the first content
97
- * change — that second pass exists because JupyterLab's collaborative document
98
- * loader can settle the file body into the shared model after the editor view
99
- * already exists, in which case the factory would otherwise have only seen an
100
- * empty string.
91
+ * Sniff each document's indentation and configure CodeMirror's `indentUnit` /
92
+ * `tabSize` to match, since JupyterLab only has one global indent setting.
93
+ * Detection reruns on the first content change: the collaborative loader can
94
+ * settle the file body after the editor view exists, so the factory may see ''.
101
95
  */
102
96
  const plugin: JupyterFrontEndPlugin<void> = {
103
97
  id: PLUGIN_ID,
@@ -32,11 +32,9 @@ import { toCanonicalPath, toServerPath } from './contents';
32
32
  import { FILE_BROWSER_ID, IXtralabFileBrowser } from './widget';
33
33
 
34
34
  /**
35
- * Command identifiers exposed by the xtralab browser. We deliberately namespace
36
- * these under `xtralab:` rather than reusing the `filebrowser:` ids
37
- * because the core `filebrowser:*` commands look up a `FileBrowser` instance
38
- * via `IFileBrowserFactory.tracker` — our widget is not a `FileBrowser`, so
39
- * those handlers would never see it.
35
+ * Command ids for the xtralab browser. Namespaced `xtralab:` rather than
36
+ * reusing the `filebrowser:` ids: the core handlers resolve their target via
37
+ * `IFileBrowserFactory.tracker`, which never sees this widget.
40
38
  */
41
39
  export namespace CommandIDs {
42
40
  export const open = 'xtralab:open';
@@ -56,27 +54,40 @@ export namespace CommandIDs {
56
54
  }
57
55
 
58
56
  /**
59
- * Submenu id used to host the dynamically populated "Open With" entries.
60
- * Distinct from the core file browser's `jp-contextmenu-open-with` so the
61
- * core's populator does not try to fill ours with items derived from the
62
- * default file browser's selection.
57
+ * "Open With" submenu id. Distinct from the core `jp-contextmenu-open-with`
58
+ * so the core's populator does not fill ours from its own selection.
63
59
  */
64
60
  const OPEN_WITH_SUBMENU_ID = 'jp-contextmenu-xtralab-open-with';
65
61
 
62
+ /**
63
+ * Options for registering the xtralab file browser commands.
64
+ */
66
65
  interface IRegisterCommandsOptions {
66
+ /**
67
+ * The JupyterLab application the commands are registered on.
68
+ */
67
69
  app: JupyterFrontEnd;
70
+ /**
71
+ * The file browser widget the commands act on.
72
+ */
68
73
  browser: IXtralabFileBrowser;
74
+ /**
75
+ * The document manager used to resolve the current widget's file path.
76
+ */
69
77
  docManager: IDocumentManager;
78
+ /**
79
+ * The command palette the commands are added to, or `null` when unavailable.
80
+ */
70
81
  palette: ICommandPalette | null;
82
+ /**
83
+ * The translator for user-facing strings; `null` falls back to English.
84
+ */
71
85
  translator: ITranslator | null;
72
86
  }
73
87
 
74
88
  /**
75
- * Resolve the path of the item the user right-clicked. We prefer the
76
- * contextmenu event's target — that is how `app.contextMenu` resolves which
77
- * items match a selector — and fall back to the first item in the tree's
78
- * selection if the command was invoked without an active contextmenu event
79
- * (e.g. from the command palette).
89
+ * Path of the right-clicked item, from the contextmenu event's target;
90
+ * falls back to the tree selection when invoked without one (the palette).
80
91
  */
81
92
  function getTargetPath(
82
93
  app: JupyterFrontEnd,
@@ -92,11 +103,6 @@ function getTargetPath(
92
103
  return browser.selectedPaths[0];
93
104
  }
94
105
 
95
- /**
96
- * Resolve the kind of the item that was right-clicked, based on the data
97
- * attributes emitted by `@pierre/trees` rows. Returns `undefined` if no
98
- * contextmenu event is active or the target is not a tree item.
99
- */
100
106
  function getTargetKind(app: JupyterFrontEnd): 'file' | 'folder' | undefined {
101
107
  const node = app.contextMenuHitTest(
102
108
  n => n.dataset !== undefined && n.dataset.type === 'item'
@@ -106,14 +112,9 @@ function getTargetKind(app: JupyterFrontEnd): 'file' | 'folder' | undefined {
106
112
  }
107
113
 
108
114
  /**
109
- * Resolve the canonical paths the "Open" command should act on. When the
110
- * user right-clicks a row that is part of the current selection, every
111
- * selected file is opened — matching the default file browser, where
112
- * "Open" on any file in a multi-selection opens them all. If the
113
- * right-clicked row is *not* in the selection, only that row is opened.
114
- *
115
- * Folders are always filtered out: there is no "current directory" in
116
- * this tree, so opening a folder via "Open" is a no-op.
115
+ * Canonical paths "Open" acts on: the whole selection when the right-clicked
116
+ * row is part of it (matching the default browser), else just that row.
117
+ * Folders are filtered out — this tree has no "current directory".
117
118
  */
118
119
  function getOpenPaths(
119
120
  app: JupyterFrontEnd,
@@ -134,9 +135,6 @@ function getOpenPaths(
134
135
  return candidates.filter(path => !path.endsWith('/'));
135
136
  }
136
137
 
137
- /**
138
- * True iff there is an actionable target for a command on the right-click.
139
- */
140
138
  function hasTarget(
141
139
  app: JupyterFrontEnd,
142
140
  browser: IXtralabFileBrowser
@@ -145,11 +143,9 @@ function hasTarget(
145
143
  }
146
144
 
147
145
  /**
148
- * Resolve the directory used as the working directory for actions that
149
- * create a new file or folder. Folder selections become their own cwd;
150
- * file selections fall back to their parent. Returns the empty string
151
- * when the tree has no selection — that matches the contents API's
152
- * convention for "the root directory".
146
+ * Working directory for create actions: a selected folder is its own cwd, a
147
+ * file falls back to its parent; empty string (the contents-API root) when
148
+ * nothing is selected.
153
149
  */
154
150
  function getWorkingDirectory(browser: IXtralabFileBrowser): string {
155
151
  const first = browser.selectedPaths[0];
@@ -164,14 +160,10 @@ function getWorkingDirectory(browser: IXtralabFileBrowser): string {
164
160
  }
165
161
 
166
162
  /**
167
- * Resolve the main-area widget whose tab is the target of the current
168
- * context-menu event, or `null` when that target is not a document tab. The
169
- * shell stamps each tab's `data-id` with its widget id, so the right-clicked
170
- * tab resolves even when it is not the current one.
171
- *
172
- * Valid only during a context-menu invocation: `contextMenuHitTest` reads the
173
- * last context-menu event, which is not cleared when the menu closes, so
174
- * {@link documentPathToReveal} gates this to avoid resolving a stale tab.
163
+ * Main-area widget whose tab is the target of the current context-menu
164
+ * event, or `null`. `contextMenuHitTest` reads the last context-menu event,
165
+ * which is not cleared when the menu closes, so callers must gate against
166
+ * resolving a stale tab.
175
167
  */
176
168
  function contextMenuTabWidget(app: JupyterFrontEnd): Widget | null {
177
169
  const node = app.contextMenuHitTest(
@@ -190,10 +182,9 @@ function contextMenuTabWidget(app: JupyterFrontEnd): Widget | null {
190
182
  }
191
183
 
192
184
  /**
193
- * Resolve the document to reveal as a canonical `@pierre/trees` path: the
194
- * right-clicked tab when `fromContextMenu` is true (the file-tab menu),
195
- * otherwise the active main-area widget (the palette). A file's context
196
- * `path` is already canonical. Returns `undefined` when nothing resolves.
185
+ * Canonical path of the document to reveal: the right-clicked tab when
186
+ * `fromContextMenu` is true (the file-tab menu), else the active main-area
187
+ * widget (the palette).
197
188
  */
198
189
  function documentPathToReveal(
199
190
  app: JupyterFrontEnd,
@@ -211,14 +202,10 @@ function documentPathToReveal(
211
202
  }
212
203
 
213
204
  /**
214
- * Refresh the "Open With" submenu's items from the document factories that
215
- * can open the file under the right-click. Mirrors the
216
- * `@jupyterlab/filebrowser-extension:open-with` pattern: the schema declares
217
- * an empty submenu by id, and we look it up on the open context menu and
218
- * fill it before the user can hover.
219
- *
205
+ * Build the populator that fills the "Open With" submenu on every
206
+ * context-menu open, mirroring `filebrowser-extension:open-with`.
220
207
  * `preferredWidgetFactories(path)` is path-only, so the populator stays
221
- * synchronous and the submenu is ready before the user can hover over it.
208
+ * synchronous and the submenu is ready before the user can hover.
222
209
  */
223
210
  function makeOpenWithUpdater(
224
211
  app: JupyterFrontEnd,
@@ -259,13 +246,9 @@ function makeOpenWithUpdater(
259
246
  }
260
247
 
261
248
  /**
262
- * Register every xtralab command on the application command registry, attach
263
- * the items to the application context menu, and wire up the dynamic
264
- * "Open With" submenu populator.
265
- *
266
- * @returns A function that detaches the menu items and signal listener.
267
- * The commands themselves are owned by the registry for the lifetime of
268
- * the plugin.
249
+ * Register the xtralab commands, context-menu wiring, and the dynamic
250
+ * "Open With" populator. Returns a detach function; the commands themselves
251
+ * stay owned by the registry.
269
252
  */
270
253
  export function registerCommands(opts: IRegisterCommandsOptions): () => void {
271
254
  const { app, browser, docManager, palette, translator } = opts;
@@ -496,23 +479,16 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
496
479
  }
497
480
  });
498
481
 
499
- // Refresh the toggle's state however the filter is shown or hidden —
500
- // the toolbar button, or the auto-show that kicks in when typing with
501
- // the tree focused opens a search session.
482
+ // The filter can also auto-show when typing with the tree focused, so
483
+ // track visibility changes rather than the command's own executions.
502
484
  const onFilterVisibleChanged = (): void => {
503
485
  commands.notifyCommandChanged(CommandIDs.toggleFileFilter);
504
486
  };
505
487
  browser.fileFilterVisibleChanged.connect(onFilterVisibleChanged);
506
488
 
507
- // Public reveal seam. Other plugins (editor breadcrumbs, future
508
- // "reveal in tree" actions) call this command rather than depending
509
- // on the file browser widget directly. Activating the sidebar is part
510
- // of the contract: the tree is hidden behind a tab and a reveal that
511
- // does not surface the panel would silently no-op from the user's
512
- // point of view. An empty path is the "workspace root" gesture —
513
- // there is no tree row for the root itself, so the browser is asked
514
- // to scroll back to the top and clear its selection instead.
515
- // The tree is movable, so surface whichever sidebar widget holds it.
489
+ // Public reveal seam for other plugins; an empty path is the workspace-root
490
+ // gesture (the root has no tree row of its own). The tree is movable, so
491
+ // surface whichever sidebar widget holds it.
516
492
  const activateTreeHost = (): void => {
517
493
  for (const area of ['left', 'right']) {
518
494
  for (const widget of app.shell.widgets(area)) {
@@ -538,8 +514,6 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
538
514
  }
539
515
  });
540
516
 
541
- // Reveal the resolved document in the tree browser via the reveal-path
542
- // command, which surfaces the sidebar and scrolls to the target.
543
517
  commands.addCommand(CommandIDs.revealInFileTree, {
544
518
  label: trans.__('Show in File Tree'),
545
519
  caption: trans.__('Show this file in the xtralab file browser'),
@@ -579,10 +553,8 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
579
553
  commands.addCommand(CommandIDs.newLauncher, {
580
554
  label: trans.__('New Launcher'),
581
555
  caption: trans.__('Open a new launcher'),
582
- // Skip the `menuItem` bindprops the other commands use: this command
583
- // lives on the toolbar, where the launcher-extension's blue button
584
- // styling expects the raw `jp-icon3` paths from `addIcon` so it can
585
- // recolor them against the brand background.
556
+ // Raw `addIcon`, no `menuItem` bindprops: the launcher-extension's blue
557
+ // toolbar-button styling recolors the raw `jp-icon3` paths.
586
558
  icon: addIcon,
587
559
  execute: () => {
588
560
  const cwd = getWorkingDirectory(browser);
@@ -590,34 +562,24 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
590
562
  }
591
563
  });
592
564
 
593
- // Re-evaluate enabled/visible state when the user changes the tree
594
- // selection, in case a command's predicate depends on the selection.
595
565
  browser.selectionChanged.connect(() => {
596
566
  for (const id of Object.values(CommandIDs)) {
597
567
  commands.notifyCommandChanged(id);
598
568
  }
599
569
  });
600
570
 
601
- // This command's enabled state tracks the active widget, not the tree
602
- // selection, so re-evaluate it on current-widget changes. `currentChanged`
603
- // is optional on the shell interface.
604
571
  const onCurrentChanged = (): void => {
605
572
  commands.notifyCommandChanged(CommandIDs.revealInFileTree);
606
573
  };
607
574
  app.shell.currentChanged?.connect(onCurrentChanged);
608
575
 
609
- // Also surface it in the palette (acts on the active editor); the file-tab
610
- // context-menu entry is declared in `schema/plugin.json`.
611
576
  const paletteItem = palette?.addItem({
612
577
  command: CommandIDs.revealInFileTree,
613
578
  category: trans.__('File Browser')
614
579
  });
615
580
 
616
- // The static items are declared in `schema/plugin.json` under
617
- // `jupyter.lab.menus.context` so users can override or disable them. The
618
- // schema also declares an empty submenu placeholder with id
619
- // `OPEN_WITH_SUBMENU_ID`; we fill it on every open from the document
620
- // factories that can open the right-clicked file.
581
+ // Static context-menu items live in `schema/plugin.json` so users can
582
+ // override them; the schema declares the empty "Open With" placeholder.
621
583
  const updateOpenWithMenu = makeOpenWithUpdater(app, browser);
622
584
  app.contextMenu.opened.connect(updateOpenWithMenu);
623
585
 
@@ -630,9 +592,8 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
630
592
  }
631
593
 
632
594
  /**
633
- * The names we register with the toolbar. Kept distinct from
634
- * {@link CommandIDs} because the toolbar API uses opaque names rather
635
- * than commands.
595
+ * Toolbar item names, distinct from {@link CommandIDs}: the toolbar API
596
+ * uses opaque names rather than commands.
636
597
  */
637
598
  export namespace ToolbarNames {
638
599
  export const newLauncher = 'new-launcher';
@@ -643,10 +604,8 @@ export namespace ToolbarNames {
643
604
  }
644
605
 
645
606
  /**
646
- * Populate the file browser's toolbar with the buttons that mirror the
647
- * default JupyterLab file browser: a "+" launcher button, a new-folder
648
- * button, a refresh button, a collapse-all button, and the file filter
649
- * toggle.
607
+ * Populate the toolbar with the buttons that mirror the default JupyterLab
608
+ * file browser.
650
609
  */
651
610
  export function populateToolbar(opts: {
652
611
  app: JupyterFrontEnd;