xtralab 0.11.1 → 0.12.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 +38 -231
- 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.js +3 -4
- 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 +15 -5
- package/lib/launcher/icons.d.ts +0 -37
- package/lib/launcher/icons.js +11 -12
- package/lib/launcher/index.js +2 -1
- 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/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 +3 -4
- 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 +15 -5
- package/src/launcher/icons.ts +11 -12
- package/src/launcher/index.ts +2 -1
- 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
package/lib/omnibox/model.d.ts
CHANGED
|
@@ -3,43 +3,72 @@ import type { TranslationBundle } from '@jupyterlab/translation';
|
|
|
3
3
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
4
4
|
import type { CommandRegistry } from '@lumino/commands';
|
|
5
5
|
import type { IAgent } from '../launcher/agents';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import type { OmniboxRecents } from './recents';
|
|
7
|
+
/**
|
|
8
|
+
* The source a result row came from, used to group rows under a header.
|
|
9
|
+
*/
|
|
10
|
+
type OmniboxItemKind = 'command' | 'file' | 'agent';
|
|
11
|
+
/**
|
|
12
|
+
* A single result row, with the action it runs when chosen.
|
|
13
|
+
*/
|
|
9
14
|
export interface IOmniboxItem {
|
|
10
15
|
kind: OmniboxItemKind;
|
|
11
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Stable React key.
|
|
18
|
+
*/
|
|
12
19
|
key: string;
|
|
13
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Primary text shown on the row.
|
|
22
|
+
*/
|
|
14
23
|
label: string;
|
|
15
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Indices of matched characters in `label`, for highlighting. May be empty.
|
|
26
|
+
*/
|
|
16
27
|
matchIndices: readonly number[];
|
|
17
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Optional secondary text shown trailing the label.
|
|
30
|
+
*/
|
|
18
31
|
caption?: string;
|
|
19
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Optional leading icon.
|
|
34
|
+
*/
|
|
20
35
|
icon?: LabIcon;
|
|
21
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Run the row's action.
|
|
38
|
+
*/
|
|
22
39
|
execute: () => void;
|
|
23
40
|
}
|
|
24
|
-
/**
|
|
25
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Results grouped by source; each group is already capped and sorted.
|
|
43
|
+
*/
|
|
44
|
+
interface IOmniboxSections {
|
|
45
|
+
/**
|
|
46
|
+
* Recently used commands and files, shown while the term is empty.
|
|
47
|
+
*/
|
|
48
|
+
recent: IOmniboxItem[];
|
|
26
49
|
commands: IOmniboxItem[];
|
|
27
50
|
files: IOmniboxItem[];
|
|
28
51
|
agents: IOmniboxItem[];
|
|
29
52
|
}
|
|
30
|
-
|
|
53
|
+
interface IComputeOptions {
|
|
31
54
|
query: string;
|
|
32
55
|
commands: CommandRegistry;
|
|
33
56
|
docRegistry: DocumentRegistry;
|
|
34
57
|
agents: IAgent[];
|
|
35
58
|
files: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Recently-used tracker; `null` disables the recent rows and recording.
|
|
61
|
+
*/
|
|
62
|
+
recents: OmniboxRecents | null;
|
|
36
63
|
trans: TranslationBundle;
|
|
37
64
|
}
|
|
38
65
|
/**
|
|
39
66
|
* Build the grouped result set for a query. A leading `>` searches only
|
|
40
67
|
* commands and a leading `/` only files; otherwise commands and files are
|
|
41
68
|
* fuzzy-matched and every prompt-capable agent is offered an "Ask" row
|
|
42
|
-
* carrying the query as its prompt. An empty term yields
|
|
43
|
-
* shows a hint
|
|
69
|
+
* carrying the query as its prompt. An empty term yields the recently used
|
|
70
|
+
* rows for the active mode instead (and the widget shows a hint when there
|
|
71
|
+
* are none).
|
|
44
72
|
*/
|
|
45
73
|
export declare function computeSections(options: IComputeOptions): IOmniboxSections;
|
|
74
|
+
export {};
|
package/lib/omnibox/model.js
CHANGED
|
@@ -14,11 +14,12 @@ const FILE_PREFIX = '/';
|
|
|
14
14
|
* Build the grouped result set for a query. A leading `>` searches only
|
|
15
15
|
* commands and a leading `/` only files; otherwise commands and files are
|
|
16
16
|
* fuzzy-matched and every prompt-capable agent is offered an "Ask" row
|
|
17
|
-
* carrying the query as its prompt. An empty term yields
|
|
18
|
-
* shows a hint
|
|
17
|
+
* carrying the query as its prompt. An empty term yields the recently used
|
|
18
|
+
* rows for the active mode instead (and the widget shows a hint when there
|
|
19
|
+
* are none).
|
|
19
20
|
*/
|
|
20
21
|
export function computeSections(options) {
|
|
21
|
-
const { query, commands,
|
|
22
|
+
const { query, commands, agents, trans } = options;
|
|
22
23
|
const trimmed = query.trim();
|
|
23
24
|
let mode = 'all';
|
|
24
25
|
let term = trimmed;
|
|
@@ -31,16 +32,121 @@ export function computeSections(options) {
|
|
|
31
32
|
term = trimmed.slice(FILE_PREFIX.length).trim();
|
|
32
33
|
}
|
|
33
34
|
if (!term) {
|
|
34
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
recent: buildRecentItems(options, mode),
|
|
37
|
+
commands: [],
|
|
38
|
+
files: [],
|
|
39
|
+
agents: []
|
|
40
|
+
};
|
|
35
41
|
}
|
|
36
42
|
return {
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
recent: [],
|
|
44
|
+
commands: mode === 'files' ? [] : matchCommands(options, term),
|
|
45
|
+
files: mode === 'commands' ? [] : matchFiles(options, term),
|
|
39
46
|
// Agents only in the unprefixed view; the prompt is the full typed query.
|
|
40
47
|
agents: mode === 'all' ? buildAgentItems(commands, agents, term, trans) : []
|
|
41
48
|
};
|
|
42
49
|
}
|
|
43
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Run a command and record the use on success, so failed commands never enter
|
|
52
|
+
* the recents list.
|
|
53
|
+
*/
|
|
54
|
+
function executeCommand(commands, recents, id) {
|
|
55
|
+
void commands
|
|
56
|
+
.execute(id)
|
|
57
|
+
.then(() => {
|
|
58
|
+
recents === null || recents === void 0 ? void 0 : recents.touch('command', id);
|
|
59
|
+
})
|
|
60
|
+
.catch(reason => {
|
|
61
|
+
console.error(`xtralab omnibox: command "${id}" failed`, reason);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Open a file and record the use on success.
|
|
66
|
+
*/
|
|
67
|
+
function openFile(commands, recents, path) {
|
|
68
|
+
void commands
|
|
69
|
+
.execute('docmanager:open', { path })
|
|
70
|
+
.then(() => {
|
|
71
|
+
recents === null || recents === void 0 ? void 0 : recents.touch('file', path);
|
|
72
|
+
})
|
|
73
|
+
.catch(reason => {
|
|
74
|
+
console.error(`xtralab omnibox: failed to open "${path}"`, reason);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Read a command's label, visibility and caption, or `null` when the command
|
|
79
|
+
* is missing, hidden, label-less, or its accessors throw (a command's
|
|
80
|
+
* accessors can assume a context — e.g. an active notebook — the omnibox
|
|
81
|
+
* doesn't provide).
|
|
82
|
+
*/
|
|
83
|
+
function commandDisplay(commands, id) {
|
|
84
|
+
if (!commands.hasCommand(id)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const label = commands.label(id);
|
|
89
|
+
if (!label || !commands.isVisible(id)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return { label, caption: commands.caption(id) };
|
|
93
|
+
}
|
|
94
|
+
catch (_a) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The recently used rows for the empty term: both kinds interleaved by
|
|
100
|
+
* recency in the unprefixed view, or only the mode's kind under a bare `>` or
|
|
101
|
+
* `/`. Commands that no longer resolve are dropped, as are files missing from
|
|
102
|
+
* the loaded workspace listing (while the listing is still loading — or
|
|
103
|
+
* unavailable — file entries are shown as recorded).
|
|
104
|
+
*/
|
|
105
|
+
function buildRecentItems(options, mode) {
|
|
106
|
+
const { commands, docRegistry, files, recents } = options;
|
|
107
|
+
if (!recents) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const kind = mode === 'commands' ? 'command' : mode === 'files' ? 'file' : undefined;
|
|
111
|
+
const knownFiles = files.length > 0 ? new Set(files) : null;
|
|
112
|
+
const items = [];
|
|
113
|
+
for (const entry of recents.entries(kind)) {
|
|
114
|
+
if (entry.kind === 'command') {
|
|
115
|
+
const display = commandDisplay(commands, entry.id);
|
|
116
|
+
if (!display) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
items.push({
|
|
120
|
+
kind: 'command',
|
|
121
|
+
key: `recent:command:${entry.id}`,
|
|
122
|
+
label: display.label,
|
|
123
|
+
matchIndices: [],
|
|
124
|
+
caption: display.caption || undefined,
|
|
125
|
+
execute: () => {
|
|
126
|
+
executeCommand(commands, recents, entry.id);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
if (knownFiles && !knownFiles.has(entry.id)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
items.push({
|
|
135
|
+
kind: 'file',
|
|
136
|
+
key: `recent:file:${entry.id}`,
|
|
137
|
+
label: entry.id,
|
|
138
|
+
matchIndices: [],
|
|
139
|
+
icon: fileIconForPath(docRegistry, entry.id),
|
|
140
|
+
execute: () => {
|
|
141
|
+
openFile(commands, recents, entry.id);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return items;
|
|
147
|
+
}
|
|
148
|
+
function matchCommands(options, term) {
|
|
149
|
+
const { commands, recents } = options;
|
|
44
150
|
const query = term.toLowerCase();
|
|
45
151
|
const scored = [];
|
|
46
152
|
for (const id of commands.listCommands()) {
|
|
@@ -73,9 +179,7 @@ function matchCommands(commands, term) {
|
|
|
73
179
|
matchIndices: match.indices,
|
|
74
180
|
caption: caption || undefined,
|
|
75
181
|
execute: () => {
|
|
76
|
-
|
|
77
|
-
console.error(`xtralab omnibox: command "${id}" failed`, reason);
|
|
78
|
-
});
|
|
182
|
+
executeCommand(commands, recents, id);
|
|
79
183
|
}
|
|
80
184
|
}
|
|
81
185
|
});
|
|
@@ -83,7 +187,8 @@ function matchCommands(commands, term) {
|
|
|
83
187
|
scored.sort((a, b) => a.score - b.score);
|
|
84
188
|
return scored.slice(0, COMMAND_LIMIT).map(entry => entry.item);
|
|
85
189
|
}
|
|
86
|
-
function matchFiles(
|
|
190
|
+
function matchFiles(options, term) {
|
|
191
|
+
const { commands, docRegistry, files, recents } = options;
|
|
87
192
|
const query = term.toLowerCase();
|
|
88
193
|
const scored = [];
|
|
89
194
|
for (const path of files) {
|
|
@@ -100,9 +205,7 @@ function matchFiles(commands, docRegistry, files, term) {
|
|
|
100
205
|
matchIndices: indices,
|
|
101
206
|
icon: fileIconForPath(docRegistry, path),
|
|
102
207
|
execute: () => {
|
|
103
|
-
|
|
104
|
-
console.error(`xtralab omnibox: failed to open "${path}"`, reason);
|
|
105
|
-
});
|
|
208
|
+
openFile(commands, recents, path);
|
|
106
209
|
}
|
|
107
210
|
}));
|
|
108
211
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { IStateDB } from '@jupyterlab/statedb';
|
|
2
|
+
/**
|
|
3
|
+
* Kinds of omnibox results whose uses are recorded.
|
|
4
|
+
*/
|
|
5
|
+
export type RecentKind = 'command' | 'file';
|
|
6
|
+
/**
|
|
7
|
+
* A single recorded use: a command that was run or a file that was opened.
|
|
8
|
+
*/
|
|
9
|
+
interface IRecentEntry {
|
|
10
|
+
kind: RecentKind;
|
|
11
|
+
/**
|
|
12
|
+
* The command id, or the workspace-relative file path.
|
|
13
|
+
*/
|
|
14
|
+
id: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A most-recently-used list of omnibox results, generic over the result kind
|
|
18
|
+
* so commands and files (and future kinds) share one store. Entries are kept
|
|
19
|
+
* most-recent-first, capped at `maxItems` per kind, and persisted through the
|
|
20
|
+
* state database so they survive reloads. Without a state database the list
|
|
21
|
+
* still works for the lifetime of the page.
|
|
22
|
+
*/
|
|
23
|
+
export declare class OmniboxRecents {
|
|
24
|
+
constructor(options?: OmniboxRecents.IOptions);
|
|
25
|
+
/**
|
|
26
|
+
* Maximum number of entries remembered per kind. Lowering it trims the list
|
|
27
|
+
* immediately; `0` disables recents entirely.
|
|
28
|
+
*/
|
|
29
|
+
get maxItems(): number;
|
|
30
|
+
set maxItems(value: number);
|
|
31
|
+
/**
|
|
32
|
+
* The recorded entries, most recent first — all of them, or only those of
|
|
33
|
+
* one kind.
|
|
34
|
+
*/
|
|
35
|
+
entries(kind?: RecentKind): IRecentEntry[];
|
|
36
|
+
/**
|
|
37
|
+
* Record a use of `id`, moving it to the front of its kind's list.
|
|
38
|
+
*/
|
|
39
|
+
touch(kind: RecentKind, id: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Load the persisted list. Entries recorded before the load completes stay
|
|
42
|
+
* at the front, ahead of the restored history.
|
|
43
|
+
*/
|
|
44
|
+
restore(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Keep at most `maxItems` entries of each kind, preserving order.
|
|
47
|
+
*/
|
|
48
|
+
private _trim;
|
|
49
|
+
private _save;
|
|
50
|
+
private _state;
|
|
51
|
+
private _entries;
|
|
52
|
+
private _maxItems;
|
|
53
|
+
}
|
|
54
|
+
export declare namespace OmniboxRecents {
|
|
55
|
+
/**
|
|
56
|
+
* Construction options for {@link OmniboxRecents}.
|
|
57
|
+
*/
|
|
58
|
+
interface IOptions {
|
|
59
|
+
/**
|
|
60
|
+
* The state database to persist through, if available.
|
|
61
|
+
*/
|
|
62
|
+
state?: IStateDB | null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State database key the recents list is persisted under.
|
|
3
|
+
*/
|
|
4
|
+
const STATE_KEY = 'xtralab:omnibox:recents';
|
|
5
|
+
/**
|
|
6
|
+
* A most-recently-used list of omnibox results, generic over the result kind
|
|
7
|
+
* so commands and files (and future kinds) share one store. Entries are kept
|
|
8
|
+
* most-recent-first, capped at `maxItems` per kind, and persisted through the
|
|
9
|
+
* state database so they survive reloads. Without a state database the list
|
|
10
|
+
* still works for the lifetime of the page.
|
|
11
|
+
*/
|
|
12
|
+
export class OmniboxRecents {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
var _a;
|
|
15
|
+
this._entries = [];
|
|
16
|
+
this._maxItems = 5;
|
|
17
|
+
this._state = (_a = options.state) !== null && _a !== void 0 ? _a : null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Maximum number of entries remembered per kind. Lowering it trims the list
|
|
21
|
+
* immediately; `0` disables recents entirely.
|
|
22
|
+
*/
|
|
23
|
+
get maxItems() {
|
|
24
|
+
return this._maxItems;
|
|
25
|
+
}
|
|
26
|
+
set maxItems(value) {
|
|
27
|
+
const normalized = Math.max(0, Math.floor(value));
|
|
28
|
+
if (normalized === this._maxItems) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this._maxItems = normalized;
|
|
32
|
+
const trimmed = this._trim(this._entries);
|
|
33
|
+
if (trimmed.length !== this._entries.length) {
|
|
34
|
+
this._entries = trimmed;
|
|
35
|
+
this._save();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The recorded entries, most recent first — all of them, or only those of
|
|
40
|
+
* one kind.
|
|
41
|
+
*/
|
|
42
|
+
entries(kind) {
|
|
43
|
+
return kind
|
|
44
|
+
? this._entries.filter(entry => entry.kind === kind)
|
|
45
|
+
: [...this._entries];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Record a use of `id`, moving it to the front of its kind's list.
|
|
49
|
+
*/
|
|
50
|
+
touch(kind, id) {
|
|
51
|
+
const rest = this._entries.filter(entry => !(entry.kind === kind && entry.id === id));
|
|
52
|
+
this._entries = this._trim([{ kind, id }, ...rest]);
|
|
53
|
+
this._save();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load the persisted list. Entries recorded before the load completes stay
|
|
57
|
+
* at the front, ahead of the restored history.
|
|
58
|
+
*/
|
|
59
|
+
async restore() {
|
|
60
|
+
if (!this._state) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let fetched;
|
|
64
|
+
try {
|
|
65
|
+
fetched = await this._state.fetch(STATE_KEY);
|
|
66
|
+
}
|
|
67
|
+
catch (_a) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const persisted = Array.isArray(fetched)
|
|
71
|
+
? fetched.filter((entry) => !!entry &&
|
|
72
|
+
typeof entry === 'object' &&
|
|
73
|
+
(entry.kind === 'command' ||
|
|
74
|
+
entry.kind === 'file') &&
|
|
75
|
+
typeof entry.id === 'string')
|
|
76
|
+
: [];
|
|
77
|
+
const merged = [...this._entries];
|
|
78
|
+
for (const entry of persisted) {
|
|
79
|
+
if (!merged.some(existing => existing.kind === entry.kind && existing.id === entry.id)) {
|
|
80
|
+
merged.push({ kind: entry.kind, id: entry.id });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
this._entries = this._trim(merged);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Keep at most `maxItems` entries of each kind, preserving order.
|
|
87
|
+
*/
|
|
88
|
+
_trim(entries) {
|
|
89
|
+
const counts = new Map();
|
|
90
|
+
return entries.filter(entry => {
|
|
91
|
+
var _a;
|
|
92
|
+
const count = (_a = counts.get(entry.kind)) !== null && _a !== void 0 ? _a : 0;
|
|
93
|
+
counts.set(entry.kind, count + 1);
|
|
94
|
+
return count < this._maxItems;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
_save() {
|
|
98
|
+
if (!this._state) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const value = this._entries.map(entry => ({
|
|
102
|
+
kind: entry.kind,
|
|
103
|
+
id: entry.id
|
|
104
|
+
}));
|
|
105
|
+
this._state.save(STATE_KEY, value).catch(reason => {
|
|
106
|
+
console.error('xtralab omnibox: failed to save recents', reason);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
package/lib/omnibox/tokens.d.ts
CHANGED
|
@@ -6,9 +6,13 @@ import { Token } from '@lumino/coreutils';
|
|
|
6
6
|
* by the top-bar command bar, which opens it on click.
|
|
7
7
|
*/
|
|
8
8
|
export interface IOmnibox {
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Open the overlay, optionally seeding the input with `query`.
|
|
11
|
+
*/
|
|
10
12
|
open(query?: string): void;
|
|
11
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Close the overlay if it is open.
|
|
15
|
+
*/
|
|
12
16
|
close(): void;
|
|
13
17
|
}
|
|
14
18
|
/**
|
package/lib/omnibox/widget.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { TranslationBundle } from '@jupyterlab/translation';
|
|
|
3
3
|
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
4
4
|
import type { CommandRegistry } from '@lumino/commands';
|
|
5
5
|
import type { IAgent } from '../launcher/agents';
|
|
6
|
+
import type { OmniboxRecents } from './recents';
|
|
6
7
|
/**
|
|
7
8
|
* The omnibox overlay widget. Hosts {@link OmniboxComponent} in a React root
|
|
8
9
|
* attached to `document.body`. It carries `jp-ThemedContainer` so JupyterLab's
|
|
@@ -18,19 +19,35 @@ export declare class OmniboxWidget extends ReactWidget {
|
|
|
18
19
|
* A namespace for `OmniboxWidget` statics.
|
|
19
20
|
*/
|
|
20
21
|
export declare namespace OmniboxWidget {
|
|
21
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Construction options for {@link OmniboxWidget}.
|
|
24
|
+
*/
|
|
22
25
|
interface IOptions {
|
|
23
26
|
commands: CommandRegistry;
|
|
24
27
|
docRegistry: DocumentRegistry;
|
|
25
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Snapshot of the available agents, read once when the overlay opens.
|
|
30
|
+
*/
|
|
26
31
|
agents: IAgent[];
|
|
27
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Placeholder text for the input.
|
|
34
|
+
*/
|
|
28
35
|
placeholder: string;
|
|
29
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Seed text for the input.
|
|
38
|
+
*/
|
|
30
39
|
initialQuery: string;
|
|
31
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Recently-used tracker; `null` disables the recent rows and recording.
|
|
42
|
+
*/
|
|
43
|
+
recents: OmniboxRecents | null;
|
|
44
|
+
/**
|
|
45
|
+
* Translation bundle for the overlay's own labels.
|
|
46
|
+
*/
|
|
32
47
|
trans: TranslationBundle;
|
|
33
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Dismiss the overlay (the plugin disposes the widget).
|
|
50
|
+
*/
|
|
34
51
|
onClose: () => void;
|
|
35
52
|
}
|
|
36
53
|
}
|
package/lib/omnibox/widget.js
CHANGED
|
@@ -2,9 +2,13 @@ import { ReactWidget } from '@jupyterlab/ui-components';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { computeSections } from './model';
|
|
4
4
|
import { loadWorkspaceFiles } from './files';
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* DOM id of the results listbox, referenced by the input's `aria-controls`.
|
|
7
|
+
*/
|
|
6
8
|
const LIST_ID = 'jp-xtralab-Omnibox-list';
|
|
7
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Stable DOM id for the result row at `index`, for `aria-activedescendant`.
|
|
11
|
+
*/
|
|
8
12
|
function optionId(index) {
|
|
9
13
|
return `jp-xtralab-Omnibox-option-${index}`;
|
|
10
14
|
}
|
|
@@ -35,7 +39,7 @@ function renderIcon(icon) {
|
|
|
35
39
|
return icon ? (React.createElement(icon.react, { tag: "span", className: "jp-xtralab-Omnibox-itemIcon" })) : (React.createElement("span", { className: "jp-xtralab-Omnibox-itemIcon" }));
|
|
36
40
|
}
|
|
37
41
|
function OmniboxComponent(props) {
|
|
38
|
-
const { commands, docRegistry, agents, placeholder, initialQuery, trans, onClose } = props;
|
|
42
|
+
const { commands, docRegistry, agents, placeholder, initialQuery, recents, trans, onClose } = props;
|
|
39
43
|
const [query, setQuery] = React.useState(initialQuery);
|
|
40
44
|
const [files, setFiles] = React.useState([]);
|
|
41
45
|
const [active, setActive] = React.useState(0);
|
|
@@ -56,8 +60,21 @@ function OmniboxComponent(props) {
|
|
|
56
60
|
cancelled = true;
|
|
57
61
|
};
|
|
58
62
|
}, []);
|
|
59
|
-
const sections = React.useMemo(() => computeSections({
|
|
60
|
-
|
|
63
|
+
const sections = React.useMemo(() => computeSections({
|
|
64
|
+
query,
|
|
65
|
+
commands,
|
|
66
|
+
docRegistry,
|
|
67
|
+
agents,
|
|
68
|
+
files,
|
|
69
|
+
recents,
|
|
70
|
+
trans
|
|
71
|
+
}), [query, commands, docRegistry, agents, files, recents, trans]);
|
|
72
|
+
const flat = React.useMemo(() => [
|
|
73
|
+
...sections.recent,
|
|
74
|
+
...sections.commands,
|
|
75
|
+
...sections.files,
|
|
76
|
+
...sections.agents
|
|
77
|
+
], [sections]);
|
|
61
78
|
const hasResults = flat.length > 0;
|
|
62
79
|
// Reset the highlighted row whenever the result set changes, and keep the
|
|
63
80
|
// index in range when results shrink.
|
|
@@ -125,7 +142,8 @@ function OmniboxComponent(props) {
|
|
|
125
142
|
item.caption ? (React.createElement("span", { className: "jp-xtralab-Omnibox-itemCaption" }, item.caption)) : null));
|
|
126
143
|
})));
|
|
127
144
|
};
|
|
128
|
-
const
|
|
145
|
+
const commandsStart = sections.recent.length;
|
|
146
|
+
const filesStart = commandsStart + sections.commands.length;
|
|
129
147
|
const agentsStart = filesStart + sections.files.length;
|
|
130
148
|
return (React.createElement("div", { className: "jp-xtralab-Omnibox-overlay", onMouseDown: event => {
|
|
131
149
|
if (event.target === event.currentTarget) {
|
|
@@ -135,7 +153,8 @@ function OmniboxComponent(props) {
|
|
|
135
153
|
React.createElement("div", { className: "jp-xtralab-Omnibox-panel", role: "dialog", "aria-modal": "true", "aria-label": placeholder, onMouseDown: event => event.stopPropagation() },
|
|
136
154
|
React.createElement("input", { ref: inputRef, className: "jp-xtralab-Omnibox-input", type: "text", spellCheck: false, placeholder: placeholder, "aria-label": placeholder, role: "combobox", "aria-autocomplete": "list", "aria-expanded": hasResults, "aria-controls": LIST_ID, "aria-activedescendant": hasResults ? optionId(active) : undefined, value: query, onChange: event => setQuery(event.target.value), onKeyDown: onKeyDown }),
|
|
137
155
|
React.createElement("div", { className: "jp-xtralab-Omnibox-results", id: LIST_ID, role: hasResults ? 'listbox' : undefined, ref: listRef }, hasResults ? (React.createElement(React.Fragment, null,
|
|
138
|
-
renderSection(trans.__('
|
|
156
|
+
renderSection(trans.__('Recently used'), sections.recent, 0),
|
|
157
|
+
renderSection(trans.__('Commands'), sections.commands, commandsStart),
|
|
139
158
|
renderSection(trans.__('Files'), sections.files, filesStart),
|
|
140
159
|
renderSection(trans.__('Ask an agent'), sections.agents, agentsStart))) : (React.createElement("div", { className: "jp-xtralab-Omnibox-empty" }, query.trim()
|
|
141
160
|
? trans.__('No matches.')
|
package/lib/showOutput/index.js
CHANGED
|
@@ -3,25 +3,12 @@ import { ICommandPalette } from '@jupyterlab/apputils';
|
|
|
3
3
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
4
4
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
5
5
|
import { Panel } from '@lumino/widgets';
|
|
6
|
+
import { coerceData } from '../mimeData';
|
|
6
7
|
const PLUGIN_ID = 'xtralab:show-output';
|
|
7
|
-
/** Render arbitrary rich content into a panel, with no notebook and no kernel. */
|
|
8
|
-
const SHOW_COMMAND = 'xtralab:show';
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
* MCP bridge sometimes serialize a spec (e.g. a Vega-Lite object) to a string;
|
|
12
|
-
* for `application/...json` MIME types we accept either and parse the string.
|
|
9
|
+
* Render arbitrary rich content into a panel, with no notebook and no kernel.
|
|
13
10
|
*/
|
|
14
|
-
|
|
15
|
-
if (typeof data === 'string' && /\+?json$/.test(mimeType)) {
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(data);
|
|
18
|
-
}
|
|
19
|
-
catch (_a) {
|
|
20
|
-
return data;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return data;
|
|
24
|
-
}
|
|
11
|
+
const SHOW_COMMAND = 'xtralab:show';
|
|
25
12
|
/**
|
|
26
13
|
* Contribute `xtralab:show`: render a single MIME bundle into a main-area
|
|
27
14
|
* panel using the application's render-mime registry.
|
|
@@ -92,7 +79,9 @@ const plugin = {
|
|
|
92
79
|
: trans.__('Output');
|
|
93
80
|
const id = typeof args['id'] === 'string' ? args['id'] : 'default';
|
|
94
81
|
const area = args['area'] === 'main' ? 'main' : 'right';
|
|
95
|
-
const mode = typeof args['mode'] === 'string'
|
|
82
|
+
const mode = typeof args['mode'] === 'string' && isDocumentMode(args['mode'])
|
|
83
|
+
? args['mode']
|
|
84
|
+
: 'split-right';
|
|
96
85
|
const bundle = { [mimeType]: data };
|
|
97
86
|
const chosen = rendermime.preferredMimeType(bundle, 'any');
|
|
98
87
|
if (!chosen) {
|
|
@@ -125,7 +114,7 @@ const plugin = {
|
|
|
125
114
|
// only split the document area when `area` is "main".
|
|
126
115
|
if (area === 'main') {
|
|
127
116
|
labShell.add(widget, 'main', {
|
|
128
|
-
mode
|
|
117
|
+
mode,
|
|
129
118
|
activate: true
|
|
130
119
|
});
|
|
131
120
|
}
|
|
@@ -151,4 +140,12 @@ const plugin = {
|
|
|
151
140
|
}
|
|
152
141
|
}
|
|
153
142
|
};
|
|
143
|
+
function isDocumentMode(value) {
|
|
144
|
+
return (value === 'split-top' ||
|
|
145
|
+
value === 'split-left' ||
|
|
146
|
+
value === 'split-right' ||
|
|
147
|
+
value === 'split-bottom' ||
|
|
148
|
+
value === 'tab-before' ||
|
|
149
|
+
value === 'tab-after');
|
|
150
|
+
}
|
|
154
151
|
export default plugin;
|