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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JupyterFrontEnd,
|
|
3
|
+
JupyterFrontEndPlugin
|
|
4
|
+
} from '@jupyterlab/application';
|
|
5
|
+
import {
|
|
6
|
+
IMovableSectionDestination,
|
|
7
|
+
IMovableSectionRegistry
|
|
8
|
+
} from '@jupyterlab/apputils';
|
|
9
|
+
import {
|
|
10
|
+
ITranslator,
|
|
11
|
+
nullTranslator,
|
|
12
|
+
type TranslationBundle
|
|
13
|
+
} from '@jupyterlab/translation';
|
|
14
|
+
import { SidePanel, tableRowsIcon } from '@jupyterlab/ui-components';
|
|
15
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
16
|
+
import { AccordionPanel, Widget } from '@lumino/widgets';
|
|
17
|
+
|
|
18
|
+
const PLUGIN_ID = 'xtralab:custom-panel';
|
|
19
|
+
|
|
20
|
+
const CUSTOM_PANEL_ID = 'xtralab-custom-panel';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A sidebar panel holding only the sections moved in from other panels.
|
|
24
|
+
*/
|
|
25
|
+
class CustomPanel extends SidePanel implements IMovableSectionDestination {
|
|
26
|
+
constructor(options: { trans: TranslationBundle }) {
|
|
27
|
+
super();
|
|
28
|
+
this.id = CUSTOM_PANEL_ID;
|
|
29
|
+
this.title.icon = tableRowsIcon;
|
|
30
|
+
this.title.caption = options.trans.__('Custom Panel');
|
|
31
|
+
this.addClass('jp-xtralab-CustomPanel');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The accordion panel hosting the sections.
|
|
36
|
+
*/
|
|
37
|
+
get accordionPanel(): AccordionPanel {
|
|
38
|
+
return this.content as AccordionPanel;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The section widgets currently hosted by the panel.
|
|
43
|
+
*/
|
|
44
|
+
get sections(): ReadonlyArray<Widget> {
|
|
45
|
+
return this.content.widgets;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A signal emitted with the new count when the number of sections changes.
|
|
50
|
+
*/
|
|
51
|
+
get sectionCountChanged(): ISignal<this, number> {
|
|
52
|
+
return this._sectionCountChanged;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Add a section widget to the panel.
|
|
57
|
+
*/
|
|
58
|
+
addSection(widget: Widget): void {
|
|
59
|
+
this.addWidget(widget);
|
|
60
|
+
this._sectionCountChanged.emit(this.content.widgets.length);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Remove a section widget from the panel, if it is hosted here.
|
|
65
|
+
*/
|
|
66
|
+
removeSectionWidget(widget: Widget): void {
|
|
67
|
+
if (widget.parent !== this.content) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
widget.parent = null;
|
|
71
|
+
this._sectionCountChanged.emit(this.content.widgets.length);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private _sectionCountChanged = new Signal<this, number>(this);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Registers "Custom Panel", which takes a sidebar tab only while it hosts a
|
|
79
|
+
* section.
|
|
80
|
+
*/
|
|
81
|
+
const plugin: JupyterFrontEndPlugin<void> = {
|
|
82
|
+
id: PLUGIN_ID,
|
|
83
|
+
description:
|
|
84
|
+
'A user-assembled sidebar panel hosting sections moved in from other panels; hidden while empty.',
|
|
85
|
+
autoStart: true,
|
|
86
|
+
optional: [IMovableSectionRegistry, ITranslator],
|
|
87
|
+
activate: (
|
|
88
|
+
app: JupyterFrontEnd,
|
|
89
|
+
registry: IMovableSectionRegistry | null,
|
|
90
|
+
translator: ITranslator | null
|
|
91
|
+
): void => {
|
|
92
|
+
if (!registry) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
96
|
+
const panel = new CustomPanel({ trans });
|
|
97
|
+
|
|
98
|
+
// Only a user move should switch the sidebar, not the startup restoration.
|
|
99
|
+
let restored = false;
|
|
100
|
+
void app.restored.then(() => {
|
|
101
|
+
restored = true;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
panel.sectionCountChanged.connect((_, count) => {
|
|
105
|
+
if (count > 0 && !panel.parent) {
|
|
106
|
+
app.shell.add(panel, 'left', { rank: 3 });
|
|
107
|
+
if (restored) {
|
|
108
|
+
app.shell.activateById(panel.id);
|
|
109
|
+
}
|
|
110
|
+
} else if (count === 0 && panel.parent) {
|
|
111
|
+
panel.parent = null;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
registry.registerTarget(PLUGIN_ID, trans.__('Custom Panel'), panel);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export default plugin;
|
|
@@ -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
|
|
60
|
-
*
|
|
61
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
42
|
-
//
|
|
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
|
|
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
|
|
121
|
-
//
|
|
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
|
|
130
|
-
*
|
|
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
|
-
*
|
|
18
|
-
*
|
|
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`
|
|
80
|
-
//
|
|
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
|
|
90
|
-
* `
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
-
*
|
|
60
|
-
*
|
|
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
|
-
*
|
|
76
|
-
*
|
|
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
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
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
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
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
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
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
|
-
*
|
|
215
|
-
*
|
|
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
|
|
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
|
|
263
|
-
*
|
|
264
|
-
*
|
|
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,27 +479,33 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
|
|
|
496
479
|
}
|
|
497
480
|
});
|
|
498
481
|
|
|
499
|
-
//
|
|
500
|
-
//
|
|
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
|
|
508
|
-
//
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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.
|
|
492
|
+
const activateTreeHost = (): void => {
|
|
493
|
+
for (const area of ['left', 'right']) {
|
|
494
|
+
for (const widget of app.shell.widgets(area)) {
|
|
495
|
+
if (widget.node.contains(browser.sectionNode)) {
|
|
496
|
+
app.shell.activateById(widget.id);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
app.shell.activateById(FILE_BROWSER_ID);
|
|
502
|
+
};
|
|
503
|
+
|
|
515
504
|
commands.addCommand(CommandIDs.revealPath, {
|
|
516
505
|
label: trans.__('Reveal in File Browser'),
|
|
517
506
|
execute: (args: ReadonlyPartialJSONObject) => {
|
|
518
507
|
const path = (args.path as string | undefined) ?? '';
|
|
519
|
-
|
|
508
|
+
activateTreeHost();
|
|
520
509
|
if (path.length === 0) {
|
|
521
510
|
browser.scrollToRoot();
|
|
522
511
|
} else {
|
|
@@ -525,8 +514,6 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
|
|
|
525
514
|
}
|
|
526
515
|
});
|
|
527
516
|
|
|
528
|
-
// Reveal the resolved document in the tree browser via the reveal-path
|
|
529
|
-
// command, which surfaces the sidebar and scrolls to the target.
|
|
530
517
|
commands.addCommand(CommandIDs.revealInFileTree, {
|
|
531
518
|
label: trans.__('Show in File Tree'),
|
|
532
519
|
caption: trans.__('Show this file in the xtralab file browser'),
|
|
@@ -566,10 +553,8 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
|
|
|
566
553
|
commands.addCommand(CommandIDs.newLauncher, {
|
|
567
554
|
label: trans.__('New Launcher'),
|
|
568
555
|
caption: trans.__('Open a new launcher'),
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
// styling expects the raw `jp-icon3` paths from `addIcon` so it can
|
|
572
|
-
// 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.
|
|
573
558
|
icon: addIcon,
|
|
574
559
|
execute: () => {
|
|
575
560
|
const cwd = getWorkingDirectory(browser);
|
|
@@ -577,34 +562,24 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
|
|
|
577
562
|
}
|
|
578
563
|
});
|
|
579
564
|
|
|
580
|
-
// Re-evaluate enabled/visible state when the user changes the tree
|
|
581
|
-
// selection, in case a command's predicate depends on the selection.
|
|
582
565
|
browser.selectionChanged.connect(() => {
|
|
583
566
|
for (const id of Object.values(CommandIDs)) {
|
|
584
567
|
commands.notifyCommandChanged(id);
|
|
585
568
|
}
|
|
586
569
|
});
|
|
587
570
|
|
|
588
|
-
// This command's enabled state tracks the active widget, not the tree
|
|
589
|
-
// selection, so re-evaluate it on current-widget changes. `currentChanged`
|
|
590
|
-
// is optional on the shell interface.
|
|
591
571
|
const onCurrentChanged = (): void => {
|
|
592
572
|
commands.notifyCommandChanged(CommandIDs.revealInFileTree);
|
|
593
573
|
};
|
|
594
574
|
app.shell.currentChanged?.connect(onCurrentChanged);
|
|
595
575
|
|
|
596
|
-
// Also surface it in the palette (acts on the active editor); the file-tab
|
|
597
|
-
// context-menu entry is declared in `schema/plugin.json`.
|
|
598
576
|
const paletteItem = palette?.addItem({
|
|
599
577
|
command: CommandIDs.revealInFileTree,
|
|
600
578
|
category: trans.__('File Browser')
|
|
601
579
|
});
|
|
602
580
|
|
|
603
|
-
//
|
|
604
|
-
//
|
|
605
|
-
// schema also declares an empty submenu placeholder with id
|
|
606
|
-
// `OPEN_WITH_SUBMENU_ID`; we fill it on every open from the document
|
|
607
|
-
// 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.
|
|
608
583
|
const updateOpenWithMenu = makeOpenWithUpdater(app, browser);
|
|
609
584
|
app.contextMenu.opened.connect(updateOpenWithMenu);
|
|
610
585
|
|
|
@@ -617,9 +592,8 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
|
|
|
617
592
|
}
|
|
618
593
|
|
|
619
594
|
/**
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
* than commands.
|
|
595
|
+
* Toolbar item names, distinct from {@link CommandIDs}: the toolbar API
|
|
596
|
+
* uses opaque names rather than commands.
|
|
623
597
|
*/
|
|
624
598
|
export namespace ToolbarNames {
|
|
625
599
|
export const newLauncher = 'new-launcher';
|
|
@@ -630,10 +604,8 @@ export namespace ToolbarNames {
|
|
|
630
604
|
}
|
|
631
605
|
|
|
632
606
|
/**
|
|
633
|
-
* Populate the
|
|
634
|
-
*
|
|
635
|
-
* button, a refresh button, a collapse-all button, and the file filter
|
|
636
|
-
* toggle.
|
|
607
|
+
* Populate the toolbar with the buttons that mirror the default JupyterLab
|
|
608
|
+
* file browser.
|
|
637
609
|
*/
|
|
638
610
|
export function populateToolbar(opts: {
|
|
639
611
|
app: JupyterFrontEnd;
|