xtralab 0.15.0 → 0.15.2
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 +65 -30
- package/lib/about/icons.d.ts +0 -5
- package/lib/about/icons.js +0 -5
- package/lib/about/index.d.ts +4 -8
- package/lib/about/index.js +6 -17
- package/lib/agentSessions.d.ts +9 -29
- package/lib/agentSessions.js +2 -7
- package/lib/askAgent/editorSelection.d.ts +20 -26
- package/lib/askAgent/editorSelection.js +11 -26
- package/lib/askAgent/icons.d.ts +2 -3
- package/lib/askAgent/icons.js +2 -3
- package/lib/askAgent/index.d.ts +4 -26
- package/lib/askAgent/index.js +54 -158
- package/lib/askAgent/popup.d.ts +12 -16
- package/lib/askAgent/popup.js +18 -37
- package/lib/askAgent/prompt.d.ts +3 -5
- package/lib/askAgent/prompt.js +6 -19
- package/lib/askAgent/queue.d.ts +11 -21
- package/lib/askAgent/queue.js +9 -19
- package/lib/askAgent/queuePanel.d.ts +13 -18
- package/lib/askAgent/queuePanel.js +15 -37
- package/lib/askAgent/targetPicker.d.ts +2 -9
- package/lib/askAgent/targetPicker.js +6 -11
- package/lib/askAgent/tokens.d.ts +32 -46
- package/lib/askAgent/tokens.js +4 -6
- package/lib/commandBar/index.d.ts +4 -13
- package/lib/commandBar/index.js +21 -39
- package/lib/customPanel/index.js +15 -0
- package/lib/editorBreadcrumbs/index.d.ts +3 -6
- package/lib/editorBreadcrumbs/index.js +10 -6
- package/lib/editorBreadcrumbs/widget.d.ts +17 -4
- package/lib/editorBreadcrumbs/widget.js +11 -17
- package/lib/editorIndent/index.d.ts +4 -12
- package/lib/editorIndent/index.js +8 -23
- package/lib/fileBrowser/commands.d.ts +29 -19
- package/lib/fileBrowser/commands.js +83 -97
- package/lib/fileBrowser/contents.d.ts +3 -0
- package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
- package/lib/fileBrowser/dragAndDrop.js +2 -7
- package/lib/fileBrowser/fileBrowser.d.ts +19 -9
- package/lib/fileBrowser/fileBrowser.js +70 -222
- package/lib/fileBrowser/gitStatus.d.ts +16 -10
- package/lib/fileBrowser/gitStatus.js +31 -30
- package/lib/fileBrowser/gitignore.d.ts +4 -17
- package/lib/fileBrowser/gitignore.js +4 -25
- package/lib/fileBrowser/icons.d.ts +7 -17
- package/lib/fileBrowser/icons.js +25 -80
- package/lib/fileBrowser/index.js +2 -4
- package/lib/fileBrowser/widget.d.ts +141 -48
- package/lib/fileBrowser/widget.js +102 -4
- package/lib/fileTypeIcons/index.js +0 -10
- package/lib/git/api.d.ts +5 -13
- package/lib/git/api.js +10 -36
- package/lib/git/askRequest.d.ts +4 -8
- package/lib/git/askRequest.js +6 -20
- package/lib/git/commands.d.ts +45 -5
- package/lib/git/commands.js +20 -13
- package/lib/git/diffModel.js +4 -0
- package/lib/git/diffPreferences.d.ts +90 -0
- package/lib/git/diffPreferences.js +107 -0
- package/lib/git/diffProvider.js +10 -2
- package/lib/git/diffSurface.d.ts +64 -63
- package/lib/git/diffSurface.js +72 -213
- package/lib/git/diffTheme.d.ts +7 -0
- package/lib/git/diffTheme.js +23 -0
- package/lib/git/diffWidget.d.ts +70 -14
- package/lib/git/diffWidget.js +100 -94
- package/lib/git/imageDiff.d.ts +14 -6
- package/lib/git/imageDiff.js +19 -75
- package/lib/git/index.d.ts +0 -3
- package/lib/git/index.js +44 -10
- package/lib/git/notebookDiff.d.ts +110 -33
- package/lib/git/notebookDiff.js +55 -197
- package/lib/git/tokens.d.ts +73 -31
- package/lib/git/tokens.js +2 -3
- package/lib/highlight/index.d.ts +4 -10
- package/lib/highlight/index.js +10 -41
- package/lib/index.d.ts +0 -9
- package/lib/index.js +0 -9
- package/lib/launcher/agents.d.ts +55 -51
- package/lib/launcher/agents.js +15 -57
- package/lib/launcher/availability.d.ts +3 -9
- package/lib/launcher/availability.js +3 -9
- package/lib/launcher/commands.d.ts +7 -32
- package/lib/launcher/commands.js +14 -45
- package/lib/launcher/dashboard.d.ts +19 -24
- package/lib/launcher/dashboard.js +21 -67
- package/lib/launcher/editorRegistry.d.ts +10 -22
- package/lib/launcher/editorRegistry.js +14 -18
- package/lib/launcher/editors.d.ts +39 -45
- package/lib/launcher/editors.js +8 -34
- package/lib/launcher/icons.d.ts +0 -12
- package/lib/launcher/icons.js +5 -39
- package/lib/launcher/index.js +12 -55
- package/lib/launcher/invocation.d.ts +4 -10
- package/lib/launcher/invocation.js +7 -22
- package/lib/launcher/registry.d.ts +9 -7
- package/lib/launcher/registry.js +9 -7
- package/lib/launcher/tokens.d.ts +10 -22
- package/lib/launcher/tokens.js +5 -8
- package/lib/menuBar/index.d.ts +3 -10
- package/lib/menuBar/index.js +11 -28
- package/lib/menus/index.d.ts +3 -9
- package/lib/menus/index.js +14 -30
- package/lib/omnibox/files.d.ts +2 -4
- package/lib/omnibox/files.js +5 -11
- package/lib/omnibox/index.d.ts +4 -14
- package/lib/omnibox/index.js +9 -27
- package/lib/omnibox/model.d.ts +38 -10
- package/lib/omnibox/model.js +15 -33
- package/lib/omnibox/recents.d.ts +15 -17
- package/lib/omnibox/recents.js +12 -17
- package/lib/omnibox/tokens.d.ts +5 -10
- package/lib/omnibox/tokens.js +2 -6
- package/lib/omnibox/widget.d.ts +11 -7
- package/lib/omnibox/widget.js +8 -16
- package/lib/searchReplace/index.d.ts +4 -20
- package/lib/searchReplace/index.js +7 -27
- package/lib/showOutput/index.d.ts +3 -12
- package/lib/showOutput/index.js +6 -22
- package/lib/sidebar/index.d.ts +4 -7
- package/lib/sidebar/index.js +5 -12
- package/lib/terminalNotifications/index.d.ts +4 -10
- package/lib/terminalNotifications/index.js +6 -20
- package/lib/terminals/agentTerminals.d.ts +12 -5
- package/lib/terminals/agentTerminals.js +25 -44
- package/lib/terminals/detection.d.ts +4 -8
- package/lib/terminals/detection.js +4 -8
- package/lib/terminals/index.d.ts +0 -35
- package/lib/terminals/index.js +12 -101
- package/lib/terminals/model.d.ts +64 -139
- package/lib/terminals/model.js +80 -220
- package/lib/terminals/tokens.d.ts +11 -22
- package/lib/terminals/tokens.js +2 -2
- package/lib/terminals/widget.d.ts +43 -13
- package/lib/terminals/widget.js +33 -18
- package/lib/topBar/icons.d.ts +3 -8
- package/lib/topBar/icons.js +3 -8
- package/lib/topBar/index.d.ts +4 -11
- package/lib/topBar/index.js +10 -38
- package/lib/walkthrough/index.d.ts +4 -9
- package/lib/walkthrough/index.js +6 -20
- package/lib/walkthrough/panel.d.ts +40 -9
- package/lib/walkthrough/panel.js +4 -10
- package/package.json +5 -3
- package/schema/git-diff-preferences.json +42 -0
- package/src/about/icons.ts +0 -5
- package/src/about/index.tsx +6 -17
- package/src/agentSessions.ts +9 -29
- package/src/askAgent/editorSelection.ts +22 -29
- package/src/askAgent/icons.ts +2 -3
- package/src/askAgent/index.ts +54 -158
- package/src/askAgent/popup.tsx +23 -45
- package/src/askAgent/prompt.ts +6 -19
- package/src/askAgent/queue.ts +13 -24
- package/src/askAgent/queuePanel.tsx +21 -46
- package/src/askAgent/targetPicker.tsx +5 -15
- package/src/askAgent/tokens.ts +32 -46
- package/src/commandBar/index.ts +24 -39
- package/src/customPanel/index.ts +15 -0
- package/src/editorBreadcrumbs/index.ts +10 -6
- package/src/editorBreadcrumbs/widget.ts +23 -17
- package/src/editorIndent/index.ts +17 -23
- package/src/fileBrowser/commands.ts +119 -96
- package/src/fileBrowser/contents.ts +3 -0
- package/src/fileBrowser/dragAndDrop.ts +17 -7
- package/src/fileBrowser/fileBrowser.tsx +86 -230
- package/src/fileBrowser/gitStatus.ts +36 -33
- package/src/fileBrowser/gitignore.ts +4 -25
- package/src/fileBrowser/icons.ts +25 -80
- package/src/fileBrowser/index.ts +2 -4
- package/src/fileBrowser/widget.tsx +171 -50
- package/src/fileTypeIcons/index.ts +0 -10
- package/src/git/api.ts +10 -36
- package/src/git/askRequest.ts +6 -20
- package/src/git/commands.ts +83 -11
- package/src/git/diffModel.ts +25 -0
- package/src/git/diffPreferences.ts +192 -0
- package/src/git/diffProvider.tsx +11 -2
- package/src/git/diffSurface.tsx +165 -286
- package/src/git/diffTheme.ts +24 -0
- package/src/git/diffWidget.tsx +194 -134
- package/src/git/imageDiff.tsx +56 -84
- package/src/git/index.ts +54 -10
- package/src/git/notebookDiff.tsx +214 -240
- package/src/git/tokens.ts +73 -31
- package/src/highlight/index.ts +10 -41
- package/src/index.ts +0 -9
- package/src/launcher/agents.ts +64 -95
- package/src/launcher/availability.ts +3 -9
- package/src/launcher/commands.ts +14 -51
- package/src/launcher/dashboard.tsx +49 -88
- package/src/launcher/editorRegistry.ts +21 -29
- package/src/launcher/editors.ts +39 -58
- package/src/launcher/icons.ts +5 -39
- package/src/launcher/index.ts +12 -55
- package/src/launcher/invocation.ts +7 -22
- package/src/launcher/registry.ts +9 -7
- package/src/launcher/tokens.ts +10 -22
- package/src/menuBar/index.ts +11 -28
- package/src/menus/index.ts +14 -30
- package/src/omnibox/files.ts +10 -14
- package/src/omnibox/index.ts +9 -27
- package/src/omnibox/model.ts +50 -38
- package/src/omnibox/recents.ts +17 -22
- package/src/omnibox/tokens.ts +5 -10
- package/src/omnibox/widget.tsx +14 -19
- package/src/searchReplace/index.ts +7 -27
- package/src/showOutput/index.ts +6 -22
- package/src/sidebar/index.ts +20 -12
- package/src/terminalNotifications/index.ts +42 -25
- package/src/terminals/agentTerminals.ts +25 -49
- package/src/terminals/detection.ts +4 -8
- package/src/terminals/index.ts +12 -101
- package/src/terminals/model.ts +130 -246
- package/src/terminals/tokens.ts +11 -22
- package/src/terminals/widget.tsx +48 -31
- package/src/topBar/icons.ts +3 -8
- package/src/topBar/index.ts +21 -44
- package/src/walkthrough/index.ts +6 -20
- package/src/walkthrough/panel.ts +40 -10
- package/style/about.css +2 -7
- package/style/askAgent.css +8 -40
- package/style/base.css +9 -45
- package/style/chrome.css +108 -308
- package/style/commandBar.css +4 -20
- package/style/customPanel.css +1 -4
- package/style/git.css +26 -186
- package/style/highlight.css +1 -3
- package/style/launcher.css +6 -70
- package/style/omnibox.css +2 -14
- package/style/showOutput.css +0 -6
- package/style/sidebar.css +29 -9
- package/style/tabs.css +5 -13
- package/style/terminals.css +6 -53
- package/style/topBar.css +2 -22
- package/style/walkthrough.css +0 -8
package/src/menus/index.ts
CHANGED
|
@@ -10,15 +10,9 @@ import type { Menu, MenuBar, Widget } from '@lumino/widgets';
|
|
|
10
10
|
const PLUGIN_ID = 'xtralab:menus';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Hide the
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* cannot meaningfully invoke; remove them until a notebook or console is
|
|
17
|
-
* opened, then restore them at their original ranks.
|
|
18
|
-
*
|
|
19
|
-
* Detection is duck-typed on `sessionContext`: NotebookPanel and ConsolePanel
|
|
20
|
-
* both expose it, which avoids pulling in `@jupyterlab/notebook` and
|
|
21
|
-
* `@jupyterlab/console` just to identify their widgets.
|
|
13
|
+
* Hide the Run and Kernel menus while no kernel-using widget is open in the
|
|
14
|
+
* main area, then restore them at their original ranks — the agent-first
|
|
15
|
+
* launcher does not use kernels, so a fresh workspace should not surface them.
|
|
22
16
|
*/
|
|
23
17
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
24
18
|
id: PLUGIN_ID,
|
|
@@ -31,17 +25,14 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
31
25
|
mainMenu: IMainMenu,
|
|
32
26
|
labShell: ILabShell
|
|
33
27
|
): void => {
|
|
34
|
-
// The interface types
|
|
35
|
-
//
|
|
36
|
-
// to MenuBar.removeMenu and IMainMenu.addMenu, which expect a `Menu`.
|
|
28
|
+
// The interface types extend IRankedMenu but the concrete instances are
|
|
29
|
+
// RankedMenu/Menu; cast for the MenuBar methods, which expect a `Menu`.
|
|
37
30
|
const menuBar = mainMenu as unknown as MenuBar;
|
|
38
31
|
const runMenu = mainMenu.runMenu as unknown as Menu;
|
|
39
32
|
const kernelMenu = mainMenu.kernelMenu as unknown as Menu;
|
|
40
33
|
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// back to the hard-coded defaults from `MainMenu` keeps things sane
|
|
44
|
-
// if the schema ever omits the rank.
|
|
34
|
+
// Schema-assigned ranks so the menus reappear in their original slots;
|
|
35
|
+
// fall back to MainMenu's hard-coded defaults if the schema omits them.
|
|
45
36
|
const runRank = mainMenu.runMenu.rank ?? 4;
|
|
46
37
|
const kernelRank = mainMenu.kernelMenu.rank ?? 5;
|
|
47
38
|
|
|
@@ -51,11 +42,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
51
42
|
const update = (): void => {
|
|
52
43
|
const show = hasKernelWidget(labShell);
|
|
53
44
|
if (show) {
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
// so re-adding lands the menu at the wrong position. Insert
|
|
57
|
-
// directly on the MenuBar by walking current menus and finding
|
|
58
|
-
// the first sibling with a higher rank.
|
|
45
|
+
// Not `mainMenu.addMenu`: its insertion index comes from a private
|
|
46
|
+
// `_items` array that `removeMenu` doesn't update, misplacing re-adds.
|
|
59
47
|
if (!isPresent(runMenu)) {
|
|
60
48
|
insertByRank(menuBar, runMenu, runRank);
|
|
61
49
|
}
|
|
@@ -80,11 +68,9 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
80
68
|
};
|
|
81
69
|
|
|
82
70
|
/**
|
|
83
|
-
* True iff any
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* `@jupyterlab/notebook` and `@jupyterlab/console` into xtralab's bundle
|
|
87
|
-
* just to do an `instanceof` check.
|
|
71
|
+
* True iff any main-area widget exposes a `sessionContext` (NotebookPanel,
|
|
72
|
+
* ConsolePanel, kernel-attached editors) — duck-typed to keep
|
|
73
|
+
* `@jupyterlab/notebook` and `@jupyterlab/console` out of the bundle.
|
|
88
74
|
*/
|
|
89
75
|
function hasKernelWidget(labShell: ILabShell): boolean {
|
|
90
76
|
for (const widget of labShell.widgets('main')) {
|
|
@@ -103,10 +89,8 @@ function hasSessionContext(widget: Widget): boolean {
|
|
|
103
89
|
}
|
|
104
90
|
|
|
105
91
|
/**
|
|
106
|
-
* Insert `menu`
|
|
107
|
-
*
|
|
108
|
-
* rank are treated as +Infinity so they sink to the end (matching the
|
|
109
|
-
* convention in `MenuFactory.createMenus`).
|
|
92
|
+
* Insert `menu` at the slot dictated by `rank`; rank-less menus count as
|
|
93
|
+
* +Infinity and sink to the end, matching `MenuFactory.createMenus`.
|
|
110
94
|
*/
|
|
111
95
|
function insertByRank(menuBar: MenuBar, menu: Menu, rank: number): void {
|
|
112
96
|
const menus = menuBar.menus;
|
package/src/omnibox/files.ts
CHANGED
|
@@ -2,23 +2,21 @@ import { URLExt } from '@jupyterlab/coreutils';
|
|
|
2
2
|
import { ServerConnection } from '@jupyterlab/services';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Shape of the `jupyterlab-quickopen` `api/files` response:
|
|
6
|
-
*
|
|
7
|
-
* bare filenames it contains.
|
|
5
|
+
* Shape of the `jupyterlab-quickopen` `api/files` response: directory
|
|
6
|
+
* (relative to the server root, `""` for the root) to bare filenames.
|
|
8
7
|
*/
|
|
9
8
|
interface IQuickOpenContents {
|
|
9
|
+
/**
|
|
10
|
+
* Filenames grouped by their containing directory.
|
|
11
|
+
*/
|
|
10
12
|
contents: { [dir: string]: string[] };
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Fetch and flatten the workspace file list from `jupyterlab-quickopen`'s
|
|
15
|
-
* server endpoint
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* listing honors the same server-side `.gitignore` filtering quickopen does
|
|
19
|
-
* (xtralab ships `respectGitignore: true`) in a single request. The endpoint
|
|
20
|
-
* only exists when quickopen's server extension is installed and the server
|
|
21
|
-
* reads a local filesystem; callers treat a rejection as "no file results".
|
|
17
|
+
* server endpoint, which applies server-side `.gitignore` filtering in one
|
|
18
|
+
* request. Rejects when the server extension is absent; callers treat that as
|
|
19
|
+
* "no file results".
|
|
22
20
|
*/
|
|
23
21
|
async function fetchWorkspaceFiles(respectGitignore = true): Promise<string[]> {
|
|
24
22
|
const settings = ServerConnection.makeSettings();
|
|
@@ -54,10 +52,8 @@ const CACHE_TTL_MS = 15_000;
|
|
|
54
52
|
let cache: { at: number; files: Promise<string[]> } | null = null;
|
|
55
53
|
|
|
56
54
|
/**
|
|
57
|
-
* The workspace file list, cached briefly so reopening the omnibox
|
|
58
|
-
*
|
|
59
|
-
* extension is absent) resolves to an empty list so file search degrades to
|
|
60
|
-
* "no results" instead of surfacing an error.
|
|
55
|
+
* The workspace file list, cached briefly so reopening the omnibox reuses one
|
|
56
|
+
* scan. A failed fetch resolves to an empty list.
|
|
61
57
|
*/
|
|
62
58
|
export function loadWorkspaceFiles(): Promise<string[]> {
|
|
63
59
|
const now = Date.now();
|
package/src/omnibox/index.ts
CHANGED
|
@@ -17,20 +17,10 @@ import { OmniboxWidget } from './widget';
|
|
|
17
17
|
const PLUGIN_ID = 'xtralab:omnibox';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* The omnibox: a
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* (running it in a fresh terminal through the launcher's
|
|
25
|
-
* `xtralab:start-agent:<id>` commands). It is opened by the top-bar command
|
|
26
|
-
* bar.
|
|
27
|
-
*
|
|
28
|
-
* Commands run and files opened through the overlay are remembered (persisted
|
|
29
|
-
* in the state database) and offered again at the top while the query is
|
|
30
|
-
* empty; the `maxNumberRecents` setting caps how many of each are kept.
|
|
31
|
-
*
|
|
32
|
-
* The agent rows come from the launcher's `IAgentRegistry`; when the launcher
|
|
33
|
-
* is disabled the omnibox still searches files and commands.
|
|
20
|
+
* The omnibox: a launcher overlay that fuzzy-searches workspace files and
|
|
21
|
+
* commands and routes a typed prompt to a configured agent in a fresh
|
|
22
|
+
* terminal. Recently used commands and files persist in the state database;
|
|
23
|
+
* without the launcher it still searches files and commands.
|
|
34
24
|
*/
|
|
35
25
|
const plugin: JupyterFrontEndPlugin<IOmnibox> = {
|
|
36
26
|
id: PLUGIN_ID,
|
|
@@ -57,12 +47,8 @@ const plugin: JupyterFrontEndPlugin<IOmnibox> = {
|
|
|
57
47
|
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
58
48
|
const placeholder = trans.__('Search files and commands, or ask an agent…');
|
|
59
49
|
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// exposes no item list and the widget itself is unreachable through the
|
|
63
|
-
// shell (`LabShell.add` defers it until layout restore, after which the
|
|
64
|
-
// modal setting re-parents it out of the left area). The wrapper's
|
|
65
|
-
// `_palette` field is private in TypeScript only, so read it guarded.
|
|
50
|
+
// `ICommandPalette` exposes no item list and the palette widget is
|
|
51
|
+
// unreachable through the shell; duck-read the wrapper's TS-only-private `_palette`.
|
|
66
52
|
const paletteItems = (): ReadonlyArray<CommandPalette.IItem> => {
|
|
67
53
|
const widget = (palette as unknown as { _palette?: unknown } | null)
|
|
68
54
|
?._palette;
|
|
@@ -71,10 +57,8 @@ const plugin: JupyterFrontEndPlugin<IOmnibox> = {
|
|
|
71
57
|
|
|
72
58
|
const recents = new OmniboxRecents({ state });
|
|
73
59
|
|
|
74
|
-
// Restore
|
|
75
|
-
// `
|
|
76
|
-
// would permanently drop entries beyond it whenever the user configured a
|
|
77
|
-
// larger cap and the state fetch won the race against the settings load.
|
|
60
|
+
// Restore only after the configured cap is known: `restore` trims to
|
|
61
|
+
// `maxItems`, so restoring at the default could permanently drop entries.
|
|
78
62
|
if (settingRegistry) {
|
|
79
63
|
void settingRegistry
|
|
80
64
|
.load(PLUGIN_ID)
|
|
@@ -110,7 +94,7 @@ const plugin: JupyterFrontEndPlugin<IOmnibox> = {
|
|
|
110
94
|
|
|
111
95
|
const open = (query?: string): void => {
|
|
112
96
|
// Reopen fresh each time so the input resets and the agent snapshot is
|
|
113
|
-
// current; the file list
|
|
97
|
+
// current; the file list stays cached (files.ts).
|
|
114
98
|
close();
|
|
115
99
|
const widget = new OmniboxWidget({
|
|
116
100
|
commands,
|
|
@@ -136,7 +120,6 @@ const plugin: JupyterFrontEndPlugin<IOmnibox> = {
|
|
|
136
120
|
label: trans.__('Search…'),
|
|
137
121
|
caption: trans.__('Search files and commands, or ask an agent'),
|
|
138
122
|
execute: args => {
|
|
139
|
-
// Toggle: a second press (e.g. of the keyboard shortcut) closes it.
|
|
140
123
|
if (current && !current.isDisposed) {
|
|
141
124
|
close();
|
|
142
125
|
return;
|
|
@@ -146,7 +129,6 @@ const plugin: JupyterFrontEndPlugin<IOmnibox> = {
|
|
|
146
129
|
}
|
|
147
130
|
});
|
|
148
131
|
|
|
149
|
-
// Open the omnibox with Cmd/Ctrl+K, the common "command center" chord.
|
|
150
132
|
commands.addKeyBinding({
|
|
151
133
|
command: OMNIBOX_OPEN_COMMAND,
|
|
152
134
|
keys: ['Accel K'],
|
package/src/omnibox/model.ts
CHANGED
|
@@ -11,15 +11,15 @@ import { agentCommandId } from '../launcher/tokens';
|
|
|
11
11
|
|
|
12
12
|
import type { OmniboxRecents, RecentKind } from './recents';
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* The source a result row came from, used to group rows under a header.
|
|
16
|
-
*/
|
|
17
14
|
type OmniboxItemKind = 'command' | 'file' | 'agent';
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
17
|
* A single result row, with the action it runs when chosen.
|
|
21
18
|
*/
|
|
22
19
|
export interface IOmniboxItem {
|
|
20
|
+
/**
|
|
21
|
+
* The source category of the row: command, file, or agent.
|
|
22
|
+
*/
|
|
23
23
|
kind: OmniboxItemKind;
|
|
24
24
|
/**
|
|
25
25
|
* Stable React key.
|
|
@@ -55,49 +55,73 @@ interface IOmniboxSections {
|
|
|
55
55
|
* Recently used commands and files, shown while the term is empty.
|
|
56
56
|
*/
|
|
57
57
|
recent: IOmniboxItem[];
|
|
58
|
+
/**
|
|
59
|
+
* Commands matching the term.
|
|
60
|
+
*/
|
|
58
61
|
commands: IOmniboxItem[];
|
|
62
|
+
/**
|
|
63
|
+
* Workspace files matching the term.
|
|
64
|
+
*/
|
|
59
65
|
files: IOmniboxItem[];
|
|
66
|
+
/**
|
|
67
|
+
* Per-agent "Ask" rows carrying the term as a prompt.
|
|
68
|
+
*/
|
|
60
69
|
agents: IOmniboxItem[];
|
|
61
70
|
}
|
|
62
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Inputs for {@link computeSections}.
|
|
74
|
+
*/
|
|
63
75
|
interface IComputeOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Raw query text from the input.
|
|
78
|
+
*/
|
|
64
79
|
query: string;
|
|
80
|
+
/**
|
|
81
|
+
* The command registry used to look up and execute commands.
|
|
82
|
+
*/
|
|
65
83
|
commands: CommandRegistry;
|
|
66
84
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
85
|
+
* Palette items, whose labels are computed with each item's args — the only
|
|
86
|
+
* form in which entries like "Use Theme: …" exist.
|
|
69
87
|
*/
|
|
70
88
|
paletteItems: ReadonlyArray<CommandPalette.IItem>;
|
|
89
|
+
/**
|
|
90
|
+
* The document registry used to pick file icons.
|
|
91
|
+
*/
|
|
71
92
|
docRegistry: DocumentRegistry;
|
|
93
|
+
/**
|
|
94
|
+
* The available agents; prompt-capable ones get "Ask" rows.
|
|
95
|
+
*/
|
|
72
96
|
agents: IAgent[];
|
|
97
|
+
/**
|
|
98
|
+
* Workspace-relative file paths to match against.
|
|
99
|
+
*/
|
|
73
100
|
files: string[];
|
|
74
101
|
/**
|
|
75
102
|
* Recently-used tracker; `null` disables the recent rows and recording.
|
|
76
103
|
*/
|
|
77
104
|
recents: OmniboxRecents | null;
|
|
105
|
+
/**
|
|
106
|
+
* Translation bundle for row labels.
|
|
107
|
+
*/
|
|
78
108
|
trans: TranslationBundle;
|
|
79
109
|
}
|
|
80
110
|
|
|
81
111
|
const COMMAND_LIMIT = 7;
|
|
82
112
|
const FILE_LIMIT = 10;
|
|
83
113
|
|
|
84
|
-
/**
|
|
85
|
-
* Prefixes that narrow the search to a single source: a leading `>` to
|
|
86
|
-
* commands, a leading `/` to files (relative paths never start with `/`, so it
|
|
87
|
-
* is unambiguous). Without a prefix, every source is searched.
|
|
88
|
-
*/
|
|
89
114
|
const COMMAND_PREFIX = '>';
|
|
115
|
+
// Unambiguous: workspace-relative paths never start with '/'.
|
|
90
116
|
const FILE_PREFIX = '/';
|
|
91
117
|
|
|
92
118
|
type Mode = 'all' | 'commands' | 'files';
|
|
93
119
|
|
|
94
120
|
/**
|
|
95
121
|
* Build the grouped result set for a query. A leading `>` searches only
|
|
96
|
-
* commands
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* rows for the active mode instead (and the widget shows a hint when there
|
|
100
|
-
* are none).
|
|
122
|
+
* commands, `/` only files; otherwise every prompt-capable agent also gets an
|
|
123
|
+
* "Ask" row carrying the query as its prompt. An empty term yields the
|
|
124
|
+
* recently used rows for the active mode.
|
|
101
125
|
*/
|
|
102
126
|
export function computeSections(options: IComputeOptions): IOmniboxSections {
|
|
103
127
|
const { query, commands, agents, trans } = options;
|
|
@@ -126,14 +150,12 @@ export function computeSections(options: IComputeOptions): IOmniboxSections {
|
|
|
126
150
|
recent: [],
|
|
127
151
|
commands: mode === 'files' ? [] : matchCommands(options, term),
|
|
128
152
|
files: mode === 'commands' ? [] : matchFiles(options, term),
|
|
129
|
-
// Agents only in the unprefixed view; the prompt is the full typed query.
|
|
130
153
|
agents: mode === 'all' ? buildAgentItems(commands, agents, term, trans) : []
|
|
131
154
|
};
|
|
132
155
|
}
|
|
133
156
|
|
|
134
157
|
/**
|
|
135
|
-
* Run a command and record the use on success, so
|
|
136
|
-
* the recents list.
|
|
158
|
+
* Run a command and record the use on success, so failures never enter the recents.
|
|
137
159
|
*/
|
|
138
160
|
function executeCommand(
|
|
139
161
|
commands: CommandRegistry,
|
|
@@ -151,9 +173,6 @@ function executeCommand(
|
|
|
151
173
|
});
|
|
152
174
|
}
|
|
153
175
|
|
|
154
|
-
/**
|
|
155
|
-
* Open a file and record the use on success.
|
|
156
|
-
*/
|
|
157
176
|
function openFile(
|
|
158
177
|
commands: CommandRegistry,
|
|
159
178
|
recents: OmniboxRecents | null,
|
|
@@ -171,9 +190,8 @@ function openFile(
|
|
|
171
190
|
|
|
172
191
|
/**
|
|
173
192
|
* Read a command's label, visibility and caption, or `null` when the command
|
|
174
|
-
* is missing, hidden, label-less, or its accessors throw (a
|
|
175
|
-
*
|
|
176
|
-
* doesn't provide).
|
|
193
|
+
* is missing, hidden, label-less, or its accessors throw (they can assume a
|
|
194
|
+
* context the omnibox doesn't provide).
|
|
177
195
|
*/
|
|
178
196
|
function commandDisplay(
|
|
179
197
|
commands: CommandRegistry,
|
|
@@ -196,10 +214,9 @@ function commandDisplay(
|
|
|
196
214
|
|
|
197
215
|
/**
|
|
198
216
|
* The recently used rows for the empty term: both kinds interleaved by
|
|
199
|
-
* recency
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* unavailable — file entries are shown as recorded).
|
|
217
|
+
* recency, or only the mode's kind under a bare `>` or `/`. Unresolvable
|
|
218
|
+
* commands are dropped, as are files absent from the workspace listing once
|
|
219
|
+
* it loads.
|
|
203
220
|
*/
|
|
204
221
|
function buildRecentItems(
|
|
205
222
|
options: IComputeOptions,
|
|
@@ -250,11 +267,10 @@ function buildRecentItems(
|
|
|
250
267
|
}
|
|
251
268
|
|
|
252
269
|
/**
|
|
253
|
-
* Match
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* pass so a command never also surfaces under its argless label.
|
|
270
|
+
* Match palette items first — their labels carry per-item args (one "Use
|
|
271
|
+
* Theme: …" per theme), which a registry scan can never produce — then
|
|
272
|
+
* registry commands the palette doesn't present, skipping palette-covered ids
|
|
273
|
+
* so a command never also surfaces under its argless label.
|
|
258
274
|
*/
|
|
259
275
|
function matchCommands(options: IComputeOptions, term: string): IOmniboxItem[] {
|
|
260
276
|
const { commands, paletteItems, recents } = options;
|
|
@@ -269,8 +285,6 @@ function matchCommands(options: IComputeOptions, term: string): IOmniboxItem[] {
|
|
|
269
285
|
let visible = true;
|
|
270
286
|
let caption = '';
|
|
271
287
|
try {
|
|
272
|
-
// An item's accessors can throw if the command assumes a context (e.g.
|
|
273
|
-
// an active notebook) the omnibox doesn't provide; skip those.
|
|
274
288
|
label = item.label;
|
|
275
289
|
visible = item.isVisible;
|
|
276
290
|
caption = item.caption;
|
|
@@ -311,8 +325,6 @@ function matchCommands(options: IComputeOptions, term: string): IOmniboxItem[] {
|
|
|
311
325
|
let visible = true;
|
|
312
326
|
let caption = '';
|
|
313
327
|
try {
|
|
314
|
-
// A command's accessors can throw if they assume a context (e.g. an
|
|
315
|
-
// active notebook) the omnibox doesn't provide; skip those.
|
|
316
328
|
label = commands.label(id);
|
|
317
329
|
visible = commands.isVisible(id);
|
|
318
330
|
caption = commands.caption(id);
|
|
@@ -385,7 +397,7 @@ function buildAgentItems(
|
|
|
385
397
|
const items: IOmniboxItem[] = [];
|
|
386
398
|
for (const agent of agents) {
|
|
387
399
|
// Agents without `promptArgs` can't take an inline prompt (the launcher
|
|
388
|
-
// would drop it)
|
|
400
|
+
// would drop it).
|
|
389
401
|
if (agent.promptArgs === undefined) {
|
|
390
402
|
continue;
|
|
391
403
|
}
|
package/src/omnibox/recents.ts
CHANGED
|
@@ -1,46 +1,41 @@
|
|
|
1
1
|
import type { IStateDB } from '@jupyterlab/statedb';
|
|
2
2
|
import type { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Kinds of omnibox results whose uses are recorded.
|
|
6
|
-
*/
|
|
7
4
|
export type RecentKind = 'command' | 'file';
|
|
8
5
|
|
|
9
6
|
/**
|
|
10
7
|
* A single recorded use: a command that was run or a file that was opened.
|
|
11
8
|
*/
|
|
12
9
|
interface IRecentEntry {
|
|
10
|
+
/**
|
|
11
|
+
* Whether the entry records a command run or a file opened.
|
|
12
|
+
*/
|
|
13
13
|
kind: RecentKind;
|
|
14
14
|
/**
|
|
15
15
|
* The command id, or the workspace-relative file path.
|
|
16
16
|
*/
|
|
17
17
|
id: string;
|
|
18
18
|
/**
|
|
19
|
-
* Arguments the command ran with, for palette-style entries whose label
|
|
20
|
-
*
|
|
19
|
+
* Arguments the command ran with, for palette-style entries whose label and
|
|
20
|
+
* behavior depend on them.
|
|
21
21
|
*/
|
|
22
22
|
args?: ReadonlyPartialJSONObject;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Identity of an entry: two uses only match when kind, id and args all do,
|
|
27
|
-
*
|
|
26
|
+
* Identity of an entry: two uses only match when kind, id and args all do, so
|
|
27
|
+
* runs of one command with different args stay distinct.
|
|
28
28
|
*/
|
|
29
29
|
function entryKey(entry: IRecentEntry): string {
|
|
30
30
|
return `${entry.kind}\0${entry.id}\0${JSON.stringify(entry.args ?? null)}`;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/**
|
|
34
|
-
* State database key the recents list is persisted under.
|
|
35
|
-
*/
|
|
36
33
|
const STATE_KEY = 'xtralab:omnibox:recents';
|
|
37
34
|
|
|
38
35
|
/**
|
|
39
|
-
* A most-recently-used list of omnibox results,
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* state database so they survive reloads. Without a state database the list
|
|
43
|
-
* still works for the lifetime of the page.
|
|
36
|
+
* A most-recently-used list of omnibox results, most-recent-first, capped at
|
|
37
|
+
* `maxItems` per kind and persisted through the state database. Without a
|
|
38
|
+
* state database it still works for the lifetime of the page.
|
|
44
39
|
*/
|
|
45
40
|
export class OmniboxRecents {
|
|
46
41
|
constructor(options: OmniboxRecents.IOptions = {}) {
|
|
@@ -48,12 +43,14 @@ export class OmniboxRecents {
|
|
|
48
43
|
}
|
|
49
44
|
|
|
50
45
|
/**
|
|
51
|
-
* Maximum
|
|
52
|
-
* immediately; `0` disables recents entirely.
|
|
46
|
+
* Maximum entries kept per kind; lowering trims immediately, `0` disables recents.
|
|
53
47
|
*/
|
|
54
48
|
get maxItems(): number {
|
|
55
49
|
return this._maxItems;
|
|
56
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Set the maximum entries kept per kind.
|
|
53
|
+
*/
|
|
57
54
|
set maxItems(value: number) {
|
|
58
55
|
const normalized = Math.max(0, Math.floor(value));
|
|
59
56
|
if (normalized === this._maxItems) {
|
|
@@ -68,8 +65,7 @@ export class OmniboxRecents {
|
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
/**
|
|
71
|
-
* The recorded entries, most recent first
|
|
72
|
-
* one kind.
|
|
68
|
+
* The recorded entries, most recent first, optionally filtered to one kind.
|
|
73
69
|
*/
|
|
74
70
|
entries(kind?: RecentKind): IRecentEntry[] {
|
|
75
71
|
return kind
|
|
@@ -78,8 +74,7 @@ export class OmniboxRecents {
|
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
/**
|
|
81
|
-
* Record a use
|
|
82
|
-
* the front of its kind's list.
|
|
77
|
+
* Record a use, moving the entry to the front of its kind's list.
|
|
83
78
|
*/
|
|
84
79
|
touch(kind: RecentKind, id: string, args?: ReadonlyPartialJSONObject): void {
|
|
85
80
|
const entry: IRecentEntry =
|
|
@@ -92,7 +87,7 @@ export class OmniboxRecents {
|
|
|
92
87
|
|
|
93
88
|
/**
|
|
94
89
|
* Load the persisted list. Entries recorded before the load completes stay
|
|
95
|
-
*
|
|
90
|
+
* ahead of the restored history.
|
|
96
91
|
*/
|
|
97
92
|
async restore(): Promise<void> {
|
|
98
93
|
if (!this._state) {
|
package/src/omnibox/tokens.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Token } from '@lumino/coreutils';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A launcher overlay that fuzzy-searches
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* by the top-bar command bar, which opens it on click.
|
|
4
|
+
* A launcher overlay that fuzzy-searches workspace files and commands and
|
|
5
|
+
* routes a typed prompt to a configured agent. Provided by `xtralab:omnibox`;
|
|
6
|
+
* opened by the top-bar command bar.
|
|
8
7
|
*/
|
|
9
8
|
export interface IOmnibox {
|
|
10
9
|
/**
|
|
@@ -17,17 +16,13 @@ export interface IOmnibox {
|
|
|
17
16
|
close(): void;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
/**
|
|
21
|
-
* DI token for {@link IOmnibox}. Provided by `xtralab:omnibox`; consumed by
|
|
22
|
-
* `xtralab:command-bar` so the top-bar pill opens the omnibox.
|
|
23
|
-
*/
|
|
24
19
|
export const IOmnibox = new Token<IOmnibox>(
|
|
25
20
|
'xtralab:IOmnibox',
|
|
26
21
|
'Opens the omnibox overlay that searches files and commands and prompts agents.'
|
|
27
22
|
);
|
|
28
23
|
|
|
29
24
|
/**
|
|
30
|
-
* Command id that opens the omnibox
|
|
31
|
-
*
|
|
25
|
+
* Command id that opens the omnibox, shared so the command bar can look up
|
|
26
|
+
* its keybinding for the shortcut hint.
|
|
32
27
|
*/
|
|
33
28
|
export const OMNIBOX_OPEN_COMMAND = 'xtralab:omnibox:open';
|
package/src/omnibox/widget.tsx
CHANGED
|
@@ -11,21 +11,15 @@ import { computeSections, IOmniboxItem } from './model';
|
|
|
11
11
|
import { loadWorkspaceFiles } from './files';
|
|
12
12
|
import type { OmniboxRecents } from './recents';
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* DOM id of the results listbox, referenced by the input's `aria-controls`.
|
|
16
|
-
*/
|
|
17
14
|
const LIST_ID = 'jp-xtralab-Omnibox-list';
|
|
18
15
|
|
|
19
|
-
/**
|
|
20
|
-
* Stable DOM id for the result row at `index`, for `aria-activedescendant`.
|
|
21
|
-
*/
|
|
22
16
|
function optionId(index: number): string {
|
|
23
17
|
return `jp-xtralab-Omnibox-option-${index}`;
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
/**
|
|
27
21
|
* Render `text` with the fuzzy-matched characters at `indices` wrapped in a
|
|
28
|
-
* highlight span
|
|
22
|
+
* highlight span.
|
|
29
23
|
*/
|
|
30
24
|
function renderHighlight(
|
|
31
25
|
text: string,
|
|
@@ -125,13 +119,10 @@ function OmniboxComponent(props: OmniboxWidget.IOptions): JSX.Element {
|
|
|
125
119
|
);
|
|
126
120
|
const hasResults = flat.length > 0;
|
|
127
121
|
|
|
128
|
-
// Reset the highlighted row whenever the result set changes, and keep the
|
|
129
|
-
// index in range when results shrink.
|
|
130
122
|
React.useEffect(() => {
|
|
131
123
|
setActive(current => (current < flat.length ? current : 0));
|
|
132
124
|
}, [flat]);
|
|
133
125
|
|
|
134
|
-
// Keep the highlighted row scrolled into view as it moves.
|
|
135
126
|
React.useEffect(() => {
|
|
136
127
|
listRef.current
|
|
137
128
|
?.querySelector('.jp-mod-active')
|
|
@@ -139,8 +130,8 @@ function OmniboxComponent(props: OmniboxWidget.IOptions): JSX.Element {
|
|
|
139
130
|
}, [active, flat]);
|
|
140
131
|
|
|
141
132
|
const dismiss = React.useCallback(() => {
|
|
142
|
-
// Defer so unmounting this React root
|
|
143
|
-
//
|
|
133
|
+
// Defer so unmounting this React root never happens synchronously inside
|
|
134
|
+
// the event handler that triggered it.
|
|
144
135
|
window.setTimeout(onClose, 0);
|
|
145
136
|
}, [onClose]);
|
|
146
137
|
|
|
@@ -307,10 +298,8 @@ function OmniboxComponent(props: OmniboxWidget.IOptions): JSX.Element {
|
|
|
307
298
|
}
|
|
308
299
|
|
|
309
300
|
/**
|
|
310
|
-
* The omnibox overlay widget
|
|
311
|
-
*
|
|
312
|
-
* theme CSS variables resolve outside the shell, and its own `jp-xtralab-Omnibox`
|
|
313
|
-
* class for the overlay/panel styling (style/omnibox.css).
|
|
301
|
+
* The omnibox overlay widget, attached to `document.body`; `jp-ThemedContainer`
|
|
302
|
+
* makes JupyterLab's theme CSS variables resolve outside the shell.
|
|
314
303
|
*/
|
|
315
304
|
export class OmniboxWidget extends ReactWidget {
|
|
316
305
|
constructor(options: OmniboxWidget.IOptions) {
|
|
@@ -321,6 +310,9 @@ export class OmniboxWidget extends ReactWidget {
|
|
|
321
310
|
this.addClass('jp-ThemedContainer');
|
|
322
311
|
}
|
|
323
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Render the omnibox overlay content.
|
|
315
|
+
*/
|
|
324
316
|
render(): JSX.Element {
|
|
325
317
|
return <OmniboxComponent {...this._options} />;
|
|
326
318
|
}
|
|
@@ -328,20 +320,23 @@ export class OmniboxWidget extends ReactWidget {
|
|
|
328
320
|
private _options: OmniboxWidget.IOptions;
|
|
329
321
|
}
|
|
330
322
|
|
|
331
|
-
/**
|
|
332
|
-
* A namespace for `OmniboxWidget` statics.
|
|
333
|
-
*/
|
|
334
323
|
export namespace OmniboxWidget {
|
|
335
324
|
/**
|
|
336
325
|
* Construction options for {@link OmniboxWidget}.
|
|
337
326
|
*/
|
|
338
327
|
export interface IOptions {
|
|
328
|
+
/**
|
|
329
|
+
* The command registry used to look up and execute commands.
|
|
330
|
+
*/
|
|
339
331
|
commands: CommandRegistry;
|
|
340
332
|
/**
|
|
341
333
|
* Snapshot of the command palette's items, read once when the overlay
|
|
342
334
|
* opens; empty when the palette isn't available.
|
|
343
335
|
*/
|
|
344
336
|
paletteItems: ReadonlyArray<CommandPalette.IItem>;
|
|
337
|
+
/**
|
|
338
|
+
* The document registry used to pick file icons.
|
|
339
|
+
*/
|
|
345
340
|
docRegistry: DocumentRegistry;
|
|
346
341
|
/**
|
|
347
342
|
* Snapshot of the available agents, read once when the overlay opens.
|