xtralab 0.10.0 → 0.11.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 +27 -11
- package/lib/commandBar/index.d.ts +14 -0
- package/lib/commandBar/index.js +114 -0
- package/lib/fileBrowser/fileBrowser.js +6 -1
- package/lib/highlight/index.d.ts +15 -0
- package/lib/highlight/index.js +214 -0
- package/lib/index.js +13 -1
- package/lib/launcher/agents.d.ts +18 -1
- package/lib/launcher/agents.js +47 -4
- package/lib/launcher/editorRegistry.js +5 -3
- package/lib/launcher/editors.d.ts +17 -2
- package/lib/launcher/editors.js +31 -5
- package/lib/launcher/index.js +7 -1
- package/lib/launcher/schemaDefaults.d.ts +15 -0
- package/lib/launcher/schemaDefaults.js +44 -0
- package/lib/omnibox/files.d.ts +7 -0
- package/lib/omnibox/files.js +50 -0
- package/lib/omnibox/index.d.ts +14 -0
- package/lib/omnibox/index.js +83 -0
- package/lib/omnibox/model.d.ts +45 -0
- package/lib/omnibox/model.js +142 -0
- package/lib/omnibox/tokens.d.ts +23 -0
- package/lib/omnibox/tokens.js +11 -0
- package/lib/omnibox/widget.d.ts +36 -0
- package/lib/omnibox/widget.js +161 -0
- package/lib/searchReplace/index.d.ts +25 -0
- package/lib/searchReplace/index.js +68 -0
- package/lib/showOutput/index.d.ts +17 -0
- package/lib/showOutput/index.js +154 -0
- package/lib/terminals/index.d.ts +4 -0
- package/lib/terminals/index.js +65 -16
- package/lib/terminals/model.d.ts +56 -3
- package/lib/terminals/model.js +261 -5
- package/lib/terminals/widget.d.ts +3 -1
- package/lib/terminals/widget.js +11 -3
- package/lib/walkthrough/index.d.ts +14 -0
- package/lib/walkthrough/index.js +162 -0
- package/lib/walkthrough/panel.d.ts +54 -0
- package/lib/walkthrough/panel.js +98 -0
- package/package.json +1 -1
- package/schema/launcher.json +5 -4
- package/schema/terminals.json +15 -0
- package/src/commandBar/index.ts +165 -0
- package/src/fileBrowser/fileBrowser.tsx +8 -1
- package/src/highlight/index.ts +251 -0
- package/src/index.ts +13 -1
- package/src/launcher/agents.ts +53 -3
- package/src/launcher/editorRegistry.ts +8 -4
- package/src/launcher/editors.ts +37 -4
- package/src/launcher/index.ts +7 -1
- package/src/launcher/schemaDefaults.ts +53 -0
- package/src/omnibox/files.ts +68 -0
- package/src/omnibox/index.ts +105 -0
- package/src/omnibox/model.ts +210 -0
- package/src/omnibox/tokens.ts +29 -0
- package/src/omnibox/widget.tsx +322 -0
- package/src/searchReplace/index.ts +86 -0
- package/src/showOutput/index.ts +199 -0
- package/src/terminals/index.ts +79 -16
- package/src/terminals/model.ts +304 -3
- package/src/terminals/widget.tsx +21 -4
- package/src/walkthrough/index.ts +202 -0
- package/src/walkthrough/panel.ts +152 -0
- package/style/commandBar.css +104 -0
- package/style/highlight.css +11 -0
- package/style/index.css +5 -0
- package/style/index.js +5 -0
- package/style/omnibox.css +120 -0
- package/style/showOutput.css +11 -0
- package/style/terminals.css +54 -10
- package/style/walkthrough.css +80 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { ILabShell } from '@jupyterlab/application';
|
|
2
|
+
import { ICommandPalette } from '@jupyterlab/apputils';
|
|
3
|
+
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
4
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
5
|
+
import { tocIcon } from '@jupyterlab/ui-components';
|
|
6
|
+
import { WalkthroughPanel } from './panel';
|
|
7
|
+
const PLUGIN_ID = 'xtralab:walkthrough';
|
|
8
|
+
/** Append a step to the read-only walkthrough panel. */
|
|
9
|
+
const WALKTHROUGH_COMMAND = 'xtralab:walkthrough';
|
|
10
|
+
const HIGHLIGHT_LINES_COMMAND = 'xtralab:highlight-lines';
|
|
11
|
+
const PANEL_ID = 'xtralab-walkthrough';
|
|
12
|
+
/** Pull a `{ mimeType, data }` media object out of a JSON command argument. */
|
|
13
|
+
function readMedia(value) {
|
|
14
|
+
if (value &&
|
|
15
|
+
typeof value === 'object' &&
|
|
16
|
+
!Array.isArray(value) &&
|
|
17
|
+
typeof value.mimeType === 'string') {
|
|
18
|
+
const media = value;
|
|
19
|
+
return { mimeType: media.mimeType, data: media.data };
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
function toLine(value) {
|
|
24
|
+
const n = Math.trunc(Number(value));
|
|
25
|
+
return Number.isFinite(n) && n >= 1 ? n : undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Contribute `xtralab:walkthrough`: build a persistent, read-only walkthrough
|
|
29
|
+
* in the right side area instead of narrating only in the agent's chat.
|
|
30
|
+
*
|
|
31
|
+
* Each call appends a step (Markdown prose, an optional embedded visual, and an
|
|
32
|
+
* optional code reference). When a step names a file, the editor follows along
|
|
33
|
+
* (opening it full-width in the main area, no split) and its lines are
|
|
34
|
+
* highlighted; the step also keeps a button so the user can jump back to it
|
|
35
|
+
* later. The panel accumulates the whole tour beside the code, so the user can
|
|
36
|
+
* read at their own pace rather than chasing the chat.
|
|
37
|
+
*/
|
|
38
|
+
const plugin = {
|
|
39
|
+
id: PLUGIN_ID,
|
|
40
|
+
description: 'Build a read-only guided walkthrough in the side panel.',
|
|
41
|
+
autoStart: true,
|
|
42
|
+
requires: [IRenderMimeRegistry, ILabShell],
|
|
43
|
+
optional: [ICommandPalette, ITranslator],
|
|
44
|
+
activate: (app, rendermime, labShell, palette, translator) => {
|
|
45
|
+
const { commands } = app;
|
|
46
|
+
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
|
|
47
|
+
let panel = null;
|
|
48
|
+
const ensurePanel = () => {
|
|
49
|
+
if (!panel || panel.isDisposed) {
|
|
50
|
+
panel = new WalkthroughPanel({ rendermime, commands, trans });
|
|
51
|
+
panel.id = PANEL_ID;
|
|
52
|
+
panel.title.icon = tocIcon;
|
|
53
|
+
panel.title.label = trans.__('Walkthrough');
|
|
54
|
+
panel.title.caption = trans.__('Walkthrough');
|
|
55
|
+
// Closable so it gets a close button if the user drags it out of the
|
|
56
|
+
// side area into the main document area. Closing disposes it; the next
|
|
57
|
+
// xtralab:walkthrough call recreates it back in the side area.
|
|
58
|
+
panel.title.closable = true;
|
|
59
|
+
labShell.add(panel, 'right', { rank: 1000, type: 'Walkthrough' });
|
|
60
|
+
const created = panel;
|
|
61
|
+
created.disposed.connect(() => {
|
|
62
|
+
if (panel === created) {
|
|
63
|
+
panel = null;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return panel;
|
|
68
|
+
};
|
|
69
|
+
commands.addCommand(WALKTHROUGH_COMMAND, {
|
|
70
|
+
label: trans.__('Add Walkthrough Step'),
|
|
71
|
+
caption: trans.__('Append a step to the read-only walkthrough panel'),
|
|
72
|
+
describedBy: {
|
|
73
|
+
args: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
title: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'Optional heading for the step.'
|
|
79
|
+
},
|
|
80
|
+
body: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
description: 'Markdown for the step. Renders code snippets, lists, math, and Mermaid diagrams from a ```mermaid fenced block.'
|
|
83
|
+
},
|
|
84
|
+
path: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
description: 'Optional file the step is about, relative to the server root. The editor opens it (full width, no split) and highlights the lines, and the step keeps a button to jump back.'
|
|
87
|
+
},
|
|
88
|
+
line: {
|
|
89
|
+
type: 'number',
|
|
90
|
+
description: 'First line to highlight (1-indexed).'
|
|
91
|
+
},
|
|
92
|
+
endLine: {
|
|
93
|
+
type: 'number',
|
|
94
|
+
description: 'Last line to highlight (1-indexed, inclusive).'
|
|
95
|
+
},
|
|
96
|
+
media: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
description: 'Optional embedded visual, e.g. { "mimeType": "application/vnd.vegalite.v5+json", "data": <spec> }.',
|
|
99
|
+
properties: {
|
|
100
|
+
mimeType: { type: 'string' },
|
|
101
|
+
data: {}
|
|
102
|
+
},
|
|
103
|
+
required: ['mimeType', 'data']
|
|
104
|
+
},
|
|
105
|
+
reveal: {
|
|
106
|
+
type: 'boolean',
|
|
107
|
+
description: 'Whether to navigate and highlight the code as the step is added. Defaults to true.'
|
|
108
|
+
},
|
|
109
|
+
reset: {
|
|
110
|
+
type: 'boolean',
|
|
111
|
+
description: 'Clear the existing walkthrough before adding this step (start a new tour).'
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
execute: async (args) => {
|
|
117
|
+
const current = ensurePanel();
|
|
118
|
+
if (args['reset'] === true) {
|
|
119
|
+
current.clear();
|
|
120
|
+
}
|
|
121
|
+
const path = typeof args['path'] === 'string' ? args['path'] : undefined;
|
|
122
|
+
const line = toLine(args['line']);
|
|
123
|
+
const endLine = toLine(args['endLine']);
|
|
124
|
+
const media = readMedia(args['media']);
|
|
125
|
+
const step = {
|
|
126
|
+
title: typeof args['title'] === 'string' ? args['title'] : undefined,
|
|
127
|
+
body: typeof args['body'] === 'string' ? args['body'] : undefined,
|
|
128
|
+
media,
|
|
129
|
+
path,
|
|
130
|
+
line,
|
|
131
|
+
endLine
|
|
132
|
+
};
|
|
133
|
+
if (!step.title &&
|
|
134
|
+
!step.body &&
|
|
135
|
+
!step.media &&
|
|
136
|
+
!step.path &&
|
|
137
|
+
args['reset'] !== true) {
|
|
138
|
+
throw new Error('xtralab:walkthrough needs at least one of: title, body, media, path');
|
|
139
|
+
}
|
|
140
|
+
// Reveal the panel, then render the step into it.
|
|
141
|
+
labShell.activateById(current.id);
|
|
142
|
+
await current.addStep(step);
|
|
143
|
+
// Let the editor follow the newest step.
|
|
144
|
+
if (path && args['reveal'] !== false) {
|
|
145
|
+
await commands.execute(HIGHLIGHT_LINES_COMMAND, {
|
|
146
|
+
path,
|
|
147
|
+
line,
|
|
148
|
+
endLine
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return trans.__('Added walkthrough step');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
if (palette) {
|
|
155
|
+
palette.addItem({
|
|
156
|
+
command: WALKTHROUGH_COMMAND,
|
|
157
|
+
category: trans.__('Other')
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
export default plugin;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
+
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
3
|
+
import type { CommandRegistry } from '@lumino/commands';
|
|
4
|
+
import type { ReadonlyPartialJSONValue } from '@lumino/coreutils';
|
|
5
|
+
import type { Message } from '@lumino/messaging';
|
|
6
|
+
import { Panel } from '@lumino/widgets';
|
|
7
|
+
/** A rich visual embedded in a step (rendered through the rendermime registry). */
|
|
8
|
+
export interface IWalkthroughMedia {
|
|
9
|
+
mimeType: string;
|
|
10
|
+
data: ReadonlyPartialJSONValue;
|
|
11
|
+
}
|
|
12
|
+
/** One step of a walkthrough. Every field is optional, but a step needs at least one. */
|
|
13
|
+
export interface IWalkthroughStep {
|
|
14
|
+
title?: string;
|
|
15
|
+
body?: string;
|
|
16
|
+
media?: IWalkthroughMedia;
|
|
17
|
+
path?: string;
|
|
18
|
+
line?: number;
|
|
19
|
+
endLine?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare namespace WalkthroughPanel {
|
|
22
|
+
interface IOptions {
|
|
23
|
+
rendermime: IRenderMimeRegistry;
|
|
24
|
+
commands: CommandRegistry;
|
|
25
|
+
trans: TranslationBundle;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A read-only, scrollable column of walkthrough steps for the side area.
|
|
30
|
+
*
|
|
31
|
+
* The agent appends steps as it narrates, so the explanation lives beside the
|
|
32
|
+
* code the user is looking at and persists for them to read at their own pace,
|
|
33
|
+
* rather than scrolling away in the chat. Each step renders Markdown (with code
|
|
34
|
+
* snippets, Mermaid, math), can embed a rich visual, and can offer a button
|
|
35
|
+
* that opens the referenced file and highlights its lines.
|
|
36
|
+
*/
|
|
37
|
+
export declare class WalkthroughPanel extends Panel {
|
|
38
|
+
constructor(options: WalkthroughPanel.IOptions);
|
|
39
|
+
/** Remove every step, leaving an empty panel. */
|
|
40
|
+
clear(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Dispose on close (e.g. the close button when the panel has been dragged to
|
|
43
|
+
* the main area) rather than leaving a detached, reusable widget behind. The
|
|
44
|
+
* next `xtralab:walkthrough` call recreates the panel in the side area.
|
|
45
|
+
*/
|
|
46
|
+
protected onCloseRequest(msg: Message): void;
|
|
47
|
+
/** Append a step and scroll it into view. */
|
|
48
|
+
addStep(step: IWalkthroughStep): Promise<void>;
|
|
49
|
+
private _render;
|
|
50
|
+
private _jumpButton;
|
|
51
|
+
private _rendermime;
|
|
52
|
+
private _commands;
|
|
53
|
+
private _trans;
|
|
54
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { fileIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
import { Panel, Widget } from '@lumino/widgets';
|
|
3
|
+
/** Command the jump links dispatch; keeps the panel decoupled from the plugin. */
|
|
4
|
+
const HIGHLIGHT_LINES_COMMAND = 'xtralab:highlight-lines';
|
|
5
|
+
/**
|
|
6
|
+
* A read-only, scrollable column of walkthrough steps for the side area.
|
|
7
|
+
*
|
|
8
|
+
* The agent appends steps as it narrates, so the explanation lives beside the
|
|
9
|
+
* code the user is looking at and persists for them to read at their own pace,
|
|
10
|
+
* rather than scrolling away in the chat. Each step renders Markdown (with code
|
|
11
|
+
* snippets, Mermaid, math), can embed a rich visual, and can offer a button
|
|
12
|
+
* that opens the referenced file and highlights its lines.
|
|
13
|
+
*/
|
|
14
|
+
export class WalkthroughPanel extends Panel {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
super();
|
|
17
|
+
this._rendermime = options.rendermime;
|
|
18
|
+
this._commands = options.commands;
|
|
19
|
+
this._trans = options.trans;
|
|
20
|
+
this.addClass('jp-xtralab-Walkthrough');
|
|
21
|
+
}
|
|
22
|
+
/** Remove every step, leaving an empty panel. */
|
|
23
|
+
clear() {
|
|
24
|
+
while (this.widgets.length > 0) {
|
|
25
|
+
this.widgets[0].dispose();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Dispose on close (e.g. the close button when the panel has been dragged to
|
|
30
|
+
* the main area) rather than leaving a detached, reusable widget behind. The
|
|
31
|
+
* next `xtralab:walkthrough` call recreates the panel in the side area.
|
|
32
|
+
*/
|
|
33
|
+
onCloseRequest(msg) {
|
|
34
|
+
super.onCloseRequest(msg);
|
|
35
|
+
this.dispose();
|
|
36
|
+
}
|
|
37
|
+
/** Append a step and scroll it into view. */
|
|
38
|
+
async addStep(step) {
|
|
39
|
+
const card = new Panel();
|
|
40
|
+
card.addClass('jp-xtralab-Walkthrough-step');
|
|
41
|
+
this.addWidget(card);
|
|
42
|
+
if (step.title) {
|
|
43
|
+
const heading = new Widget({ node: document.createElement('h3') });
|
|
44
|
+
heading.addClass('jp-xtralab-Walkthrough-stepTitle');
|
|
45
|
+
heading.node.textContent = step.title;
|
|
46
|
+
card.addWidget(heading);
|
|
47
|
+
}
|
|
48
|
+
if (step.body) {
|
|
49
|
+
await this._render(card, 'text/markdown', step.body);
|
|
50
|
+
}
|
|
51
|
+
if (step.media && typeof step.media.mimeType === 'string') {
|
|
52
|
+
await this._render(card, step.media.mimeType, step.media.data);
|
|
53
|
+
}
|
|
54
|
+
if (step.path) {
|
|
55
|
+
card.addWidget(this._jumpButton(step.path, step.line, step.endLine));
|
|
56
|
+
}
|
|
57
|
+
// Bring the newest step into view.
|
|
58
|
+
this.node.scrollTop = this.node.scrollHeight;
|
|
59
|
+
}
|
|
60
|
+
async _render(parent, mimeType, data) {
|
|
61
|
+
const bundle = { [mimeType]: data };
|
|
62
|
+
const chosen = this._rendermime.preferredMimeType(bundle, 'any');
|
|
63
|
+
if (!chosen) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const renderer = this._rendermime.createRenderer(chosen);
|
|
67
|
+
renderer.addClass('jp-xtralab-Walkthrough-content');
|
|
68
|
+
parent.addWidget(renderer);
|
|
69
|
+
await renderer.renderModel(this._rendermime.createModel({ data: bundle, trusted: true }));
|
|
70
|
+
}
|
|
71
|
+
_jumpButton(path, line, endLine) {
|
|
72
|
+
const node = document.createElement('div');
|
|
73
|
+
node.className = 'jp-xtralab-Walkthrough-jumpRow';
|
|
74
|
+
const where = line ? `${path}:${line}` : path;
|
|
75
|
+
const button = document.createElement('button');
|
|
76
|
+
button.type = 'button';
|
|
77
|
+
button.className = 'jp-xtralab-Walkthrough-jump';
|
|
78
|
+
button.title = this._trans.__('Open %1 in the editor', where);
|
|
79
|
+
const icon = fileIcon.element({
|
|
80
|
+
tag: 'span',
|
|
81
|
+
className: 'jp-xtralab-Walkthrough-jumpIcon'
|
|
82
|
+
});
|
|
83
|
+
const label = document.createElement('span');
|
|
84
|
+
label.className = 'jp-xtralab-Walkthrough-jumpLabel';
|
|
85
|
+
label.textContent = where;
|
|
86
|
+
button.appendChild(icon);
|
|
87
|
+
button.appendChild(label);
|
|
88
|
+
node.appendChild(button);
|
|
89
|
+
button.addEventListener('click', () => {
|
|
90
|
+
void this._commands.execute(HIGHLIGHT_LINES_COMMAND, {
|
|
91
|
+
path,
|
|
92
|
+
line,
|
|
93
|
+
endLine
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
return new Widget({ node });
|
|
97
|
+
}
|
|
98
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xtralab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "An opinionated JupyterLab meta-package that bundles a curated set of extensions, ships a path-first file browser, renders jupyterlab-git's text, notebook and image diffs with its own viewers, and applies a quieter default workspace configuration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
package/schema/launcher.json
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
"title": "xtralab launcher",
|
|
3
3
|
"description": "Settings for the xtralab agent launcher.",
|
|
4
4
|
"jupyter.lab.shortcuts": [],
|
|
5
|
+
"jupyter.lab.transform": true,
|
|
5
6
|
"type": "object",
|
|
6
7
|
"additionalProperties": false,
|
|
7
8
|
"properties": {
|
|
8
9
|
"agents": {
|
|
9
10
|
"title": "Agents",
|
|
10
|
-
"description": "Override or extend the built-in list of agents shown on the launcher. Each entry must include an `id`. When the id matches a built-in (claude, codex, antigravity, copilot, goose, opencode, kiro, mistral-vibe), the fields you set override the corresponding defaults; leave a field out to keep the default. New ids define brand-new cards. Set `enabled: false` to hide a default.
|
|
11
|
+
"description": "Override or extend the built-in list of agents shown on the launcher. Each entry must include an `id`. When the id matches a built-in (claude, codex, antigravity, copilot, goose, opencode, kiro, mistral-vibe), the fields you set override the corresponding defaults; leave a field out to keep the default. New ids define brand-new cards. Set `enabled: false` to hide a default. Changing a built-in's `command` (for example to a shell alias like `ccm`) keeps the card visible even though the alias isn't on PATH; use `requireAvailable: false` only when you want to skip the check without changing the command. The full built-in list is shown as the default below, so you can copy an entry and tweak it in place.",
|
|
11
12
|
"type": "array",
|
|
12
13
|
"default": [],
|
|
13
14
|
"items": {
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
},
|
|
54
55
|
"requireAvailable": {
|
|
55
56
|
"title": "Require available",
|
|
56
|
-
"description": "When false, the launcher skips the availability check for this entry (useful for shell aliases that aren't on PATH but resolve in an interactive shell).",
|
|
57
|
+
"description": "When false, the launcher skips the availability check for this entry (useful for shell aliases that aren't on PATH but resolve in an interactive shell). Overriding a built-in agent's command turns this off automatically, so an aliased command still shows; set it explicitly to force the check on or off.",
|
|
57
58
|
"type": "boolean",
|
|
58
59
|
"default": true
|
|
59
60
|
},
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
},
|
|
69
70
|
"editors": {
|
|
70
71
|
"title": "Editors",
|
|
71
|
-
"description": "Override or extend the terminal editors offered in the launcher's Open section (and recognized in the Terminals panel). Each entry must include an `id`. When the id matches a built-in (nvim, vim), the fields you set override the corresponding defaults; leave a field out to keep the default. New ids define additional editors. The launcher shows a single tile — the first editor, by `rank`, whose `command` is on PATH (Neovim before Vim by default). Set `enabled: false` to hide a built-in; disable both to remove the tile entirely.
|
|
72
|
+
"description": "Override or extend the terminal editors offered in the launcher's Open section (and recognized in the Terminals panel). Each entry must include an `id`. When the id matches a built-in (nvim, vim), the fields you set override the corresponding defaults; leave a field out to keep the default. New ids define additional editors. The launcher shows a single tile — the first editor, by `rank`, whose `command` is on PATH (Neovim before Vim by default). Set `enabled: false` to hide a built-in; disable both to remove the tile entirely. Changing a built-in's `command` (for example to a shell alias) keeps the tile visible even though the alias isn't on PATH; use `requireAvailable: false` only when you want to skip the check without changing the command. The full built-in list is shown as the default below, so you can copy an entry and tweak it in place.",
|
|
72
73
|
"type": "array",
|
|
73
74
|
"default": [],
|
|
74
75
|
"items": {
|
|
@@ -114,7 +115,7 @@
|
|
|
114
115
|
},
|
|
115
116
|
"requireAvailable": {
|
|
116
117
|
"title": "Require available",
|
|
117
|
-
"description": "When false, the launcher skips the availability check for this entry (useful for a shell alias that isn't on PATH but resolves in an interactive shell).",
|
|
118
|
+
"description": "When false, the launcher skips the availability check for this entry (useful for a shell alias that isn't on PATH but resolves in an interactive shell). Overriding a built-in editor's command turns this off automatically, so an aliased command still shows; set it explicitly to force the check on or off.",
|
|
118
119
|
"type": "boolean",
|
|
119
120
|
"default": true
|
|
120
121
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "xtralab Terminals panel",
|
|
3
|
+
"description": "The left-sidebar panel that lists running terminal sessions.",
|
|
4
|
+
"jupyter.lab.shortcuts": [],
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"showAgentActivity": {
|
|
8
|
+
"type": "boolean",
|
|
9
|
+
"title": "Show agent activity under each terminal",
|
|
10
|
+
"description": "When true, every terminal running a coding agent shows a smaller line below its title with the agent's latest line of output, so you can see what a backgrounded agent is doing without switching to it. Turn this off to list terminals by title alone.",
|
|
11
|
+
"default": true
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"additionalProperties": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ILabShell,
|
|
3
|
+
JupyterFrontEnd,
|
|
4
|
+
JupyterFrontEndPlugin
|
|
5
|
+
} from '@jupyterlab/application';
|
|
6
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
7
|
+
import { searchIcon } from '@jupyterlab/ui-components';
|
|
8
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
9
|
+
import { Widget } from '@lumino/widgets';
|
|
10
|
+
|
|
11
|
+
import { IOmnibox, OMNIBOX_OPEN_COMMAND } from '../omnibox/tokens';
|
|
12
|
+
|
|
13
|
+
const PLUGIN_ID = 'xtralab:command-bar';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A search-bar-styled launcher for the `top` area. It holds no text input of
|
|
17
|
+
* its own: clicking the pill (or pressing Enter/Space while it is focused)
|
|
18
|
+
* runs `onActivate`, which opens the omnibox.
|
|
19
|
+
*
|
|
20
|
+
* The widget's root is a transparent, full-height wrapper centered as an
|
|
21
|
+
* overlay over the whole top panel (style/commandBar.css). The wrapper
|
|
22
|
+
* ignores pointer events so only the inner pill is clickable and the empty
|
|
23
|
+
* strips above and below it let clicks fall through to whatever sits beneath
|
|
24
|
+
* the overlay, such as the menu bar.
|
|
25
|
+
*/
|
|
26
|
+
class CommandBar extends Widget {
|
|
27
|
+
constructor(options: CommandBar.IOptions) {
|
|
28
|
+
super({
|
|
29
|
+
node: CommandBar.createNode(
|
|
30
|
+
options.label,
|
|
31
|
+
options.caption,
|
|
32
|
+
options.shortcut
|
|
33
|
+
)
|
|
34
|
+
});
|
|
35
|
+
this.addClass('jp-xtralab-CommandBar');
|
|
36
|
+
this._onActivate = options.onActivate;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
handleEvent(event: Event): void {
|
|
40
|
+
if (event.type === 'click') {
|
|
41
|
+
this._onActivate();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected onAfterAttach(): void {
|
|
46
|
+
// The pill restores `pointer-events: auto`, so the click bubbles from it
|
|
47
|
+
// up to the root node listened on here; keyboard Enter/Space on the
|
|
48
|
+
// focused button raises the same synthetic click.
|
|
49
|
+
this.node.addEventListener('click', this);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected onBeforeDetach(): void {
|
|
53
|
+
this.node.removeEventListener('click', this);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private _onActivate: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
namespace CommandBar {
|
|
60
|
+
export interface IOptions {
|
|
61
|
+
/** Placeholder-style text shown inside the pill. */
|
|
62
|
+
label: string;
|
|
63
|
+
/** Tooltip and accessible name for the button. */
|
|
64
|
+
caption: string;
|
|
65
|
+
/** Formatted keyboard shortcut shown as a trailing hint, if any. */
|
|
66
|
+
shortcut?: string;
|
|
67
|
+
/** Runs on click or keyboard activation. */
|
|
68
|
+
onActivate: () => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createNode(
|
|
72
|
+
label: string,
|
|
73
|
+
caption: string,
|
|
74
|
+
shortcut?: string
|
|
75
|
+
): HTMLElement {
|
|
76
|
+
const wrapper = document.createElement('div');
|
|
77
|
+
|
|
78
|
+
const button = document.createElement('button');
|
|
79
|
+
button.type = 'button';
|
|
80
|
+
button.className = 'jp-xtralab-CommandBar-button';
|
|
81
|
+
button.title = caption;
|
|
82
|
+
button.setAttribute('aria-label', caption);
|
|
83
|
+
|
|
84
|
+
const icon = searchIcon.element({
|
|
85
|
+
tag: 'span',
|
|
86
|
+
className: 'jp-xtralab-CommandBar-icon'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const text = document.createElement('span');
|
|
90
|
+
text.className = 'jp-xtralab-CommandBar-label';
|
|
91
|
+
text.textContent = label;
|
|
92
|
+
|
|
93
|
+
button.appendChild(icon);
|
|
94
|
+
button.appendChild(text);
|
|
95
|
+
|
|
96
|
+
if (shortcut) {
|
|
97
|
+
const hint = document.createElement('span');
|
|
98
|
+
hint.className = 'jp-xtralab-CommandBar-shortcut';
|
|
99
|
+
hint.textContent = shortcut;
|
|
100
|
+
// Decorative: the button's aria-label already names the action.
|
|
101
|
+
hint.setAttribute('aria-hidden', 'true');
|
|
102
|
+
button.appendChild(hint);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
wrapper.appendChild(button);
|
|
106
|
+
return wrapper;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Add a centered, search-bar-styled command bar to the top area. Clicking it
|
|
112
|
+
* opens the omnibox — a launcher overlay that fuzzy-searches files and
|
|
113
|
+
* commands and routes a typed prompt to an agent (the leading sidebar toggle
|
|
114
|
+
* and the menu bar sit to its left, the trailing sidebar toggle to its right;
|
|
115
|
+
* the bar is centered over the whole panel as an overlay, so it stays put
|
|
116
|
+
* regardless of their widths).
|
|
117
|
+
*
|
|
118
|
+
* The bar is only added when the omnibox is available — the `IOmnibox` token
|
|
119
|
+
* is provided by `xtralab:omnibox` — so it never appears with nothing to open.
|
|
120
|
+
*/
|
|
121
|
+
const plugin: JupyterFrontEndPlugin<void> = {
|
|
122
|
+
id: PLUGIN_ID,
|
|
123
|
+
description:
|
|
124
|
+
'Add a centered command bar to the top area that opens the omnibox.',
|
|
125
|
+
autoStart: true,
|
|
126
|
+
requires: [ILabShell],
|
|
127
|
+
optional: [IOmnibox, ITranslator],
|
|
128
|
+
activate: (
|
|
129
|
+
app: JupyterFrontEnd,
|
|
130
|
+
labShell: ILabShell,
|
|
131
|
+
omnibox: IOmnibox | null,
|
|
132
|
+
translator: ITranslator | null
|
|
133
|
+
): void => {
|
|
134
|
+
if (!omnibox) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
139
|
+
|
|
140
|
+
// Show the omnibox's keyboard shortcut as a hint in the pill, derived from
|
|
141
|
+
// the live binding (registered by xtralab:omnibox, which activates first as
|
|
142
|
+
// the IOmnibox provider) so it stays correct and uses the platform's
|
|
143
|
+
// modifier symbols. Empty when no binding is registered.
|
|
144
|
+
const binding = app.commands.keyBindings.find(
|
|
145
|
+
keyBinding => keyBinding.command === OMNIBOX_OPEN_COMMAND
|
|
146
|
+
);
|
|
147
|
+
const shortcut = binding
|
|
148
|
+
? CommandRegistry.formatKeystroke(binding.keys)
|
|
149
|
+
: '';
|
|
150
|
+
|
|
151
|
+
const widget = new CommandBar({
|
|
152
|
+
label: trans.__('Search…'),
|
|
153
|
+
caption: trans.__('Search files and commands, or ask an agent'),
|
|
154
|
+
shortcut,
|
|
155
|
+
onActivate: () => {
|
|
156
|
+
omnibox.open();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
widget.id = 'xtralab-command-bar';
|
|
160
|
+
|
|
161
|
+
labShell.add(widget, 'top', { rank: 500 });
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export default plugin;
|
|
@@ -93,6 +93,12 @@ const GIT_REPO_PATH = '';
|
|
|
93
93
|
*/
|
|
94
94
|
const FILE_TREE_TAG = 'file-tree-container';
|
|
95
95
|
|
|
96
|
+
const FILE_TREE_UNSAFE_CSS =
|
|
97
|
+
'[data-type="item"][data-item-selected="true"] ' +
|
|
98
|
+
'[data-item-section="spacing-item"] {' +
|
|
99
|
+
'border-left-color: transparent;' +
|
|
100
|
+
'}';
|
|
101
|
+
|
|
96
102
|
export interface IFileBrowserProps {
|
|
97
103
|
contentsManager: Contents.IManager;
|
|
98
104
|
docManager: IDocumentManager;
|
|
@@ -123,7 +129,8 @@ export function FileBrowserComponent(
|
|
|
123
129
|
initialExpansion: 'closed',
|
|
124
130
|
search: true,
|
|
125
131
|
icons: FILE_BROWSER_ICONS,
|
|
126
|
-
itemHeight: 24
|
|
132
|
+
itemHeight: 24,
|
|
133
|
+
unsafeCSS: FILE_TREE_UNSAFE_CSS
|
|
127
134
|
});
|
|
128
135
|
|
|
129
136
|
React.useEffect(() => {
|