xtralab 0.14.1 → 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.
- package/README.md +10 -0
- package/lib/about/icons.d.ts +0 -5
- package/lib/about/icons.js +0 -5
- package/lib/about/index.d.ts +4 -8
- package/lib/about/index.js +6 -17
- package/lib/agentSessions.d.ts +9 -29
- package/lib/agentSessions.js +2 -7
- package/lib/askAgent/editorSelection.d.ts +20 -26
- package/lib/askAgent/editorSelection.js +11 -26
- package/lib/askAgent/icons.d.ts +2 -3
- package/lib/askAgent/icons.js +2 -3
- package/lib/askAgent/index.d.ts +4 -26
- package/lib/askAgent/index.js +54 -158
- package/lib/askAgent/popup.d.ts +12 -16
- package/lib/askAgent/popup.js +18 -37
- package/lib/askAgent/prompt.d.ts +3 -5
- package/lib/askAgent/prompt.js +6 -19
- package/lib/askAgent/queue.d.ts +11 -21
- package/lib/askAgent/queue.js +9 -19
- package/lib/askAgent/queuePanel.d.ts +13 -18
- package/lib/askAgent/queuePanel.js +15 -37
- package/lib/askAgent/targetPicker.d.ts +2 -9
- package/lib/askAgent/targetPicker.js +6 -11
- package/lib/askAgent/tokens.d.ts +32 -46
- package/lib/askAgent/tokens.js +4 -6
- package/lib/commandBar/index.d.ts +4 -13
- package/lib/commandBar/index.js +21 -39
- package/lib/customPanel/index.d.ts +7 -0
- package/lib/customPanel/index.js +89 -0
- package/lib/editorBreadcrumbs/index.d.ts +3 -6
- package/lib/editorBreadcrumbs/index.js +10 -6
- package/lib/editorBreadcrumbs/widget.d.ts +17 -4
- package/lib/editorBreadcrumbs/widget.js +11 -17
- package/lib/editorIndent/index.d.ts +4 -12
- package/lib/editorIndent/index.js +8 -23
- package/lib/fileBrowser/commands.d.ts +28 -19
- package/lib/fileBrowser/commands.js +51 -98
- package/lib/fileBrowser/contents.d.ts +14 -0
- package/lib/fileBrowser/contents.js +28 -0
- package/lib/fileBrowser/dragAndDrop.d.ts +79 -0
- package/lib/fileBrowser/dragAndDrop.js +110 -0
- package/lib/fileBrowser/fileBrowser.d.ts +21 -9
- package/lib/fileBrowser/fileBrowser.js +211 -405
- package/lib/fileBrowser/gitStatus.d.ts +4 -9
- package/lib/fileBrowser/gitStatus.js +7 -17
- package/lib/fileBrowser/gitignore.d.ts +4 -17
- package/lib/fileBrowser/gitignore.js +4 -25
- package/lib/fileBrowser/icons.d.ts +7 -17
- package/lib/fileBrowser/icons.js +25 -80
- package/lib/fileBrowser/index.js +41 -10
- package/lib/fileBrowser/widget.d.ts +170 -53
- package/lib/fileBrowser/widget.js +242 -18
- package/lib/fileTypeIcons/index.js +0 -10
- package/lib/git/api.d.ts +5 -13
- package/lib/git/api.js +10 -36
- package/lib/git/askRequest.d.ts +4 -8
- package/lib/git/askRequest.js +6 -20
- package/lib/git/commands.d.ts +40 -5
- package/lib/git/commands.js +16 -10
- package/lib/git/diffModel.js +4 -0
- package/lib/git/diffSurface.d.ts +41 -55
- package/lib/git/diffSurface.js +42 -117
- package/lib/git/diffWidget.d.ts +81 -2
- package/lib/git/diffWidget.js +62 -12
- package/lib/git/imageDiff.d.ts +5 -6
- package/lib/git/imageDiff.js +18 -48
- package/lib/git/index.d.ts +0 -3
- package/lib/git/index.js +3 -8
- package/lib/git/notebookDiff.d.ts +110 -33
- package/lib/git/notebookDiff.js +52 -195
- package/lib/git/tokens.d.ts +73 -31
- package/lib/git/tokens.js +2 -3
- package/lib/highlight/index.d.ts +4 -10
- package/lib/highlight/index.js +10 -41
- package/lib/index.d.ts +0 -9
- package/lib/index.js +2 -9
- package/lib/launcher/agents.d.ts +55 -51
- package/lib/launcher/agents.js +25 -54
- package/lib/launcher/availability.d.ts +3 -9
- package/lib/launcher/availability.js +3 -9
- package/lib/launcher/commands.d.ts +7 -32
- package/lib/launcher/commands.js +14 -45
- package/lib/launcher/dashboard.d.ts +19 -24
- package/lib/launcher/dashboard.js +21 -67
- package/lib/launcher/editorRegistry.d.ts +10 -22
- package/lib/launcher/editorRegistry.js +14 -18
- package/lib/launcher/editors.d.ts +39 -45
- package/lib/launcher/editors.js +8 -34
- package/lib/launcher/icons.d.ts +0 -12
- package/lib/launcher/icons.js +14 -37
- package/lib/launcher/index.js +12 -55
- package/lib/launcher/invocation.d.ts +4 -10
- package/lib/launcher/invocation.js +7 -22
- package/lib/launcher/registry.d.ts +9 -7
- package/lib/launcher/registry.js +9 -7
- package/lib/launcher/schemaDefaults.d.ts +2 -9
- package/lib/launcher/schemaDefaults.js +37 -13
- package/lib/launcher/tokens.d.ts +10 -22
- package/lib/launcher/tokens.js +5 -8
- package/lib/menuBar/index.d.ts +3 -10
- package/lib/menuBar/index.js +11 -28
- package/lib/menus/index.d.ts +3 -9
- package/lib/menus/index.js +14 -30
- package/lib/omnibox/files.d.ts +2 -4
- package/lib/omnibox/files.js +5 -11
- package/lib/omnibox/index.d.ts +4 -14
- package/lib/omnibox/index.js +9 -27
- package/lib/omnibox/model.d.ts +38 -10
- package/lib/omnibox/model.js +15 -33
- package/lib/omnibox/recents.d.ts +15 -17
- package/lib/omnibox/recents.js +12 -17
- package/lib/omnibox/tokens.d.ts +5 -10
- package/lib/omnibox/tokens.js +2 -6
- package/lib/omnibox/widget.d.ts +11 -7
- package/lib/omnibox/widget.js +8 -16
- package/lib/searchReplace/index.d.ts +4 -20
- package/lib/searchReplace/index.js +7 -27
- package/lib/showOutput/index.d.ts +3 -12
- package/lib/showOutput/index.js +6 -22
- package/lib/sidebar/index.d.ts +4 -13
- package/lib/sidebar/index.js +73 -46
- package/lib/terminalNotifications/index.d.ts +4 -10
- package/lib/terminalNotifications/index.js +6 -20
- package/lib/terminals/agentTerminals.d.ts +12 -5
- package/lib/terminals/agentTerminals.js +25 -44
- package/lib/terminals/detection.d.ts +4 -8
- package/lib/terminals/detection.js +4 -8
- package/lib/terminals/index.d.ts +0 -33
- package/lib/terminals/index.js +24 -102
- package/lib/terminals/model.d.ts +64 -139
- package/lib/terminals/model.js +80 -220
- package/lib/terminals/tokens.d.ts +11 -22
- package/lib/terminals/tokens.js +2 -2
- package/lib/terminals/widget.d.ts +72 -38
- package/lib/terminals/widget.js +173 -73
- package/lib/topBar/icons.d.ts +3 -8
- package/lib/topBar/icons.js +3 -8
- package/lib/topBar/index.d.ts +4 -11
- package/lib/topBar/index.js +10 -38
- package/lib/walkthrough/index.d.ts +4 -9
- package/lib/walkthrough/index.js +6 -20
- package/lib/walkthrough/panel.d.ts +40 -9
- package/lib/walkthrough/panel.js +4 -10
- package/package.json +2 -2
- package/schema/launcher.json +2 -2
- package/schema/sidebar.json +47 -7
- package/src/about/icons.ts +0 -5
- package/src/about/index.tsx +6 -17
- package/src/agentSessions.ts +9 -29
- package/src/askAgent/editorSelection.ts +22 -29
- package/src/askAgent/icons.ts +2 -3
- package/src/askAgent/index.ts +54 -158
- package/src/askAgent/popup.tsx +23 -45
- package/src/askAgent/prompt.ts +6 -19
- package/src/askAgent/queue.ts +13 -24
- package/src/askAgent/queuePanel.tsx +21 -46
- package/src/askAgent/targetPicker.tsx +5 -15
- package/src/askAgent/tokens.ts +32 -46
- package/src/commandBar/index.ts +24 -39
- package/src/customPanel/index.ts +119 -0
- package/src/editorBreadcrumbs/index.ts +10 -6
- package/src/editorBreadcrumbs/widget.ts +23 -17
- package/src/editorIndent/index.ts +17 -23
- package/src/fileBrowser/commands.ts +70 -98
- package/src/fileBrowser/contents.ts +33 -0
- package/src/fileBrowser/dragAndDrop.ts +188 -0
- package/src/fileBrowser/fileBrowser.tsx +259 -440
- package/src/fileBrowser/gitStatus.ts +7 -17
- package/src/fileBrowser/gitignore.ts +4 -25
- package/src/fileBrowser/icons.ts +25 -80
- package/src/fileBrowser/index.ts +43 -10
- package/src/fileBrowser/widget.tsx +344 -64
- package/src/fileTypeIcons/index.ts +0 -10
- package/src/git/api.ts +10 -36
- package/src/git/askRequest.ts +6 -20
- package/src/git/commands.ts +70 -10
- package/src/git/diffModel.ts +25 -0
- package/src/git/diffSurface.tsx +71 -159
- package/src/git/diffWidget.tsx +104 -14
- package/src/git/imageDiff.tsx +45 -51
- package/src/git/index.ts +3 -8
- package/src/git/notebookDiff.tsx +211 -238
- package/src/git/tokens.ts +73 -31
- package/src/highlight/index.ts +10 -41
- package/src/index.ts +2 -9
- package/src/launcher/agents.ts +74 -92
- package/src/launcher/availability.ts +3 -9
- package/src/launcher/commands.ts +14 -51
- package/src/launcher/dashboard.tsx +49 -88
- package/src/launcher/editorRegistry.ts +21 -29
- package/src/launcher/editors.ts +39 -58
- package/src/launcher/icons.ts +15 -37
- package/src/launcher/index.ts +12 -55
- package/src/launcher/invocation.ts +7 -22
- package/src/launcher/registry.ts +9 -7
- package/src/launcher/schemaDefaults.ts +43 -13
- package/src/launcher/tokens.ts +10 -22
- package/src/menuBar/index.ts +11 -28
- package/src/menus/index.ts +14 -30
- package/src/omnibox/files.ts +10 -14
- package/src/omnibox/index.ts +9 -27
- package/src/omnibox/model.ts +50 -38
- package/src/omnibox/recents.ts +17 -22
- package/src/omnibox/tokens.ts +5 -10
- package/src/omnibox/widget.tsx +14 -19
- package/src/searchReplace/index.ts +7 -27
- package/src/showOutput/index.ts +6 -22
- package/src/sidebar/index.ts +103 -52
- package/src/terminalNotifications/index.ts +42 -25
- package/src/terminals/agentTerminals.ts +25 -49
- package/src/terminals/detection.ts +4 -8
- package/src/terminals/index.ts +31 -102
- package/src/terminals/model.ts +130 -246
- package/src/terminals/tokens.ts +11 -22
- package/src/terminals/widget.tsx +229 -113
- package/src/topBar/icons.ts +3 -8
- package/src/topBar/index.ts +21 -44
- package/src/walkthrough/index.ts +6 -20
- package/src/walkthrough/panel.ts +40 -10
- package/style/about.css +2 -7
- package/style/askAgent.css +8 -40
- package/style/base.css +25 -73
- package/style/chrome.css +108 -308
- package/style/commandBar.css +4 -20
- package/style/customPanel.css +6 -0
- package/style/git.css +22 -184
- package/style/highlight.css +1 -3
- package/style/index.js +1 -0
- package/style/launcher.css +6 -70
- package/style/omnibox.css +2 -14
- package/style/showOutput.css +0 -6
- package/style/sidebar.css +29 -9
- package/style/tabs.css +5 -13
- package/style/terminals.css +18 -123
- package/style/topBar.css +2 -22
- package/style/walkthrough.css +0 -8
package/src/omnibox/widget.tsx
CHANGED
|
@@ -11,21 +11,15 @@ import { computeSections, IOmniboxItem } from './model';
|
|
|
11
11
|
import { loadWorkspaceFiles } from './files';
|
|
12
12
|
import type { OmniboxRecents } from './recents';
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* DOM id of the results listbox, referenced by the input's `aria-controls`.
|
|
16
|
-
*/
|
|
17
14
|
const LIST_ID = 'jp-xtralab-Omnibox-list';
|
|
18
15
|
|
|
19
|
-
/**
|
|
20
|
-
* Stable DOM id for the result row at `index`, for `aria-activedescendant`.
|
|
21
|
-
*/
|
|
22
16
|
function optionId(index: number): string {
|
|
23
17
|
return `jp-xtralab-Omnibox-option-${index}`;
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
/**
|
|
27
21
|
* Render `text` with the fuzzy-matched characters at `indices` wrapped in a
|
|
28
|
-
* highlight span
|
|
22
|
+
* highlight span.
|
|
29
23
|
*/
|
|
30
24
|
function renderHighlight(
|
|
31
25
|
text: string,
|
|
@@ -125,13 +119,10 @@ function OmniboxComponent(props: OmniboxWidget.IOptions): JSX.Element {
|
|
|
125
119
|
);
|
|
126
120
|
const hasResults = flat.length > 0;
|
|
127
121
|
|
|
128
|
-
// Reset the highlighted row whenever the result set changes, and keep the
|
|
129
|
-
// index in range when results shrink.
|
|
130
122
|
React.useEffect(() => {
|
|
131
123
|
setActive(current => (current < flat.length ? current : 0));
|
|
132
124
|
}, [flat]);
|
|
133
125
|
|
|
134
|
-
// Keep the highlighted row scrolled into view as it moves.
|
|
135
126
|
React.useEffect(() => {
|
|
136
127
|
listRef.current
|
|
137
128
|
?.querySelector('.jp-mod-active')
|
|
@@ -139,8 +130,8 @@ function OmniboxComponent(props: OmniboxWidget.IOptions): JSX.Element {
|
|
|
139
130
|
}, [active, flat]);
|
|
140
131
|
|
|
141
132
|
const dismiss = React.useCallback(() => {
|
|
142
|
-
// Defer so unmounting this React root
|
|
143
|
-
//
|
|
133
|
+
// Defer so unmounting this React root never happens synchronously inside
|
|
134
|
+
// the event handler that triggered it.
|
|
144
135
|
window.setTimeout(onClose, 0);
|
|
145
136
|
}, [onClose]);
|
|
146
137
|
|
|
@@ -307,10 +298,8 @@ function OmniboxComponent(props: OmniboxWidget.IOptions): JSX.Element {
|
|
|
307
298
|
}
|
|
308
299
|
|
|
309
300
|
/**
|
|
310
|
-
* The omnibox overlay widget
|
|
311
|
-
*
|
|
312
|
-
* theme CSS variables resolve outside the shell, and its own `jp-xtralab-Omnibox`
|
|
313
|
-
* class for the overlay/panel styling (style/omnibox.css).
|
|
301
|
+
* The omnibox overlay widget, attached to `document.body`; `jp-ThemedContainer`
|
|
302
|
+
* makes JupyterLab's theme CSS variables resolve outside the shell.
|
|
314
303
|
*/
|
|
315
304
|
export class OmniboxWidget extends ReactWidget {
|
|
316
305
|
constructor(options: OmniboxWidget.IOptions) {
|
|
@@ -321,6 +310,9 @@ export class OmniboxWidget extends ReactWidget {
|
|
|
321
310
|
this.addClass('jp-ThemedContainer');
|
|
322
311
|
}
|
|
323
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Render the omnibox overlay content.
|
|
315
|
+
*/
|
|
324
316
|
render(): JSX.Element {
|
|
325
317
|
return <OmniboxComponent {...this._options} />;
|
|
326
318
|
}
|
|
@@ -328,20 +320,23 @@ export class OmniboxWidget extends ReactWidget {
|
|
|
328
320
|
private _options: OmniboxWidget.IOptions;
|
|
329
321
|
}
|
|
330
322
|
|
|
331
|
-
/**
|
|
332
|
-
* A namespace for `OmniboxWidget` statics.
|
|
333
|
-
*/
|
|
334
323
|
export namespace OmniboxWidget {
|
|
335
324
|
/**
|
|
336
325
|
* Construction options for {@link OmniboxWidget}.
|
|
337
326
|
*/
|
|
338
327
|
export interface IOptions {
|
|
328
|
+
/**
|
|
329
|
+
* The command registry used to look up and execute commands.
|
|
330
|
+
*/
|
|
339
331
|
commands: CommandRegistry;
|
|
340
332
|
/**
|
|
341
333
|
* Snapshot of the command palette's items, read once when the overlay
|
|
342
334
|
* opens; empty when the palette isn't available.
|
|
343
335
|
*/
|
|
344
336
|
paletteItems: ReadonlyArray<CommandPalette.IItem>;
|
|
337
|
+
/**
|
|
338
|
+
* The document registry used to pick file icons.
|
|
339
|
+
*/
|
|
345
340
|
docRegistry: DocumentRegistry;
|
|
346
341
|
/**
|
|
347
342
|
* Snapshot of the available agents, read once when the overlay opens.
|
|
@@ -11,34 +11,16 @@ const PLUGIN_ID = 'xtralab:search-replace';
|
|
|
11
11
|
const COMMAND_ID = 'xtralab:activate-search-replace';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Widget id of the
|
|
15
|
-
* extension
|
|
16
|
-
* so there is nothing to bind a shortcut to out of the box — this plugin adds
|
|
17
|
-
* the missing command.
|
|
14
|
+
* Widget id of the `jupyterlab-search-replace` left-sidebar panel; the
|
|
15
|
+
* extension registers no command of its own to bind a shortcut to.
|
|
18
16
|
*/
|
|
19
17
|
const SEARCH_REPLACE_WIDGET_ID = 'jp-search-replace';
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
|
-
* Bind Accel+Shift+F
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Two pieces are needed because that chord is already taken: JupyterLab core
|
|
27
|
-
* binds Accel+Shift+F to `filebrowser:toggle-main`.
|
|
28
|
-
*
|
|
29
|
-
* 1. The chord is freed declaratively. `SettingRegistry.reconcileShortcuts`
|
|
30
|
-
* keys collisions and disables on (keys, selector) — not on command — so the
|
|
31
|
-
* `@jupyterlab/shortcuts-extension:shortcuts` override in
|
|
32
|
-
* `default_setting_overrides.d/00-xtralab.json` disables the file-browser
|
|
33
|
-
* binding. The file browser stays reachable from its sidebar tab and the
|
|
34
|
-
* View menu; only the keyboard shortcut moves.
|
|
35
|
-
*
|
|
36
|
-
* 2. The new binding is added imperatively below rather than as another shipped
|
|
37
|
-
* default. A `disabled` default and a re-binding default land on the same
|
|
38
|
-
* (keys, selector) slot, and the disable suppresses every default on that
|
|
39
|
-
* slot — including the re-binding. `commands.addKeyBinding` runs outside the
|
|
40
|
-
* reconcile pass, so it is unaffected and wins cleanly once the file-browser
|
|
41
|
-
* binding is gone.
|
|
20
|
+
* Bind Accel+Shift+F to the Search and Replace panel (VS Code parity). Core
|
|
21
|
+
* binds the chord to `filebrowser:toggle-main`; a `default_setting_overrides.d`
|
|
22
|
+
* override disables it. The re-binding is imperative: `reconcileShortcuts` keys
|
|
23
|
+
* on (keys, selector), so the disable would suppress a shipped default too.
|
|
42
24
|
*/
|
|
43
25
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
44
26
|
id: PLUGIN_ID,
|
|
@@ -60,9 +42,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
60
42
|
label: trans.__('Search and Replace'),
|
|
61
43
|
caption: trans.__('Show the Search and Replace panel'),
|
|
62
44
|
execute: () => {
|
|
63
|
-
//
|
|
64
|
-
// tab, and the widget focuses its search input from `onAfterShow`, so
|
|
65
|
-
// the user can start typing a query immediately. It no-ops if
|
|
45
|
+
// The widget focuses its search input from `onAfterShow`; no-ops if
|
|
66
46
|
// `jupyterlab-search-replace` is unavailable.
|
|
67
47
|
labShell.activateById(SEARCH_REPLACE_WIDGET_ID);
|
|
68
48
|
}
|
package/src/showOutput/index.ts
CHANGED
|
@@ -12,24 +12,12 @@ import { coerceData } from '../mimeData';
|
|
|
12
12
|
|
|
13
13
|
const PLUGIN_ID = 'xtralab:show-output';
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Render arbitrary rich content into a panel, with no notebook and no kernel.
|
|
17
|
-
*/
|
|
18
15
|
const SHOW_COMMAND = 'xtralab:show';
|
|
19
16
|
|
|
20
17
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* This is the kernel-free counterpart to running a notebook. A coding agent
|
|
25
|
-
* generates the content itself (a Vega-Lite spec, a Markdown explainer, an
|
|
26
|
-
* HTML fragment, an SVG, a base64 image) and hands it to this command, which
|
|
27
|
-
* renders it with the same renderers JupyterLab uses for cell output and docks
|
|
28
|
-
* the result beside the code. Nothing is written to disk and no kernel is
|
|
29
|
-
* required, so it works even where the kernel has no plotting libraries.
|
|
30
|
-
*
|
|
31
|
-
* Repeated calls with the same `id` reuse one panel, so a walkthrough can
|
|
32
|
-
* refresh the view in place instead of piling up tabs.
|
|
18
|
+
* Contributes `xtralab:show`: render a single MIME bundle into a panel with
|
|
19
|
+
* the same renderers JupyterLab uses for cell output — no kernel, no file on
|
|
20
|
+
* disk. Repeated calls with the same `id` reuse one panel.
|
|
33
21
|
*/
|
|
34
22
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
35
23
|
id: PLUGIN_ID,
|
|
@@ -48,7 +36,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
48
36
|
const { commands } = app;
|
|
49
37
|
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
50
38
|
|
|
51
|
-
// One reusable panel per `id`, so repeated shows refresh in place.
|
|
52
39
|
const panels = new Map<string, Panel>();
|
|
53
40
|
|
|
54
41
|
commands.addCommand(SHOW_COMMAND, {
|
|
@@ -120,9 +107,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
120
107
|
|
|
121
108
|
const model = rendermime.createModel({ data: bundle, trusted: true });
|
|
122
109
|
const renderer = rendermime.createRenderer(chosen);
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
// the content is taller than it (style/showOutput.css).
|
|
110
|
+
// Not a MainAreaWidget: it does not lay out its content in the side
|
|
111
|
+
// area; the panel scrolls via style/showOutput.css.
|
|
126
112
|
const widget = new Panel();
|
|
127
113
|
widget.addClass('jp-xtralab-ShowPanel');
|
|
128
114
|
widget.id = `xtralab-show-${id}`;
|
|
@@ -130,7 +116,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
130
116
|
widget.title.closable = true;
|
|
131
117
|
widget.addWidget(renderer);
|
|
132
118
|
|
|
133
|
-
// Replace any existing panel with this id so shows refresh in place.
|
|
134
119
|
const existing = panels.get(id);
|
|
135
120
|
if (existing && !existing.isDisposed) {
|
|
136
121
|
existing.dispose();
|
|
@@ -143,8 +128,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
143
128
|
});
|
|
144
129
|
|
|
145
130
|
// Attach before rendering so renderers that need layout (Vega) size
|
|
146
|
-
// correctly.
|
|
147
|
-
// only split the document area when `area` is "main".
|
|
131
|
+
// correctly.
|
|
148
132
|
if (area === 'main') {
|
|
149
133
|
labShell.add(widget, 'main', {
|
|
150
134
|
mode,
|
package/src/sidebar/index.ts
CHANGED
|
@@ -10,21 +10,61 @@ import type { Widget } from '@lumino/widgets';
|
|
|
10
10
|
const PLUGIN_ID = 'xtralab:sidebar';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `running-extension` adds the running panel at rank 200 — and we reuse
|
|
17
|
-
* them when re-adding so the tab lands back in its usual position.
|
|
13
|
+
* `rank` is the fallback used when re-adding a hidden tab; the shipped
|
|
14
|
+
* `layout` setting normally wins because `LabShell.add` merges its
|
|
15
|
+
* per-widget options over the ones passed in.
|
|
18
16
|
*/
|
|
19
17
|
interface ITarget {
|
|
18
|
+
/**
|
|
19
|
+
* The shell widget id of the sidebar tab.
|
|
20
|
+
*/
|
|
20
21
|
id: string;
|
|
22
|
+
/**
|
|
23
|
+
* The sidebar rank used when re-adding the hidden tab.
|
|
24
|
+
*/
|
|
21
25
|
rank: number;
|
|
22
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The boolean plugin setting controlling the tab's visibility.
|
|
28
|
+
*/
|
|
29
|
+
settingKey:
|
|
30
|
+
| 'showTerminals'
|
|
31
|
+
| 'showFileBrowser'
|
|
32
|
+
| 'showGitPanel'
|
|
33
|
+
| 'showDefaultFileBrowser'
|
|
34
|
+
| 'showRunningSessions'
|
|
35
|
+
| 'showSearchReplace';
|
|
36
|
+
/**
|
|
37
|
+
* The id of the toggle command registered for the tab.
|
|
38
|
+
*/
|
|
23
39
|
command: string;
|
|
40
|
+
/**
|
|
41
|
+
* The localized label of the toggle command.
|
|
42
|
+
*/
|
|
24
43
|
label: (trans: ReturnType<ITranslator['load']>) => string;
|
|
25
44
|
}
|
|
26
45
|
|
|
27
46
|
const TARGETS: ITarget[] = [
|
|
47
|
+
{
|
|
48
|
+
id: 'xtralab-running-terminals',
|
|
49
|
+
rank: 1,
|
|
50
|
+
settingKey: 'showTerminals',
|
|
51
|
+
command: 'xtralab:toggle-terminals',
|
|
52
|
+
label: trans => trans.__('Terminals')
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'xtralab:file-browser',
|
|
56
|
+
rank: 2,
|
|
57
|
+
settingKey: 'showFileBrowser',
|
|
58
|
+
command: 'xtralab:toggle-file-browser',
|
|
59
|
+
label: trans => trans.__('File Browser')
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'jp-git-sessions',
|
|
63
|
+
rank: 200,
|
|
64
|
+
settingKey: 'showGitPanel',
|
|
65
|
+
command: 'xtralab:toggle-git-panel',
|
|
66
|
+
label: trans => trans.__('Git')
|
|
67
|
+
},
|
|
28
68
|
{
|
|
29
69
|
id: 'filebrowser',
|
|
30
70
|
rank: 100,
|
|
@@ -38,28 +78,26 @@ const TARGETS: ITarget[] = [
|
|
|
38
78
|
settingKey: 'showRunningSessions',
|
|
39
79
|
command: 'xtralab:toggle-running-sessions',
|
|
40
80
|
label: trans => trans.__('Running Terminals and Kernels')
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'jp-search-replace',
|
|
84
|
+
rank: 900,
|
|
85
|
+
settingKey: 'showSearchReplace',
|
|
86
|
+
command: 'xtralab:toggle-search-replace',
|
|
87
|
+
label: trans => trans.__('Search and Replace')
|
|
41
88
|
}
|
|
42
89
|
];
|
|
43
90
|
|
|
44
91
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* side
|
|
48
|
-
*
|
|
49
|
-
* the matching tab. The widget instance is preserved, so we can re-add it
|
|
50
|
-
* later via `labShell.add` with its original rank.
|
|
51
|
-
*
|
|
52
|
-
* The menu placement is contributed declaratively in
|
|
53
|
-
* `schema/sidebar.json`, so this plugin only registers commands and
|
|
54
|
-
* applies state — it does not touch `IMainMenu` directly.
|
|
55
|
-
*
|
|
56
|
-
* The preference is persisted to `xtralab:sidebar` settings so the choice
|
|
57
|
-
* survives reloads.
|
|
92
|
+
* Toggles individual sidebar tabs from View > Appearance > Sidebars. Hiding
|
|
93
|
+
* detaches the widget while keeping the instance for a later re-add; which
|
|
94
|
+
* side it came from is only tracked in-memory, so after a reload a re-shown
|
|
95
|
+
* tab lands on the left. Menu placement is declared in schema/sidebar.json.
|
|
58
96
|
*/
|
|
59
97
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
60
98
|
id: PLUGIN_ID,
|
|
61
99
|
description:
|
|
62
|
-
'Toggle visibility of
|
|
100
|
+
'Toggle visibility of individual sidebar tabs from the View menu.',
|
|
63
101
|
autoStart: true,
|
|
64
102
|
requires: [ILabShell],
|
|
65
103
|
optional: [ISettingRegistry, ITranslator],
|
|
@@ -72,31 +110,38 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
72
110
|
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
73
111
|
const { commands } = app;
|
|
74
112
|
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// reachable through `labShell.widgets('left')` once removed, so a
|
|
78
|
-
// missing cache entry would leave us unable to bring the tab back.
|
|
113
|
+
// Detached widgets are unreachable via `labShell.widgets(...)`; keep
|
|
114
|
+
// instances here so hidden tabs can be re-added.
|
|
79
115
|
const widgetCache = new Map<string, Widget>();
|
|
80
116
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
117
|
+
const hiddenFrom = new Map<string, 'left' | 'right'>();
|
|
118
|
+
|
|
119
|
+
const findInSidebars = (
|
|
120
|
+
id: string
|
|
121
|
+
): { widget: Widget; area: 'left' | 'right' } | null => {
|
|
122
|
+
for (const area of ['left', 'right'] as const) {
|
|
123
|
+
for (const widget of labShell.widgets(area)) {
|
|
124
|
+
if (widget.id === id) {
|
|
125
|
+
return { widget, area };
|
|
126
|
+
}
|
|
85
127
|
}
|
|
86
128
|
}
|
|
87
129
|
return null;
|
|
88
130
|
};
|
|
89
131
|
|
|
132
|
+
const locate = (id: string): 'left' | 'right' | null =>
|
|
133
|
+
findInSidebars(id)?.area ?? null;
|
|
134
|
+
|
|
90
135
|
const captureWidget = (id: string): Widget | null => {
|
|
91
136
|
const cached = widgetCache.get(id);
|
|
92
137
|
if (cached && !cached.isDisposed) {
|
|
93
138
|
return cached;
|
|
94
139
|
}
|
|
95
|
-
const found =
|
|
140
|
+
const found = findInSidebars(id);
|
|
96
141
|
if (found) {
|
|
97
|
-
widgetCache.set(id, found);
|
|
142
|
+
widgetCache.set(id, found.widget);
|
|
98
143
|
}
|
|
99
|
-
return found;
|
|
144
|
+
return found?.widget ?? null;
|
|
100
145
|
};
|
|
101
146
|
|
|
102
147
|
let settings: ISettingRegistry.ISettings | null = null;
|
|
@@ -112,19 +157,23 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
112
157
|
const apply = (target: ITarget): void => {
|
|
113
158
|
const widget = captureWidget(target.id);
|
|
114
159
|
if (!widget) {
|
|
115
|
-
// The upstream widget hasn't been added yet (or the extension is
|
|
116
|
-
// disabled). Nothing to do — the next time `apply` runs and finds
|
|
117
|
-
// it, the preference will take effect.
|
|
118
160
|
return;
|
|
119
161
|
}
|
|
120
|
-
const
|
|
162
|
+
const area = locate(target.id);
|
|
163
|
+
if (!area && widget.parent !== null) {
|
|
164
|
+
// Attached outside the sidebars — leave it alone.
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
121
167
|
const wantPresent = readPreference(target);
|
|
122
|
-
if (wantPresent && !
|
|
123
|
-
labShell.add(widget, 'left', {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
168
|
+
if (wantPresent && !area) {
|
|
169
|
+
labShell.add(widget, hiddenFrom.get(target.id) ?? 'left', {
|
|
170
|
+
rank: target.rank
|
|
171
|
+
});
|
|
172
|
+
hiddenFrom.delete(target.id);
|
|
173
|
+
} else if (!wantPresent && area) {
|
|
174
|
+
hiddenFrom.set(target.id, area);
|
|
175
|
+
// Detaching makes the SideBarHandler remove the tab while the
|
|
176
|
+
// widget instance stays alive for a later re-add.
|
|
128
177
|
widget.parent = null;
|
|
129
178
|
}
|
|
130
179
|
};
|
|
@@ -139,14 +188,21 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
139
188
|
commands.addCommand(target.command, {
|
|
140
189
|
label: target.label(trans),
|
|
141
190
|
isToggled: () => readPreference(target),
|
|
191
|
+
isVisible: () => {
|
|
192
|
+
// List only tabs this plugin can manage: in a sidebar, or
|
|
193
|
+
// hidden by this plugin (`parent === null`).
|
|
194
|
+
const widget = captureWidget(target.id);
|
|
195
|
+
return (
|
|
196
|
+
widget !== null &&
|
|
197
|
+
(widget.parent === null || locate(target.id) !== null)
|
|
198
|
+
);
|
|
199
|
+
},
|
|
142
200
|
execute: async () => {
|
|
143
201
|
const next = !readPreference(target);
|
|
144
202
|
if (settings) {
|
|
145
203
|
try {
|
|
146
204
|
await settings.set(target.settingKey, next);
|
|
147
|
-
// `settings.changed`
|
|
148
|
-
// command-state refresh. Returning here keeps the toggle's
|
|
149
|
-
// single source of truth in the settings registry.
|
|
205
|
+
// `settings.changed` drives `apply` and the state refresh.
|
|
150
206
|
return;
|
|
151
207
|
} catch (reason) {
|
|
152
208
|
console.error(
|
|
@@ -155,9 +211,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
155
211
|
);
|
|
156
212
|
}
|
|
157
213
|
}
|
|
158
|
-
// No settings
|
|
159
|
-
// back to applying the change in-memory so the toggle still
|
|
160
|
-
// works for the current session.
|
|
214
|
+
// No settings (or the write failed): apply in-memory for this session.
|
|
161
215
|
apply(target);
|
|
162
216
|
commands.notifyCommandChanged(target.command);
|
|
163
217
|
}
|
|
@@ -179,11 +233,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
179
233
|
}
|
|
180
234
|
|
|
181
235
|
void app.restored.then(() => {
|
|
182
|
-
// Capture
|
|
183
|
-
// recoverable
|
|
184
|
-
// the targets that need to stay visible — by capturing every target
|
|
185
|
-
// here we keep the cache populated for both directions of the
|
|
186
|
-
// toggle.
|
|
236
|
+
// Capture every widget before applying, so tabs hidden on startup
|
|
237
|
+
// stay recoverable.
|
|
187
238
|
for (const target of TARGETS) {
|
|
188
239
|
captureWidget(target.id);
|
|
189
240
|
}
|
|
@@ -22,16 +22,28 @@ const OSC_RXVT_NOTIFY = 777;
|
|
|
22
22
|
|
|
23
23
|
const MAX_TEXT_LENGTH = 256;
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// JupyterLab's terminal keeps its xterm in a private `_term` field; structural
|
|
26
|
+
// types use its hooks without `@xterm/xterm`, going inert (warning) if renamed.
|
|
27
|
+
/**
|
|
28
|
+
* A handle returned by an xterm listener registration.
|
|
29
|
+
*/
|
|
30
30
|
interface IXtermDisposable {
|
|
31
|
+
/**
|
|
32
|
+
* Unregister the listener.
|
|
33
|
+
*/
|
|
31
34
|
dispose(): void;
|
|
32
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The subset of the xterm.js API used to observe bells and OSC sequences.
|
|
38
|
+
*/
|
|
33
39
|
interface IXtermTerminal {
|
|
40
|
+
/**
|
|
41
|
+
* Register a handler invoked when the terminal bell rings.
|
|
42
|
+
*/
|
|
34
43
|
onBell(handler: () => void): IXtermDisposable;
|
|
44
|
+
/**
|
|
45
|
+
* The escape-sequence parser used to register OSC handlers.
|
|
46
|
+
*/
|
|
35
47
|
parser: {
|
|
36
48
|
registerOscHandler(
|
|
37
49
|
ident: number,
|
|
@@ -39,8 +51,17 @@ interface IXtermTerminal {
|
|
|
39
51
|
): IXtermDisposable;
|
|
40
52
|
};
|
|
41
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* A JupyterLab terminal widget content with its private xterm exposed.
|
|
56
|
+
*/
|
|
42
57
|
interface ITerminalContentInternals extends ITerminal.ITerminal {
|
|
58
|
+
/**
|
|
59
|
+
* A promise resolving once the underlying xterm exists.
|
|
60
|
+
*/
|
|
43
61
|
ready: Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* The private xterm instance; `undefined` if upstream renames the field.
|
|
64
|
+
*/
|
|
44
65
|
_term?: IXtermTerminal;
|
|
45
66
|
}
|
|
46
67
|
|
|
@@ -48,26 +69,30 @@ type TerminalWidget = MainAreaWidget<ITerminal.ITerminal>;
|
|
|
48
69
|
|
|
49
70
|
// The renderer→main bridge the desktop shell injects on the lab window. Absent
|
|
50
71
|
// for pip-install users in a browser, who fall back to web Notifications.
|
|
72
|
+
/**
|
|
73
|
+
* The desktop notification API exposed on `window.xtralab`.
|
|
74
|
+
*/
|
|
51
75
|
interface IDesktopBridge {
|
|
76
|
+
/**
|
|
77
|
+
* Show a native notification, tagged with the emitting terminal session.
|
|
78
|
+
*/
|
|
52
79
|
notify?: (
|
|
53
80
|
title: string,
|
|
54
81
|
body: string,
|
|
55
82
|
session?: string
|
|
56
83
|
) => Promise<void> | void;
|
|
84
|
+
/**
|
|
85
|
+
* Register a callback invoked with the tagged terminal session name when
|
|
86
|
+
* the user clicks a notification.
|
|
87
|
+
*/
|
|
57
88
|
onFocusTerminal?: (callback: (session: string) => void) => void;
|
|
58
89
|
}
|
|
59
90
|
|
|
60
91
|
/**
|
|
61
|
-
* Turns the
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* A notification is suppressed while its terminal is the focused, active tab,
|
|
67
|
-
* and throttled per terminal. The desktop shell advertises
|
|
68
|
-
* `TERM_PROGRAM=iTerm.app` so agents emit OSC 9 in the first place, and the
|
|
69
|
-
* session name is forwarded so clicking a notification focuses the terminal
|
|
70
|
-
* that fired it.
|
|
92
|
+
* Turns the OSC 9 / OSC 777 / bell sequences agents emit into desktop
|
|
93
|
+
* notifications via `window.xtralab.notify` (desktop) or the web Notifications
|
|
94
|
+
* API — JupyterLab's xterm renders them but never forwards them to the OS.
|
|
95
|
+
* The desktop shell advertises `TERM_PROGRAM=iTerm.app` so agents emit OSC 9.
|
|
71
96
|
*/
|
|
72
97
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
73
98
|
id: PLUGIN_ID,
|
|
@@ -87,13 +112,9 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
87
112
|
let enabled = true;
|
|
88
113
|
let notifyOnBell = true;
|
|
89
114
|
|
|
90
|
-
// Per-widget throttle timestamps and xterm hooks. WeakMaps let disposed
|
|
91
|
-
// widgets be collected; hooks are torn down when a tab closes.
|
|
92
115
|
const lastNotified = new WeakMap<TerminalWidget, number>();
|
|
93
116
|
const hooks = new WeakMap<TerminalWidget, IXtermDisposable[]>();
|
|
94
117
|
|
|
95
|
-
// True when the user is already looking at this terminal, so a banner would
|
|
96
|
-
// just be noise.
|
|
97
118
|
const isActivelyViewing = (widget: TerminalWidget): boolean =>
|
|
98
119
|
document.hasFocus() && app.shell.currentWidget === widget;
|
|
99
120
|
|
|
@@ -107,7 +128,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
107
128
|
);
|
|
108
129
|
return;
|
|
109
130
|
}
|
|
110
|
-
// Plain-browser fallback (pip install): the Web Notifications API.
|
|
111
131
|
if (typeof Notification === 'undefined') {
|
|
112
132
|
return;
|
|
113
133
|
}
|
|
@@ -146,9 +166,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
146
166
|
widget.title.label || trans.__('Terminal');
|
|
147
167
|
|
|
148
168
|
const onOsc9 = (widget: TerminalWidget, data: string): boolean => {
|
|
149
|
-
// OSC 9 is overloaded: ConEmu/Windows Terminal use
|
|
150
|
-
//
|
|
151
|
-
// payload and leave it unconsumed rather than treat it as a notification.
|
|
169
|
+
// OSC 9 is overloaded: ConEmu/Windows Terminal use numeric subcommands
|
|
170
|
+
// (e.g. `9 ; 4` progress); leave those unconsumed.
|
|
152
171
|
if (/^\d+(;|$)/.test(data)) {
|
|
153
172
|
return false;
|
|
154
173
|
}
|
|
@@ -197,8 +216,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
197
216
|
);
|
|
198
217
|
return;
|
|
199
218
|
}
|
|
200
|
-
// Handlers are wrapped so a parse slip cannot break xterm's parser; an
|
|
201
|
-
// OSC handler returns whether it consumed the sequence.
|
|
202
219
|
const disposables: IXtermDisposable[] = [
|
|
203
220
|
term.onBell(() => guard(() => onBell(widget))),
|
|
204
221
|
term.parser.registerOscHandler(OSC_ITERM2_GROWL, data =>
|