xtralab 0.11.0 → 0.12.0
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 +37 -229
- package/lib/agentSessions.d.ts +6 -2
- package/lib/askAgent/editorSelection.d.ts +53 -0
- package/lib/askAgent/editorSelection.js +95 -0
- package/lib/askAgent/icons.d.ts +7 -0
- package/lib/askAgent/icons.js +15 -0
- package/lib/askAgent/index.d.ts +32 -0
- package/lib/askAgent/index.js +741 -0
- package/lib/askAgent/popup.d.ts +83 -0
- package/lib/askAgent/popup.js +221 -0
- package/lib/askAgent/prompt.d.ts +24 -0
- package/lib/askAgent/prompt.js +116 -0
- package/lib/askAgent/queue.d.ts +93 -0
- package/lib/askAgent/queue.js +207 -0
- package/lib/askAgent/queuePanel.d.ts +76 -0
- package/lib/askAgent/queuePanel.js +188 -0
- package/lib/askAgent/targetPicker.d.ts +58 -0
- package/lib/askAgent/targetPicker.js +74 -0
- package/lib/askAgent/tokens.d.ts +124 -0
- package/lib/askAgent/tokens.js +13 -0
- package/lib/commandBar/index.d.ts +12 -8
- package/lib/commandBar/index.js +58 -41
- package/lib/editorBreadcrumbs/index.js +9 -4
- package/lib/editorBreadcrumbs/widget.d.ts +7 -7
- package/lib/editorBreadcrumbs/widget.js +6 -8
- package/lib/fileBrowser/commands.d.ts +2 -8
- package/lib/fileBrowser/commands.js +39 -30
- package/lib/fileBrowser/contents.d.ts +8 -3
- package/lib/fileBrowser/fileBrowser.d.ts +2 -1
- package/lib/fileBrowser/widget.d.ts +5 -9
- package/lib/fileBrowser/widget.js +1 -1
- package/lib/fileTypeIcons/index.js +5 -2
- package/lib/git/api.d.ts +0 -26
- package/lib/git/api.js +2 -44
- package/lib/git/askRequest.d.ts +23 -0
- package/lib/git/askRequest.js +90 -0
- package/lib/git/commands.d.ts +14 -2
- package/lib/git/commands.js +16 -13
- package/lib/git/diffModel.d.ts +1 -9
- package/lib/git/diffModel.js +1 -1
- package/lib/git/diffProvider.js +8 -3
- package/lib/git/diffSurface.d.ts +19 -2
- package/lib/git/diffSurface.js +64 -18
- package/lib/git/diffTheme.d.ts +0 -4
- package/lib/git/diffTheme.js +1 -1
- package/lib/git/diffWidget.d.ts +9 -0
- package/lib/git/diffWidget.js +38 -10
- package/lib/git/imageDiff.d.ts +17 -4
- package/lib/git/imageDiff.js +90 -30
- package/lib/git/index.js +7 -2
- package/lib/git/notebookDiff.d.ts +10 -67
- package/lib/git/notebookDiff.js +62 -69
- package/lib/git/tokens.d.ts +5 -2
- package/lib/highlight/index.js +12 -8
- package/lib/index.js +2 -0
- package/lib/launcher/agents.d.ts +11 -1
- package/lib/launcher/agents.js +26 -8
- package/lib/launcher/commands.d.ts +6 -2
- package/lib/launcher/dashboard.d.ts +8 -1
- package/lib/launcher/dashboard.js +39 -33
- package/lib/launcher/editorRegistry.js +5 -3
- package/lib/launcher/editors.d.ts +24 -7
- package/lib/launcher/editors.js +14 -5
- package/lib/launcher/icons.d.ts +0 -37
- package/lib/launcher/icons.js +11 -12
- package/lib/launcher/index.js +5 -2
- package/lib/launcher/invocation.d.ts +0 -15
- package/lib/launcher/invocation.js +1 -1
- package/lib/launcher/schemaDefaults.d.ts +3 -1
- package/lib/launcher/schemaDefaults.js +3 -1
- package/lib/mimeData.d.ts +7 -0
- package/lib/mimeData.js +16 -0
- package/lib/omnibox/index.d.ts +4 -0
- package/lib/omnibox/index.js +43 -2
- package/lib/omnibox/model.d.ts +43 -14
- package/lib/omnibox/model.js +117 -14
- package/lib/omnibox/recents.d.ts +65 -0
- package/lib/omnibox/recents.js +109 -0
- package/lib/omnibox/tokens.d.ts +6 -2
- package/lib/omnibox/widget.d.ts +23 -6
- package/lib/omnibox/widget.js +26 -7
- package/lib/showOutput/index.js +15 -18
- package/lib/terminals/agentTerminals.d.ts +63 -0
- package/lib/terminals/agentTerminals.js +157 -0
- package/lib/terminals/index.d.ts +7 -1
- package/lib/terminals/index.js +50 -30
- package/lib/terminals/model.d.ts +11 -2
- package/lib/terminals/model.js +27 -8
- package/lib/terminals/tokens.d.ts +66 -0
- package/lib/terminals/tokens.js +7 -0
- package/lib/terminals/widget.d.ts +3 -6
- package/lib/terminals/widget.js +1 -1
- package/lib/topBar/index.js +3 -3
- package/lib/walkthrough/index.js +14 -3
- package/lib/walkthrough/panel.d.ts +12 -4
- package/lib/walkthrough/panel.js +9 -3
- package/package.json +49 -51
- package/schema/command-bar.json +10 -0
- package/schema/launcher.json +4 -4
- package/schema/omnibox.json +15 -0
- package/src/agentSessions.ts +6 -2
- package/src/askAgent/editorSelection.ts +138 -0
- package/src/askAgent/icons.ts +16 -0
- package/src/askAgent/index.ts +915 -0
- package/src/askAgent/popup.tsx +407 -0
- package/src/askAgent/prompt.ts +144 -0
- package/src/askAgent/queue.ts +269 -0
- package/src/askAgent/queuePanel.tsx +418 -0
- package/src/askAgent/targetPicker.tsx +209 -0
- package/src/askAgent/tokens.ts +139 -0
- package/src/commandBar/index.ts +75 -49
- package/src/editorBreadcrumbs/index.ts +14 -4
- package/src/editorBreadcrumbs/widget.ts +11 -9
- package/src/fileBrowser/commands.ts +45 -31
- package/src/fileBrowser/contents.ts +7 -3
- package/src/fileBrowser/fileBrowser.tsx +1 -1
- package/src/fileBrowser/widget.tsx +5 -3
- package/src/fileTypeIcons/index.ts +6 -3
- package/src/git/api.ts +2 -61
- package/src/git/askRequest.ts +113 -0
- package/src/git/commands.ts +41 -14
- package/src/git/diffModel.ts +1 -1
- package/src/git/diffProvider.tsx +11 -3
- package/src/git/diffSurface.tsx +114 -22
- package/src/git/diffTheme.ts +1 -1
- package/src/git/diffWidget.tsx +54 -7
- package/src/git/imageDiff.tsx +169 -58
- package/src/git/index.ts +10 -2
- package/src/git/notebookDiff.tsx +91 -57
- package/src/git/tokens.ts +4 -2
- package/src/highlight/index.ts +12 -8
- package/src/index.ts +2 -0
- package/src/launcher/agents.ts +31 -7
- package/src/launcher/commands.ts +6 -2
- package/src/launcher/dashboard.tsx +86 -38
- package/src/launcher/editorRegistry.ts +10 -4
- package/src/launcher/editors.ts +34 -9
- package/src/launcher/icons.ts +11 -12
- package/src/launcher/index.ts +5 -2
- package/src/launcher/invocation.ts +1 -1
- package/src/launcher/schemaDefaults.ts +3 -1
- package/src/mimeData.ts +20 -0
- package/src/omnibox/index.ts +48 -1
- package/src/omnibox/model.ts +173 -33
- package/src/omnibox/recents.ts +155 -0
- package/src/omnibox/tokens.ts +6 -2
- package/src/omnibox/widget.tsx +54 -13
- package/src/showOutput/index.ts +20 -23
- package/src/terminals/agentTerminals.ts +238 -0
- package/src/terminals/index.ts +54 -30
- package/src/terminals/model.ts +28 -7
- package/src/terminals/tokens.ts +77 -0
- package/src/terminals/widget.tsx +4 -2
- package/src/topBar/index.ts +12 -6
- package/src/walkthrough/index.ts +19 -4
- package/src/walkthrough/panel.ts +15 -5
- package/style/askAgent.css +544 -0
- package/style/commandBar.css +12 -33
- package/style/git.css +72 -31
- package/style/index.css +1 -0
- package/style/index.js +2 -0
- package/style/topBar.css +4 -4
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Token } from '@lumino/coreutils';
|
|
2
|
+
/**
|
|
3
|
+
* The command id that opens the ask-agent popup for the current text-editor
|
|
4
|
+
* selection. Registered by `xtralab:ask-agent`; exposed here so menus and
|
|
5
|
+
* other plugins can reference it without importing the plugin internals.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ASK_AGENT_COMMAND = "xtralab:ask-agent";
|
|
8
|
+
/**
|
|
9
|
+
* A code location (and optionally the selected source text) that a prompt
|
|
10
|
+
* should point an agent at. The structured fields are composed into the
|
|
11
|
+
* final prompt by the ask-agent plugin so every entry point — editor
|
|
12
|
+
* selection, diff line selection — produces consistently worded prompts.
|
|
13
|
+
*/
|
|
14
|
+
export interface IAskAgentContext {
|
|
15
|
+
/**
|
|
16
|
+
* Path of the file the selection belongs to, relative to {@link cwd} (or
|
|
17
|
+
* to the server root when no cwd is given) so the launched agent can open
|
|
18
|
+
* it directly.
|
|
19
|
+
*/
|
|
20
|
+
path: string;
|
|
21
|
+
/**
|
|
22
|
+
* Server-relative directory the agent's terminal starts in. Omit to start
|
|
23
|
+
* in the server root. Diff selections pass the git repository root here so
|
|
24
|
+
* the agent can run git commands right away.
|
|
25
|
+
*/
|
|
26
|
+
cwd?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The notebook cell the selection lives in, when {@link path} is a
|
|
29
|
+
* notebook. `index` is the cell's 0-based position in the nbformat
|
|
30
|
+
* `cells` array; `type` is the nbformat cell type (`code`, `markdown`,
|
|
31
|
+
* `raw`). When set, {@link startLine}/{@link endLine} count within the
|
|
32
|
+
* cell's source rather than within a file.
|
|
33
|
+
*/
|
|
34
|
+
cell?: {
|
|
35
|
+
index: number;
|
|
36
|
+
type: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* First selected line, 1-indexed and inclusive. Omit when no line range is
|
|
40
|
+
* known (the prompt then only references the file).
|
|
41
|
+
*/
|
|
42
|
+
startLine?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Last selected line, 1-indexed and inclusive. Defaults to
|
|
45
|
+
* {@link startLine} when omitted.
|
|
46
|
+
*/
|
|
47
|
+
endLine?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Whether {@link startLine}/{@link endLine} index the file's current
|
|
50
|
+
* working-tree content. `false` for diff selections taken from another
|
|
51
|
+
* revision (the old side, or a challenger that is not the working tree),
|
|
52
|
+
* whose numbers must not be used to highlight the working file. Omitted
|
|
53
|
+
* means `true`.
|
|
54
|
+
*/
|
|
55
|
+
linesInWorkingFile?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The selected source text. May be empty when only a location is known;
|
|
58
|
+
* the prompt then relies on the path and line range alone.
|
|
59
|
+
*/
|
|
60
|
+
text: string;
|
|
61
|
+
/**
|
|
62
|
+
* Extra agent-facing locator appended after the line range in the prompt,
|
|
63
|
+
* e.g. `the old side (INDEX) of the git diff`. Written in English because
|
|
64
|
+
* it is consumed by the agent, not shown in the UI.
|
|
65
|
+
*/
|
|
66
|
+
location?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Short user-facing tag shown in the popup header next to the file name,
|
|
69
|
+
* e.g. "old version". Translated, unlike {@link location}. Omit when the
|
|
70
|
+
* range reads naturally as the file's current content.
|
|
71
|
+
*/
|
|
72
|
+
note?: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Where a submitted prompt goes: a fresh terminal started with the chosen
|
|
76
|
+
* agent's command, or an existing terminal session whose running agent
|
|
77
|
+
* receives the prompt in its input box (queued by the agent itself when it
|
|
78
|
+
* is busy).
|
|
79
|
+
*/
|
|
80
|
+
export type AskAgentTarget = {
|
|
81
|
+
kind: 'new';
|
|
82
|
+
agentId: string;
|
|
83
|
+
} | {
|
|
84
|
+
kind: 'session';
|
|
85
|
+
name: string;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* A request to open the ask-agent popup: what code to prompt about and
|
|
89
|
+
* where to place the popup on screen.
|
|
90
|
+
*/
|
|
91
|
+
export interface IAskAgentRequest {
|
|
92
|
+
/**
|
|
93
|
+
* The code location and selection the prompt is about.
|
|
94
|
+
*/
|
|
95
|
+
context: IAskAgentContext;
|
|
96
|
+
/**
|
|
97
|
+
* Viewport rectangle the popup anchors to (typically the selection end or
|
|
98
|
+
* the button that was clicked). `null` positions the popup near the top
|
|
99
|
+
* center of the viewport.
|
|
100
|
+
*/
|
|
101
|
+
anchor: DOMRect | null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The ask-agent popup: a small floating prompt box that sends the given
|
|
105
|
+
* code selection, plus the user's typed instruction, to one of the
|
|
106
|
+
* configured coding agents — in a fresh terminal, or into an agent already
|
|
107
|
+
* running in an existing one.
|
|
108
|
+
*/
|
|
109
|
+
export interface IAskAgent {
|
|
110
|
+
/**
|
|
111
|
+
* Open the popup for `request`, replacing any popup already open.
|
|
112
|
+
*/
|
|
113
|
+
open(request: IAskAgentRequest): void;
|
|
114
|
+
/**
|
|
115
|
+
* Close the popup if it is open.
|
|
116
|
+
*/
|
|
117
|
+
close(): void;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* DI token for {@link IAskAgent}. Provided by `xtralab:ask-agent` and
|
|
121
|
+
* consumed — optionally, so diffs still render when the plugin is disabled —
|
|
122
|
+
* by the git diff plugins to offer "ask an agent about these lines".
|
|
123
|
+
*/
|
|
124
|
+
export declare const IAskAgent: Token<IAskAgent>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Token } from '@lumino/coreutils';
|
|
2
|
+
/**
|
|
3
|
+
* The command id that opens the ask-agent popup for the current text-editor
|
|
4
|
+
* selection. Registered by `xtralab:ask-agent`; exposed here so menus and
|
|
5
|
+
* other plugins can reference it without importing the plugin internals.
|
|
6
|
+
*/
|
|
7
|
+
export const ASK_AGENT_COMMAND = 'xtralab:ask-agent';
|
|
8
|
+
/**
|
|
9
|
+
* DI token for {@link IAskAgent}. Provided by `xtralab:ask-agent` and
|
|
10
|
+
* consumed — optionally, so diffs still render when the plugin is disabled —
|
|
11
|
+
* by the git diff plugins to offer "ask an agent about these lines".
|
|
12
|
+
*/
|
|
13
|
+
export const IAskAgent = new Token('xtralab:IAskAgent', 'A floating prompt box that sends the current code selection to a coding agent running in a terminal.');
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* opens the omnibox — a launcher overlay that fuzzy-searches files
|
|
5
|
-
* commands and routes a typed prompt to an agent
|
|
6
|
-
* and the menu bar sit to its left, the trailing sidebar toggle to its right;
|
|
7
|
-
* the bar is centered over the whole panel as an overlay, so it stays put
|
|
8
|
-
* regardless of their widths).
|
|
3
|
+
* Contribute a search-bar-styled command bar to JupyterLab's top bar toolbar.
|
|
4
|
+
* Clicking it opens the omnibox — a launcher overlay that fuzzy-searches files
|
|
5
|
+
* and commands and routes a typed prompt to an agent.
|
|
9
6
|
*
|
|
10
|
-
* The
|
|
11
|
-
*
|
|
7
|
+
* The pill is a regular settings-driven toolbar item: this plugin registers a
|
|
8
|
+
* widget factory for it on the `IToolbarWidgetRegistry` and declares its rank
|
|
9
|
+
* in schema/command-bar.json, so users can move or disable it from the Top
|
|
10
|
+
* Bar settings like any other toolbar item.
|
|
11
|
+
*
|
|
12
|
+
* The factory is only registered when the omnibox is available — the
|
|
13
|
+
* `IOmnibox` token is provided by `xtralab:omnibox` — so the pill never
|
|
14
|
+
* appears with nothing to open (without a factory, the toolbar item resolves
|
|
15
|
+
* to an empty command button that renders nothing).
|
|
12
16
|
*/
|
|
13
17
|
declare const plugin: JupyterFrontEndPlugin<void>;
|
|
14
18
|
export default plugin;
|
package/lib/commandBar/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IToolbarWidgetRegistry } from '@jupyterlab/apputils';
|
|
2
2
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
3
3
|
import { searchIcon } from '@jupyterlab/ui-components';
|
|
4
4
|
import { CommandRegistry } from '@lumino/commands';
|
|
@@ -6,15 +6,22 @@ import { Widget } from '@lumino/widgets';
|
|
|
6
6
|
import { IOmnibox, OMNIBOX_OPEN_COMMAND } from '../omnibox/tokens';
|
|
7
7
|
const PLUGIN_ID = 'xtralab:command-bar';
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the
|
|
9
|
+
* Factory name of JupyterLab's settings-driven top bar toolbar (`#jp-top-bar`,
|
|
10
|
+
* built by `@jupyterlab/application-extension:top-bar`).
|
|
11
|
+
*/
|
|
12
|
+
const TOPBAR_FACTORY = 'TopBar';
|
|
13
|
+
/**
|
|
14
|
+
* Name of the pill item within that toolbar. Its placement is declared under
|
|
15
|
+
* `jupyter.lab.toolbars` in this plugin's settings schema
|
|
16
|
+
* (schema/command-bar.json): the core toolbar ships a spacer at rank 50, so
|
|
17
|
+
* the pill's higher rank lands it at the trailing end of the toolbar, next to
|
|
18
|
+
* the right-sidebar toggle button.
|
|
19
|
+
*/
|
|
20
|
+
const ITEM_NAME = 'omnibox';
|
|
21
|
+
/**
|
|
22
|
+
* A search-bar-styled launcher pill. It holds no text input of its own:
|
|
23
|
+
* clicking it (or pressing Enter/Space while it is focused) runs `onActivate`,
|
|
24
|
+
* which opens the omnibox.
|
|
18
25
|
*/
|
|
19
26
|
class CommandBar extends Widget {
|
|
20
27
|
constructor(options) {
|
|
@@ -30,9 +37,9 @@ class CommandBar extends Widget {
|
|
|
30
37
|
}
|
|
31
38
|
}
|
|
32
39
|
onAfterAttach() {
|
|
33
|
-
// The
|
|
34
|
-
//
|
|
35
|
-
//
|
|
40
|
+
// The click bubbles from the inner pill button up to the root node
|
|
41
|
+
// listened on here; keyboard Enter/Space on the focused button raises the
|
|
42
|
+
// same synthetic click.
|
|
36
43
|
this.node.addEventListener('click', this);
|
|
37
44
|
}
|
|
38
45
|
onBeforeDetach() {
|
|
@@ -41,6 +48,10 @@ class CommandBar extends Widget {
|
|
|
41
48
|
}
|
|
42
49
|
(function (CommandBar) {
|
|
43
50
|
function createNode(label, caption, shortcut) {
|
|
51
|
+
// The root is a plain wrapper: as a toolbar item it receives the core
|
|
52
|
+
// `.jp-Toolbar > .jp-Toolbar-item` sizing (full height, centered flex),
|
|
53
|
+
// which would otherwise stretch the pill itself; the button inside keeps
|
|
54
|
+
// its own compact pill height.
|
|
44
55
|
const wrapper = document.createElement('div');
|
|
45
56
|
const button = document.createElement('button');
|
|
46
57
|
button.type = 'button';
|
|
@@ -70,45 +81,51 @@ class CommandBar extends Widget {
|
|
|
70
81
|
CommandBar.createNode = createNode;
|
|
71
82
|
})(CommandBar || (CommandBar = {}));
|
|
72
83
|
/**
|
|
73
|
-
*
|
|
74
|
-
* opens the omnibox — a launcher overlay that fuzzy-searches files
|
|
75
|
-
* commands and routes a typed prompt to an agent
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
84
|
+
* Contribute a search-bar-styled command bar to JupyterLab's top bar toolbar.
|
|
85
|
+
* Clicking it opens the omnibox — a launcher overlay that fuzzy-searches files
|
|
86
|
+
* and commands and routes a typed prompt to an agent.
|
|
87
|
+
*
|
|
88
|
+
* The pill is a regular settings-driven toolbar item: this plugin registers a
|
|
89
|
+
* widget factory for it on the `IToolbarWidgetRegistry` and declares its rank
|
|
90
|
+
* in schema/command-bar.json, so users can move or disable it from the Top
|
|
91
|
+
* Bar settings like any other toolbar item.
|
|
79
92
|
*
|
|
80
|
-
* The
|
|
81
|
-
* is provided by `xtralab:omnibox` — so
|
|
93
|
+
* The factory is only registered when the omnibox is available — the
|
|
94
|
+
* `IOmnibox` token is provided by `xtralab:omnibox` — so the pill never
|
|
95
|
+
* appears with nothing to open (without a factory, the toolbar item resolves
|
|
96
|
+
* to an empty command button that renders nothing).
|
|
82
97
|
*/
|
|
83
98
|
const plugin = {
|
|
84
99
|
id: PLUGIN_ID,
|
|
85
|
-
description: 'Add a
|
|
100
|
+
description: 'Add a command bar to the top bar toolbar that opens the omnibox.',
|
|
86
101
|
autoStart: true,
|
|
87
|
-
requires: [
|
|
102
|
+
requires: [IToolbarWidgetRegistry],
|
|
88
103
|
optional: [IOmnibox, ITranslator],
|
|
89
|
-
activate: (app,
|
|
104
|
+
activate: (app, toolbarRegistry, omnibox, translator) => {
|
|
90
105
|
if (!omnibox) {
|
|
91
106
|
return;
|
|
92
107
|
}
|
|
93
108
|
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
toolbarRegistry.addFactory(TOPBAR_FACTORY, ITEM_NAME, () => {
|
|
110
|
+
// Show the omnibox's keyboard shortcut as a hint in the pill, derived
|
|
111
|
+
// from the live binding (registered by xtralab:omnibox, which activates
|
|
112
|
+
// first as the IOmnibox provider) so it stays correct and uses the
|
|
113
|
+
// platform's modifier symbols. Empty when no binding is registered.
|
|
114
|
+
const binding = app.commands.keyBindings.find(keyBinding => keyBinding.command === OMNIBOX_OPEN_COMMAND);
|
|
115
|
+
const shortcut = binding
|
|
116
|
+
? CommandRegistry.formatKeystroke(binding.keys)
|
|
117
|
+
: '';
|
|
118
|
+
const widget = new CommandBar({
|
|
119
|
+
label: trans.__('Search…'),
|
|
120
|
+
caption: trans.__('Search files and commands, or ask an agent'),
|
|
121
|
+
shortcut,
|
|
122
|
+
onActivate: () => {
|
|
123
|
+
omnibox.open();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
widget.id = 'xtralab-command-bar';
|
|
127
|
+
return widget;
|
|
109
128
|
});
|
|
110
|
-
widget.id = 'xtralab-command-bar';
|
|
111
|
-
labShell.add(widget, 'top', { rank: 500 });
|
|
112
129
|
}
|
|
113
130
|
};
|
|
114
131
|
export default plugin;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
1
2
|
import { DisposableDelegate } from '@lumino/disposable';
|
|
2
3
|
import { EditorBreadcrumbs } from './widget';
|
|
3
4
|
const PLUGIN_ID = 'xtralab:editor-breadcrumbs';
|
|
4
5
|
const EDITOR_FACTORY = 'Editor';
|
|
5
6
|
const TOOLBAR_ITEM_NAME = 'xtralab-editor-breadcrumbs';
|
|
6
7
|
class EditorBreadcrumbsExtension {
|
|
7
|
-
constructor(commands) {
|
|
8
|
+
constructor(commands, trans) {
|
|
8
9
|
this._commands = commands;
|
|
10
|
+
this._trans = trans;
|
|
9
11
|
}
|
|
10
12
|
createNew(widget, context) {
|
|
11
13
|
const breadcrumbs = new EditorBreadcrumbs({
|
|
12
14
|
context,
|
|
13
|
-
commands: this._commands
|
|
15
|
+
commands: this._commands,
|
|
16
|
+
trans: this._trans
|
|
14
17
|
});
|
|
15
18
|
const added = widget.toolbar.insertItem(0, TOOLBAR_ITEM_NAME, breadcrumbs);
|
|
16
19
|
if (!added) {
|
|
@@ -34,8 +37,10 @@ const plugin = {
|
|
|
34
37
|
id: PLUGIN_ID,
|
|
35
38
|
description: 'Display the active file path in text editor toolbars.',
|
|
36
39
|
autoStart: true,
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
optional: [ITranslator],
|
|
41
|
+
activate: (app, translator) => {
|
|
42
|
+
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
|
|
43
|
+
app.docRegistry.addWidgetExtension(EDITOR_FACTORY, new EditorBreadcrumbsExtension(app.commands, trans));
|
|
39
44
|
}
|
|
40
45
|
};
|
|
41
46
|
export default plugin;
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import type { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
2
|
+
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
2
3
|
import type { CommandRegistry } from '@lumino/commands';
|
|
3
4
|
import { Widget } from '@lumino/widgets';
|
|
4
|
-
|
|
5
|
+
interface IEditorBreadcrumbsOptions {
|
|
5
6
|
context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
|
|
6
7
|
commands: CommandRegistry;
|
|
8
|
+
trans: TranslationBundle;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
|
-
* Read-only breadcrumb path for file editor toolbars
|
|
10
|
-
*
|
|
11
|
-
* The upstream `@jupyterlab/filebrowser` BreadCrumbs widget is coupled to
|
|
12
|
-
* FileBrowserModel navigation, drag/drop, and path editing. If that code
|
|
13
|
-
* becomes reusable upstream, replace this local display-only renderer with
|
|
14
|
-
* the shared implementation.
|
|
11
|
+
* Read-only breadcrumb path for file editor toolbars: renders the open
|
|
12
|
+
* file's path as clickable segments, each dispatching the reveal command.
|
|
15
13
|
*/
|
|
16
14
|
export declare class EditorBreadcrumbs extends Widget {
|
|
17
15
|
constructor(options: IEditorBreadcrumbsOptions);
|
|
@@ -29,6 +27,8 @@ export declare class EditorBreadcrumbs extends Widget {
|
|
|
29
27
|
private _createSeparator;
|
|
30
28
|
private _context;
|
|
31
29
|
private _commands;
|
|
30
|
+
private _trans;
|
|
32
31
|
private _container;
|
|
33
32
|
private _content;
|
|
34
33
|
}
|
|
34
|
+
export {};
|
|
@@ -15,20 +15,17 @@ const SEPARATOR_CLASS = 'jp-BreadCrumbs-separator';
|
|
|
15
15
|
*/
|
|
16
16
|
const REVEAL_COMMAND = 'xtralab:reveal-path';
|
|
17
17
|
/**
|
|
18
|
-
* Read-only breadcrumb path for file editor toolbars
|
|
19
|
-
*
|
|
20
|
-
* The upstream `@jupyterlab/filebrowser` BreadCrumbs widget is coupled to
|
|
21
|
-
* FileBrowserModel navigation, drag/drop, and path editing. If that code
|
|
22
|
-
* becomes reusable upstream, replace this local display-only renderer with
|
|
23
|
-
* the shared implementation.
|
|
18
|
+
* Read-only breadcrumb path for file editor toolbars: renders the open
|
|
19
|
+
* file's path as clickable segments, each dispatching the reveal command.
|
|
24
20
|
*/
|
|
25
21
|
export class EditorBreadcrumbs extends Widget {
|
|
26
22
|
constructor(options) {
|
|
27
23
|
super({ node: document.createElement('nav') });
|
|
28
24
|
this._context = options.context;
|
|
29
25
|
this._commands = options.commands;
|
|
26
|
+
this._trans = options.trans;
|
|
30
27
|
this.addClass(EDITOR_BREADCRUMBS_CLASS);
|
|
31
|
-
this.node.setAttribute('aria-label', 'Editor file path');
|
|
28
|
+
this.node.setAttribute('aria-label', this._trans.__('Editor file path'));
|
|
32
29
|
this._container = document.createElement('span');
|
|
33
30
|
this._container.className = BREADCRUMBS_CONTAINER_CLASS;
|
|
34
31
|
this._content = document.createElement('span');
|
|
@@ -85,7 +82,8 @@ export class EditorBreadcrumbs extends Widget {
|
|
|
85
82
|
const item = rootIcon.element({
|
|
86
83
|
className: ROOT_CLASS,
|
|
87
84
|
tag: 'span',
|
|
88
|
-
title: PageConfig.getOption('serverRoot') ||
|
|
85
|
+
title: PageConfig.getOption('serverRoot') ||
|
|
86
|
+
this._trans.__('Jupyter Server Root'),
|
|
89
87
|
stylesheet: 'breadCrumb'
|
|
90
88
|
});
|
|
91
89
|
item.dataset.path = '/';
|
|
@@ -25,14 +25,7 @@ export declare namespace CommandIDs {
|
|
|
25
25
|
const revealPath = "xtralab:reveal-path";
|
|
26
26
|
const revealInFileTree = "xtralab:reveal-in-file-tree";
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
* Submenu id used to host the dynamically populated "Open With" entries.
|
|
30
|
-
* Distinct from the core file browser's `jp-contextmenu-open-with` so the
|
|
31
|
-
* core's populator does not try to fill ours with items derived from the
|
|
32
|
-
* default file browser's selection.
|
|
33
|
-
*/
|
|
34
|
-
export declare const OPEN_WITH_SUBMENU_ID = "jp-contextmenu-xtralab-open-with";
|
|
35
|
-
export interface IRegisterCommandsOptions {
|
|
28
|
+
interface IRegisterCommandsOptions {
|
|
36
29
|
app: JupyterFrontEnd;
|
|
37
30
|
browser: IXtralabFileBrowser;
|
|
38
31
|
docManager: IDocumentManager;
|
|
@@ -70,3 +63,4 @@ export declare function populateToolbar(opts: {
|
|
|
70
63
|
app: JupyterFrontEnd;
|
|
71
64
|
browser: IXtralabFileBrowser;
|
|
72
65
|
}): void;
|
|
66
|
+
export {};
|
|
@@ -33,7 +33,7 @@ export var CommandIDs;
|
|
|
33
33
|
* core's populator does not try to fill ours with items derived from the
|
|
34
34
|
* default file browser's selection.
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
const OPEN_WITH_SUBMENU_ID = 'jp-contextmenu-xtralab-open-with';
|
|
37
37
|
/**
|
|
38
38
|
* Resolve the path of the item the user right-clicked. We prefer the
|
|
39
39
|
* contextmenu event's target — that is how `app.contextMenu` resolves which
|
|
@@ -82,7 +82,9 @@ function getOpenPaths(app, browser) {
|
|
|
82
82
|
}
|
|
83
83
|
return candidates.filter(path => !path.endsWith('/'));
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* True iff there is an actionable target for a command on the right-click.
|
|
87
|
+
*/
|
|
86
88
|
function hasTarget(app, browser) {
|
|
87
89
|
return getTargetPath(app, browser) !== undefined;
|
|
88
90
|
}
|
|
@@ -193,7 +195,10 @@ export function registerCommands(opts) {
|
|
|
193
195
|
const { commands } = app;
|
|
194
196
|
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
|
|
195
197
|
commands.addCommand(CommandIDs.open, {
|
|
196
|
-
label: args => {
|
|
198
|
+
label: args => {
|
|
199
|
+
var _a, _b;
|
|
200
|
+
return ((_b = (_a = args.label) !== null && _a !== void 0 ? _a : args.factory) !== null && _b !== void 0 ? _b : trans.__('Open'));
|
|
201
|
+
},
|
|
197
202
|
icon: args => {
|
|
198
203
|
var _a;
|
|
199
204
|
const factoryName = args.factory;
|
|
@@ -219,7 +224,7 @@ export function registerCommands(opts) {
|
|
|
219
224
|
}
|
|
220
225
|
});
|
|
221
226
|
commands.addCommand(CommandIDs.openBrowserTab, {
|
|
222
|
-
label: 'Open in New Browser Tab',
|
|
227
|
+
label: trans.__('Open in New Browser Tab'),
|
|
223
228
|
icon: fileIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
224
229
|
isEnabled: () => getTargetKind(app) === 'file',
|
|
225
230
|
isVisible: () => getTargetKind(app) === 'file',
|
|
@@ -234,7 +239,7 @@ export function registerCommands(opts) {
|
|
|
234
239
|
}
|
|
235
240
|
});
|
|
236
241
|
commands.addCommand(CommandIDs.rename, {
|
|
237
|
-
label: 'Rename…',
|
|
242
|
+
label: trans.__('Rename…'),
|
|
238
243
|
icon: editIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
239
244
|
mnemonic: 0,
|
|
240
245
|
isEnabled: () => hasTarget(app, browser),
|
|
@@ -247,10 +252,10 @@ export function registerCommands(opts) {
|
|
|
247
252
|
const serverPath = toServerPath(targetPath);
|
|
248
253
|
const oldName = PathExt.basename(serverPath);
|
|
249
254
|
const result = await InputDialog.getText({
|
|
250
|
-
title: 'Rename',
|
|
251
|
-
label: 'Enter a new name',
|
|
255
|
+
title: trans.__('Rename'),
|
|
256
|
+
label: trans.__('Enter a new name'),
|
|
252
257
|
text: oldName,
|
|
253
|
-
okLabel: 'Rename'
|
|
258
|
+
okLabel: trans.__('Rename')
|
|
254
259
|
});
|
|
255
260
|
const newName = (_a = result.value) === null || _a === void 0 ? void 0 : _a.trim();
|
|
256
261
|
if (result.button.accept !== true ||
|
|
@@ -265,14 +270,14 @@ export function registerCommands(opts) {
|
|
|
265
270
|
browser.refresh();
|
|
266
271
|
}
|
|
267
272
|
catch (err) {
|
|
268
|
-
await showErrorMessage('Rename failed', err);
|
|
273
|
+
await showErrorMessage(trans.__('Rename failed'), err);
|
|
269
274
|
}
|
|
270
275
|
}
|
|
271
276
|
});
|
|
272
277
|
commands.addCommand(CommandIDs.del, {
|
|
273
278
|
label: () => PageConfig.getOption('delete_to_trash') === 'true'
|
|
274
|
-
? 'Move to Trash'
|
|
275
|
-
: 'Delete',
|
|
279
|
+
? trans.__('Move to Trash')
|
|
280
|
+
: trans.__('Delete'),
|
|
276
281
|
icon: closeIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
277
282
|
mnemonic: 0,
|
|
278
283
|
isEnabled: () => hasTarget(app, browser),
|
|
@@ -284,11 +289,15 @@ export function registerCommands(opts) {
|
|
|
284
289
|
const serverPath = toServerPath(targetPath);
|
|
285
290
|
const trashing = PageConfig.getOption('delete_to_trash') === 'true';
|
|
286
291
|
const result = await showDialog({
|
|
287
|
-
title: trashing ? 'Move to Trash' : 'Delete',
|
|
288
|
-
body:
|
|
292
|
+
title: trashing ? trans.__('Move to Trash') : trans.__('Delete'),
|
|
293
|
+
body: trashing
|
|
294
|
+
? trans.__('Are you sure you want to move to trash: %1?', serverPath)
|
|
295
|
+
: trans.__('Are you sure you want to permanently delete: %1?', serverPath),
|
|
289
296
|
buttons: [
|
|
290
297
|
Dialog.cancelButton(),
|
|
291
|
-
Dialog.warnButton({
|
|
298
|
+
Dialog.warnButton({
|
|
299
|
+
label: trashing ? trans.__('Move to Trash') : trans.__('Delete')
|
|
300
|
+
})
|
|
292
301
|
]
|
|
293
302
|
});
|
|
294
303
|
if (result.button.accept !== true) {
|
|
@@ -299,12 +308,12 @@ export function registerCommands(opts) {
|
|
|
299
308
|
browser.refresh();
|
|
300
309
|
}
|
|
301
310
|
catch (err) {
|
|
302
|
-
await showErrorMessage('Delete failed', err);
|
|
311
|
+
await showErrorMessage(trans.__('Delete failed'), err);
|
|
303
312
|
}
|
|
304
313
|
}
|
|
305
314
|
});
|
|
306
315
|
commands.addCommand(CommandIDs.duplicate, {
|
|
307
|
-
label: 'Duplicate',
|
|
316
|
+
label: trans.__('Duplicate'),
|
|
308
317
|
icon: copyIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
309
318
|
isEnabled: () => getTargetKind(app) === 'file',
|
|
310
319
|
isVisible: () => getTargetKind(app) === 'file',
|
|
@@ -320,12 +329,12 @@ export function registerCommands(opts) {
|
|
|
320
329
|
browser.notifyPathAdded(toCanonicalPath(created));
|
|
321
330
|
}
|
|
322
331
|
catch (err) {
|
|
323
|
-
await showErrorMessage('Duplicate failed', err);
|
|
332
|
+
await showErrorMessage(trans.__('Duplicate failed'), err);
|
|
324
333
|
}
|
|
325
334
|
}
|
|
326
335
|
});
|
|
327
336
|
commands.addCommand(CommandIDs.copyPath, {
|
|
328
|
-
label: 'Copy Path',
|
|
337
|
+
label: trans.__('Copy Path'),
|
|
329
338
|
icon: fileIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
330
339
|
isEnabled: () => hasTarget(app, browser),
|
|
331
340
|
execute: async () => {
|
|
@@ -344,7 +353,7 @@ export function registerCommands(opts) {
|
|
|
344
353
|
}
|
|
345
354
|
});
|
|
346
355
|
commands.addCommand(CommandIDs.download, {
|
|
347
|
-
label: 'Download',
|
|
356
|
+
label: trans.__('Download'),
|
|
348
357
|
icon: downloadIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
349
358
|
isEnabled: () => getTargetKind(app) === 'file',
|
|
350
359
|
isVisible: () => getTargetKind(app) === 'file',
|
|
@@ -364,21 +373,21 @@ export function registerCommands(opts) {
|
|
|
364
373
|
document.body.removeChild(anchor);
|
|
365
374
|
}
|
|
366
375
|
catch (err) {
|
|
367
|
-
await showErrorMessage('Download failed', err);
|
|
376
|
+
await showErrorMessage(trans.__('Download failed'), err);
|
|
368
377
|
}
|
|
369
378
|
}
|
|
370
379
|
});
|
|
371
380
|
commands.addCommand(CommandIDs.refresh, {
|
|
372
|
-
label: 'Refresh File List',
|
|
373
|
-
caption: 'Refresh the file browser',
|
|
381
|
+
label: trans.__('Refresh File List'),
|
|
382
|
+
caption: trans.__('Refresh the file browser'),
|
|
374
383
|
icon: refreshIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
375
384
|
execute: () => {
|
|
376
385
|
browser.refresh();
|
|
377
386
|
}
|
|
378
387
|
});
|
|
379
388
|
commands.addCommand(CommandIDs.collapseAll, {
|
|
380
|
-
label: 'Collapse All Folders',
|
|
381
|
-
caption: 'Collapse all folders in the file browser',
|
|
389
|
+
label: trans.__('Collapse All Folders'),
|
|
390
|
+
caption: trans.__('Collapse all folders in the file browser'),
|
|
382
391
|
icon: collapseAllIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
383
392
|
execute: () => {
|
|
384
393
|
browser.collapseAll();
|
|
@@ -393,7 +402,7 @@ export function registerCommands(opts) {
|
|
|
393
402
|
// there is no tree row for the root itself, so the browser is asked
|
|
394
403
|
// to scroll back to the top and clear its selection instead.
|
|
395
404
|
commands.addCommand(CommandIDs.revealPath, {
|
|
396
|
-
label: 'Reveal in File Browser',
|
|
405
|
+
label: trans.__('Reveal in File Browser'),
|
|
397
406
|
execute: (args) => {
|
|
398
407
|
var _a;
|
|
399
408
|
const path = (_a = args.path) !== null && _a !== void 0 ? _a : '';
|
|
@@ -422,8 +431,8 @@ export function registerCommands(opts) {
|
|
|
422
431
|
}
|
|
423
432
|
});
|
|
424
433
|
commands.addCommand(CommandIDs.createNewDirectory, {
|
|
425
|
-
label: 'New Folder',
|
|
426
|
-
caption: 'Create a new folder',
|
|
434
|
+
label: trans.__('New Folder'),
|
|
435
|
+
caption: trans.__('Create a new folder'),
|
|
427
436
|
icon: newFolderIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
428
437
|
execute: async () => {
|
|
429
438
|
const cwd = getWorkingDirectory(browser);
|
|
@@ -435,13 +444,13 @@ export function registerCommands(opts) {
|
|
|
435
444
|
browser.notifyPathAdded(toCanonicalPath(created));
|
|
436
445
|
}
|
|
437
446
|
catch (err) {
|
|
438
|
-
await showErrorMessage('Could not create folder', err);
|
|
447
|
+
await showErrorMessage(trans.__('Could not create folder'), err);
|
|
439
448
|
}
|
|
440
449
|
}
|
|
441
450
|
});
|
|
442
451
|
commands.addCommand(CommandIDs.newLauncher, {
|
|
443
|
-
label: 'New Launcher',
|
|
444
|
-
caption: 'Open a new launcher',
|
|
452
|
+
label: trans.__('New Launcher'),
|
|
453
|
+
caption: trans.__('Open a new launcher'),
|
|
445
454
|
// Skip the `menuItem` bindprops the other commands use: this command
|
|
446
455
|
// lives on the toolbar, where the launcher-extension's blue button
|
|
447
456
|
// styling expects the raw `jp-icon3` paths from `addIcon` so it can
|
|
@@ -15,10 +15,14 @@ export declare function toCanonicalPath(child: Contents.IModel): string;
|
|
|
15
15
|
* paths so the value can be sent to the Jupyter contents API.
|
|
16
16
|
*/
|
|
17
17
|
export declare function toServerPath(canonicalPath: string): string;
|
|
18
|
-
|
|
19
|
-
/**
|
|
18
|
+
interface IListedDirectory {
|
|
19
|
+
/**
|
|
20
|
+
* Canonical paths for every immediate child of the requested directory.
|
|
21
|
+
*/
|
|
20
22
|
paths: string[];
|
|
21
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Subset of {@link IListedDirectory.paths} that are directories.
|
|
25
|
+
*/
|
|
22
26
|
subdirectories: string[];
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
@@ -26,3 +30,4 @@ export interface IListedDirectory {
|
|
|
26
30
|
* and return them as canonical `@pierre/trees` paths.
|
|
27
31
|
*/
|
|
28
32
|
export declare function listDirectory(manager: Contents.IManager, serverPath: string): Promise<IListedDirectory>;
|
|
33
|
+
export {};
|