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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
2
|
+
import { addIcon, LabIcon } from '@jupyterlab/ui-components';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import type { IAgent } from '../launcher/agents';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A running agent terminal offered as a prompt target: the session data
|
|
9
|
+
* from `IAgentTerminals` plus the matching agent's icon, resolved by the
|
|
10
|
+
* plugin so these components stay presentation-only.
|
|
11
|
+
*/
|
|
12
|
+
export interface ISessionTarget {
|
|
13
|
+
/**
|
|
14
|
+
* The terminal session name.
|
|
15
|
+
*/
|
|
16
|
+
name: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The session's display label (usually the agent's name).
|
|
20
|
+
*/
|
|
21
|
+
label: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The agent's latest activity line, when one is available.
|
|
25
|
+
*/
|
|
26
|
+
activity: string | null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The running agent's icon.
|
|
30
|
+
*/
|
|
31
|
+
icon: LabIcon;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Keyboard support shared by the two radiogroups below, following the ARIA
|
|
36
|
+
* radio-group pattern: the group is one tab stop (only the checked radio is
|
|
37
|
+
* tabbable) and the arrow keys move the selection to the neighbouring
|
|
38
|
+
* radio, wrapping around. Selection follows focus, so the handler clicks
|
|
39
|
+
* the neighbour and the re-render moves the roving tabindex along.
|
|
40
|
+
*/
|
|
41
|
+
function onRadioGroupKeyDown(event: React.KeyboardEvent<HTMLElement>): void {
|
|
42
|
+
let delta: number;
|
|
43
|
+
switch (event.key) {
|
|
44
|
+
case 'ArrowRight':
|
|
45
|
+
case 'ArrowDown':
|
|
46
|
+
delta = 1;
|
|
47
|
+
break;
|
|
48
|
+
case 'ArrowLeft':
|
|
49
|
+
case 'ArrowUp':
|
|
50
|
+
delta = -1;
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const radios = Array.from(
|
|
56
|
+
event.currentTarget.querySelectorAll<HTMLButtonElement>('[role="radio"]')
|
|
57
|
+
);
|
|
58
|
+
const index = radios.findIndex(radio => radio === event.target);
|
|
59
|
+
if (index === -1) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
const next = radios[(index + delta + radios.length) % radios.length];
|
|
64
|
+
next.focus();
|
|
65
|
+
next.click();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The chip row picking where a prompt goes: "New terminal" plus one chip
|
|
70
|
+
* per running agent session. Renders nothing while no agent session is
|
|
71
|
+
* running (a new terminal is then the only possibility and the row would
|
|
72
|
+
* be noise).
|
|
73
|
+
*/
|
|
74
|
+
export function TargetChips(props: {
|
|
75
|
+
/**
|
|
76
|
+
* Agents that could start in a new terminal; gates the "New terminal" chip.
|
|
77
|
+
*/
|
|
78
|
+
agents: IAgent[];
|
|
79
|
+
/**
|
|
80
|
+
* The running agent sessions.
|
|
81
|
+
*/
|
|
82
|
+
targets: ISessionTarget[];
|
|
83
|
+
/**
|
|
84
|
+
* The selected session name, or `null` for a new terminal.
|
|
85
|
+
*/
|
|
86
|
+
targetName: string | null;
|
|
87
|
+
trans: TranslationBundle;
|
|
88
|
+
onSelect: (targetName: string | null) => void;
|
|
89
|
+
}): JSX.Element | null {
|
|
90
|
+
const { agents, targets, targetName, trans, onSelect } = props;
|
|
91
|
+
if (targets.length === 0) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
className="jp-xtralab-AskAgent-targets"
|
|
97
|
+
role="radiogroup"
|
|
98
|
+
aria-label={trans.__('Send to')}
|
|
99
|
+
onKeyDown={onRadioGroupKeyDown}
|
|
100
|
+
>
|
|
101
|
+
{agents.length > 0 && (
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
role="radio"
|
|
105
|
+
aria-checked={targetName === null}
|
|
106
|
+
tabIndex={targetName === null ? 0 : -1}
|
|
107
|
+
title={trans.__('Start the agent in a new terminal')}
|
|
108
|
+
className={
|
|
109
|
+
'jp-xtralab-AskAgent-targetButton' +
|
|
110
|
+
(targetName === null ? ' jp-mod-selected' : '')
|
|
111
|
+
}
|
|
112
|
+
// Keep focus where the user is typing while picking a target.
|
|
113
|
+
onMouseDown={event => event.preventDefault()}
|
|
114
|
+
onClick={() => onSelect(null)}
|
|
115
|
+
>
|
|
116
|
+
<addIcon.react
|
|
117
|
+
tag="span"
|
|
118
|
+
className="jp-xtralab-AskAgent-targetIcon"
|
|
119
|
+
/>
|
|
120
|
+
<span className="jp-xtralab-AskAgent-targetLabel">
|
|
121
|
+
{trans.__('New terminal')}
|
|
122
|
+
</span>
|
|
123
|
+
</button>
|
|
124
|
+
)}
|
|
125
|
+
{targets.map(target => (
|
|
126
|
+
<button
|
|
127
|
+
key={target.name}
|
|
128
|
+
type="button"
|
|
129
|
+
role="radio"
|
|
130
|
+
aria-checked={target.name === targetName}
|
|
131
|
+
tabIndex={target.name === targetName ? 0 : -1}
|
|
132
|
+
title={
|
|
133
|
+
target.activity
|
|
134
|
+
? trans.__(
|
|
135
|
+
'%1 · %2 — %3',
|
|
136
|
+
target.label,
|
|
137
|
+
target.name,
|
|
138
|
+
target.activity
|
|
139
|
+
)
|
|
140
|
+
: trans.__('%1 · %2', target.label, target.name)
|
|
141
|
+
}
|
|
142
|
+
className={
|
|
143
|
+
'jp-xtralab-AskAgent-targetButton' +
|
|
144
|
+
(target.name === targetName ? ' jp-mod-selected' : '')
|
|
145
|
+
}
|
|
146
|
+
onMouseDown={event => event.preventDefault()}
|
|
147
|
+
onClick={() => onSelect(target.name)}
|
|
148
|
+
>
|
|
149
|
+
<target.icon.react
|
|
150
|
+
tag="span"
|
|
151
|
+
className="jp-xtralab-AskAgent-targetIcon"
|
|
152
|
+
/>
|
|
153
|
+
<span className="jp-xtralab-AskAgent-targetLabel">
|
|
154
|
+
{target.label}
|
|
155
|
+
</span>
|
|
156
|
+
<span className="jp-xtralab-AskAgent-targetName">{target.name}</span>
|
|
157
|
+
</button>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The icon radiogroup picking which agent a new terminal starts with.
|
|
165
|
+
* Renders nothing when no agent accepts an initial prompt.
|
|
166
|
+
*/
|
|
167
|
+
export function AgentChoices(props: {
|
|
168
|
+
agents: IAgent[];
|
|
169
|
+
agentId: string;
|
|
170
|
+
trans: TranslationBundle;
|
|
171
|
+
onSelect: (agentId: string) => void;
|
|
172
|
+
}): JSX.Element | null {
|
|
173
|
+
const { agents, agentId, trans, onSelect } = props;
|
|
174
|
+
if (agents.length === 0) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
return (
|
|
178
|
+
<div
|
|
179
|
+
className="jp-xtralab-AskAgent-agents"
|
|
180
|
+
role="radiogroup"
|
|
181
|
+
aria-label={trans.__('Agent')}
|
|
182
|
+
onKeyDown={onRadioGroupKeyDown}
|
|
183
|
+
>
|
|
184
|
+
{agents.map(agent => (
|
|
185
|
+
<button
|
|
186
|
+
key={agent.id}
|
|
187
|
+
type="button"
|
|
188
|
+
role="radio"
|
|
189
|
+
aria-checked={agent.id === agentId}
|
|
190
|
+
tabIndex={agent.id === agentId ? 0 : -1}
|
|
191
|
+
title={agent.label}
|
|
192
|
+
aria-label={agent.label}
|
|
193
|
+
className={
|
|
194
|
+
'jp-xtralab-AskAgent-agentButton' +
|
|
195
|
+
(agent.id === agentId ? ' jp-mod-selected' : '')
|
|
196
|
+
}
|
|
197
|
+
// Keep focus where the user is typing while picking an agent.
|
|
198
|
+
onMouseDown={event => event.preventDefault()}
|
|
199
|
+
onClick={() => onSelect(agent.id)}
|
|
200
|
+
>
|
|
201
|
+
<agent.icon.react
|
|
202
|
+
tag="span"
|
|
203
|
+
className="jp-xtralab-AskAgent-agentIcon"
|
|
204
|
+
/>
|
|
205
|
+
</button>
|
|
206
|
+
))}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Token } from '@lumino/coreutils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The command id that opens the ask-agent popup for the current text-editor
|
|
5
|
+
* selection. Registered by `xtralab:ask-agent`; exposed here so menus and
|
|
6
|
+
* other plugins can reference it without importing the plugin internals.
|
|
7
|
+
*/
|
|
8
|
+
export const ASK_AGENT_COMMAND = 'xtralab:ask-agent';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A code location (and optionally the selected source text) that a prompt
|
|
12
|
+
* should point an agent at. The structured fields are composed into the
|
|
13
|
+
* final prompt by the ask-agent plugin so every entry point — editor
|
|
14
|
+
* selection, diff line selection — produces consistently worded prompts.
|
|
15
|
+
*/
|
|
16
|
+
export interface IAskAgentContext {
|
|
17
|
+
/**
|
|
18
|
+
* Path of the file the selection belongs to, relative to {@link cwd} (or
|
|
19
|
+
* to the server root when no cwd is given) so the launched agent can open
|
|
20
|
+
* it directly.
|
|
21
|
+
*/
|
|
22
|
+
path: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Server-relative directory the agent's terminal starts in. Omit to start
|
|
26
|
+
* in the server root. Diff selections pass the git repository root here so
|
|
27
|
+
* the agent can run git commands right away.
|
|
28
|
+
*/
|
|
29
|
+
cwd?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The notebook cell the selection lives in, when {@link path} is a
|
|
33
|
+
* notebook. `index` is the cell's 0-based position in the nbformat
|
|
34
|
+
* `cells` array; `type` is the nbformat cell type (`code`, `markdown`,
|
|
35
|
+
* `raw`). When set, {@link startLine}/{@link endLine} count within the
|
|
36
|
+
* cell's source rather than within a file.
|
|
37
|
+
*/
|
|
38
|
+
cell?: {
|
|
39
|
+
index: number;
|
|
40
|
+
type: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* First selected line, 1-indexed and inclusive. Omit when no line range is
|
|
45
|
+
* known (the prompt then only references the file).
|
|
46
|
+
*/
|
|
47
|
+
startLine?: number;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Last selected line, 1-indexed and inclusive. Defaults to
|
|
51
|
+
* {@link startLine} when omitted.
|
|
52
|
+
*/
|
|
53
|
+
endLine?: number;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether {@link startLine}/{@link endLine} index the file's current
|
|
57
|
+
* working-tree content. `false` for diff selections taken from another
|
|
58
|
+
* revision (the old side, or a challenger that is not the working tree),
|
|
59
|
+
* whose numbers must not be used to highlight the working file. Omitted
|
|
60
|
+
* means `true`.
|
|
61
|
+
*/
|
|
62
|
+
linesInWorkingFile?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The selected source text. May be empty when only a location is known;
|
|
66
|
+
* the prompt then relies on the path and line range alone.
|
|
67
|
+
*/
|
|
68
|
+
text: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extra agent-facing locator appended after the line range in the prompt,
|
|
72
|
+
* e.g. `the old side (INDEX) of the git diff`. Written in English because
|
|
73
|
+
* it is consumed by the agent, not shown in the UI.
|
|
74
|
+
*/
|
|
75
|
+
location?: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Short user-facing tag shown in the popup header next to the file name,
|
|
79
|
+
* e.g. "old version". Translated, unlike {@link location}. Omit when the
|
|
80
|
+
* range reads naturally as the file's current content.
|
|
81
|
+
*/
|
|
82
|
+
note?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Where a submitted prompt goes: a fresh terminal started with the chosen
|
|
87
|
+
* agent's command, or an existing terminal session whose running agent
|
|
88
|
+
* receives the prompt in its input box (queued by the agent itself when it
|
|
89
|
+
* is busy).
|
|
90
|
+
*/
|
|
91
|
+
export type AskAgentTarget =
|
|
92
|
+
| { kind: 'new'; agentId: string }
|
|
93
|
+
| { kind: 'session'; name: string };
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A request to open the ask-agent popup: what code to prompt about and
|
|
97
|
+
* where to place the popup on screen.
|
|
98
|
+
*/
|
|
99
|
+
export interface IAskAgentRequest {
|
|
100
|
+
/**
|
|
101
|
+
* The code location and selection the prompt is about.
|
|
102
|
+
*/
|
|
103
|
+
context: IAskAgentContext;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Viewport rectangle the popup anchors to (typically the selection end or
|
|
107
|
+
* the button that was clicked). `null` positions the popup near the top
|
|
108
|
+
* center of the viewport.
|
|
109
|
+
*/
|
|
110
|
+
anchor: DOMRect | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The ask-agent popup: a small floating prompt box that sends the given
|
|
115
|
+
* code selection, plus the user's typed instruction, to one of the
|
|
116
|
+
* configured coding agents — in a fresh terminal, or into an agent already
|
|
117
|
+
* running in an existing one.
|
|
118
|
+
*/
|
|
119
|
+
export interface IAskAgent {
|
|
120
|
+
/**
|
|
121
|
+
* Open the popup for `request`, replacing any popup already open.
|
|
122
|
+
*/
|
|
123
|
+
open(request: IAskAgentRequest): void;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Close the popup if it is open.
|
|
127
|
+
*/
|
|
128
|
+
close(): void;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* DI token for {@link IAskAgent}. Provided by `xtralab:ask-agent` and
|
|
133
|
+
* consumed — optionally, so diffs still render when the plugin is disabled —
|
|
134
|
+
* by the git diff plugins to offer "ask an agent about these lines".
|
|
135
|
+
*/
|
|
136
|
+
export const IAskAgent = new Token<IAskAgent>(
|
|
137
|
+
'xtralab:IAskAgent',
|
|
138
|
+
'A floating prompt box that sends the current code selection to a coding agent running in a terminal.'
|
|
139
|
+
);
|
package/src/commandBar/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ILabShell,
|
|
3
2
|
JupyterFrontEnd,
|
|
4
3
|
JupyterFrontEndPlugin
|
|
5
4
|
} from '@jupyterlab/application';
|
|
5
|
+
import { IToolbarWidgetRegistry } from '@jupyterlab/apputils';
|
|
6
6
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
7
7
|
import { searchIcon } from '@jupyterlab/ui-components';
|
|
8
8
|
import { CommandRegistry } from '@lumino/commands';
|
|
@@ -13,15 +13,24 @@ import { IOmnibox, OMNIBOX_OPEN_COMMAND } from '../omnibox/tokens';
|
|
|
13
13
|
const PLUGIN_ID = 'xtralab:command-bar';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the
|
|
16
|
+
* Factory name of JupyterLab's settings-driven top bar toolbar (`#jp-top-bar`,
|
|
17
|
+
* built by `@jupyterlab/application-extension:top-bar`).
|
|
18
|
+
*/
|
|
19
|
+
const TOPBAR_FACTORY = 'TopBar';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Name of the pill item within that toolbar. Its placement is declared under
|
|
23
|
+
* `jupyter.lab.toolbars` in this plugin's settings schema
|
|
24
|
+
* (schema/command-bar.json): the core toolbar ships a spacer at rank 50, so
|
|
25
|
+
* the pill's higher rank lands it at the trailing end of the toolbar, next to
|
|
26
|
+
* the right-sidebar toggle button.
|
|
27
|
+
*/
|
|
28
|
+
const ITEM_NAME = 'omnibox';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A search-bar-styled launcher pill. It holds no text input of its own:
|
|
32
|
+
* clicking it (or pressing Enter/Space while it is focused) runs `onActivate`,
|
|
33
|
+
* which opens the omnibox.
|
|
25
34
|
*/
|
|
26
35
|
class CommandBar extends Widget {
|
|
27
36
|
constructor(options: CommandBar.IOptions) {
|
|
@@ -43,9 +52,9 @@ class CommandBar extends Widget {
|
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
protected onAfterAttach(): void {
|
|
46
|
-
// The
|
|
47
|
-
//
|
|
48
|
-
//
|
|
55
|
+
// The click bubbles from the inner pill button up to the root node
|
|
56
|
+
// listened on here; keyboard Enter/Space on the focused button raises the
|
|
57
|
+
// same synthetic click.
|
|
49
58
|
this.node.addEventListener('click', this);
|
|
50
59
|
}
|
|
51
60
|
|
|
@@ -58,13 +67,21 @@ class CommandBar extends Widget {
|
|
|
58
67
|
|
|
59
68
|
namespace CommandBar {
|
|
60
69
|
export interface IOptions {
|
|
61
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Placeholder-style text shown inside the pill.
|
|
72
|
+
*/
|
|
62
73
|
label: string;
|
|
63
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Tooltip and accessible name for the button.
|
|
76
|
+
*/
|
|
64
77
|
caption: string;
|
|
65
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Formatted keyboard shortcut shown as a trailing hint, if any.
|
|
80
|
+
*/
|
|
66
81
|
shortcut?: string;
|
|
67
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Runs on click or keyboard activation.
|
|
84
|
+
*/
|
|
68
85
|
onActivate: () => void;
|
|
69
86
|
}
|
|
70
87
|
|
|
@@ -73,6 +90,10 @@ namespace CommandBar {
|
|
|
73
90
|
caption: string,
|
|
74
91
|
shortcut?: string
|
|
75
92
|
): HTMLElement {
|
|
93
|
+
// The root is a plain wrapper: as a toolbar item it receives the core
|
|
94
|
+
// `.jp-Toolbar > .jp-Toolbar-item` sizing (full height, centered flex),
|
|
95
|
+
// which would otherwise stretch the pill itself; the button inside keeps
|
|
96
|
+
// its own compact pill height.
|
|
76
97
|
const wrapper = document.createElement('div');
|
|
77
98
|
|
|
78
99
|
const button = document.createElement('button');
|
|
@@ -108,26 +129,30 @@ namespace CommandBar {
|
|
|
108
129
|
}
|
|
109
130
|
|
|
110
131
|
/**
|
|
111
|
-
*
|
|
112
|
-
* opens the omnibox — a launcher overlay that fuzzy-searches files
|
|
113
|
-
* commands and routes a typed prompt to an agent
|
|
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).
|
|
132
|
+
* Contribute a search-bar-styled command bar to JupyterLab's top bar toolbar.
|
|
133
|
+
* Clicking it opens the omnibox — a launcher overlay that fuzzy-searches files
|
|
134
|
+
* and commands and routes a typed prompt to an agent.
|
|
117
135
|
*
|
|
118
|
-
* The
|
|
119
|
-
*
|
|
136
|
+
* The pill is a regular settings-driven toolbar item: this plugin registers a
|
|
137
|
+
* widget factory for it on the `IToolbarWidgetRegistry` and declares its rank
|
|
138
|
+
* in schema/command-bar.json, so users can move or disable it from the Top
|
|
139
|
+
* Bar settings like any other toolbar item.
|
|
140
|
+
*
|
|
141
|
+
* The factory is only registered when the omnibox is available — the
|
|
142
|
+
* `IOmnibox` token is provided by `xtralab:omnibox` — so the pill never
|
|
143
|
+
* appears with nothing to open (without a factory, the toolbar item resolves
|
|
144
|
+
* to an empty command button that renders nothing).
|
|
120
145
|
*/
|
|
121
146
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
122
147
|
id: PLUGIN_ID,
|
|
123
148
|
description:
|
|
124
|
-
'Add a
|
|
149
|
+
'Add a command bar to the top bar toolbar that opens the omnibox.',
|
|
125
150
|
autoStart: true,
|
|
126
|
-
requires: [
|
|
151
|
+
requires: [IToolbarWidgetRegistry],
|
|
127
152
|
optional: [IOmnibox, ITranslator],
|
|
128
153
|
activate: (
|
|
129
154
|
app: JupyterFrontEnd,
|
|
130
|
-
|
|
155
|
+
toolbarRegistry: IToolbarWidgetRegistry,
|
|
131
156
|
omnibox: IOmnibox | null,
|
|
132
157
|
translator: ITranslator | null
|
|
133
158
|
): void => {
|
|
@@ -137,28 +162,29 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
137
162
|
|
|
138
163
|
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
139
164
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
toolbarRegistry.addFactory(TOPBAR_FACTORY, ITEM_NAME, () => {
|
|
166
|
+
// Show the omnibox's keyboard shortcut as a hint in the pill, derived
|
|
167
|
+
// from the live binding (registered by xtralab:omnibox, which activates
|
|
168
|
+
// first as the IOmnibox provider) so it stays correct and uses the
|
|
169
|
+
// platform's modifier symbols. Empty when no binding is registered.
|
|
170
|
+
const binding = app.commands.keyBindings.find(
|
|
171
|
+
keyBinding => keyBinding.command === OMNIBOX_OPEN_COMMAND
|
|
172
|
+
);
|
|
173
|
+
const shortcut = binding
|
|
174
|
+
? CommandRegistry.formatKeystroke(binding.keys)
|
|
175
|
+
: '';
|
|
176
|
+
|
|
177
|
+
const widget = new CommandBar({
|
|
178
|
+
label: trans.__('Search…'),
|
|
179
|
+
caption: trans.__('Search files and commands, or ask an agent'),
|
|
180
|
+
shortcut,
|
|
181
|
+
onActivate: () => {
|
|
182
|
+
omnibox.open();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
widget.id = 'xtralab-command-bar';
|
|
186
|
+
return widget;
|
|
158
187
|
});
|
|
159
|
-
widget.id = 'xtralab-command-bar';
|
|
160
|
-
|
|
161
|
-
labShell.add(widget, 'top', { rank: 500 });
|
|
162
188
|
}
|
|
163
189
|
};
|
|
164
190
|
|
|
@@ -6,6 +6,11 @@ import type {
|
|
|
6
6
|
DocumentRegistry,
|
|
7
7
|
IDocumentWidget
|
|
8
8
|
} from '@jupyterlab/docregistry';
|
|
9
|
+
import {
|
|
10
|
+
ITranslator,
|
|
11
|
+
nullTranslator,
|
|
12
|
+
type TranslationBundle
|
|
13
|
+
} from '@jupyterlab/translation';
|
|
9
14
|
import type { CommandRegistry } from '@lumino/commands';
|
|
10
15
|
import { DisposableDelegate, type IDisposable } from '@lumino/disposable';
|
|
11
16
|
import type { Widget } from '@lumino/widgets';
|
|
@@ -20,8 +25,9 @@ class EditorBreadcrumbsExtension implements DocumentRegistry.IWidgetExtension<
|
|
|
20
25
|
IDocumentWidget<Widget, DocumentRegistry.IModel>,
|
|
21
26
|
DocumentRegistry.IModel
|
|
22
27
|
> {
|
|
23
|
-
constructor(commands: CommandRegistry) {
|
|
28
|
+
constructor(commands: CommandRegistry, trans: TranslationBundle) {
|
|
24
29
|
this._commands = commands;
|
|
30
|
+
this._trans = trans;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
createNew(
|
|
@@ -30,7 +36,8 @@ class EditorBreadcrumbsExtension implements DocumentRegistry.IWidgetExtension<
|
|
|
30
36
|
): IDisposable | void {
|
|
31
37
|
const breadcrumbs = new EditorBreadcrumbs({
|
|
32
38
|
context,
|
|
33
|
-
commands: this._commands
|
|
39
|
+
commands: this._commands,
|
|
40
|
+
trans: this._trans
|
|
34
41
|
});
|
|
35
42
|
const added = widget.toolbar.insertItem(0, TOOLBAR_ITEM_NAME, breadcrumbs);
|
|
36
43
|
|
|
@@ -45,6 +52,7 @@ class EditorBreadcrumbsExtension implements DocumentRegistry.IWidgetExtension<
|
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
private _commands: CommandRegistry;
|
|
55
|
+
private _trans: TranslationBundle;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
/**
|
|
@@ -59,10 +67,12 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
59
67
|
id: PLUGIN_ID,
|
|
60
68
|
description: 'Display the active file path in text editor toolbars.',
|
|
61
69
|
autoStart: true,
|
|
62
|
-
|
|
70
|
+
optional: [ITranslator],
|
|
71
|
+
activate: (app: JupyterFrontEnd, translator: ITranslator | null): void => {
|
|
72
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
63
73
|
app.docRegistry.addWidgetExtension(
|
|
64
74
|
EDITOR_FACTORY,
|
|
65
|
-
new EditorBreadcrumbsExtension(app.commands)
|
|
75
|
+
new EditorBreadcrumbsExtension(app.commands, trans)
|
|
66
76
|
);
|
|
67
77
|
}
|
|
68
78
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PageConfig } from '@jupyterlab/coreutils';
|
|
2
2
|
import type { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
3
|
+
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
3
4
|
import { homeIcon as rootIcon } from '@jupyterlab/ui-components';
|
|
4
5
|
import type { CommandRegistry } from '@lumino/commands';
|
|
5
6
|
import { Widget } from '@lumino/widgets';
|
|
@@ -19,26 +20,24 @@ const SEPARATOR_CLASS = 'jp-BreadCrumbs-separator';
|
|
|
19
20
|
*/
|
|
20
21
|
const REVEAL_COMMAND = 'xtralab:reveal-path';
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
interface IEditorBreadcrumbsOptions {
|
|
23
24
|
context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
|
|
24
25
|
commands: CommandRegistry;
|
|
26
|
+
trans: TranslationBundle;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
* Read-only breadcrumb path for file editor toolbars
|
|
29
|
-
*
|
|
30
|
-
* The upstream `@jupyterlab/filebrowser` BreadCrumbs widget is coupled to
|
|
31
|
-
* FileBrowserModel navigation, drag/drop, and path editing. If that code
|
|
32
|
-
* becomes reusable upstream, replace this local display-only renderer with
|
|
33
|
-
* the shared implementation.
|
|
30
|
+
* Read-only breadcrumb path for file editor toolbars: renders the open
|
|
31
|
+
* file's path as clickable segments, each dispatching the reveal command.
|
|
34
32
|
*/
|
|
35
33
|
export class EditorBreadcrumbs extends Widget {
|
|
36
34
|
constructor(options: IEditorBreadcrumbsOptions) {
|
|
37
35
|
super({ node: document.createElement('nav') });
|
|
38
36
|
this._context = options.context;
|
|
39
37
|
this._commands = options.commands;
|
|
38
|
+
this._trans = options.trans;
|
|
40
39
|
this.addClass(EDITOR_BREADCRUMBS_CLASS);
|
|
41
|
-
this.node.setAttribute('aria-label', 'Editor file path');
|
|
40
|
+
this.node.setAttribute('aria-label', this._trans.__('Editor file path'));
|
|
42
41
|
|
|
43
42
|
this._container = document.createElement('span');
|
|
44
43
|
this._container.className = BREADCRUMBS_CONTAINER_CLASS;
|
|
@@ -105,7 +104,9 @@ export class EditorBreadcrumbs extends Widget {
|
|
|
105
104
|
const item = rootIcon.element({
|
|
106
105
|
className: ROOT_CLASS,
|
|
107
106
|
tag: 'span',
|
|
108
|
-
title:
|
|
107
|
+
title:
|
|
108
|
+
PageConfig.getOption('serverRoot') ||
|
|
109
|
+
this._trans.__('Jupyter Server Root'),
|
|
109
110
|
stylesheet: 'breadCrumb'
|
|
110
111
|
});
|
|
111
112
|
item.dataset.path = '/';
|
|
@@ -153,6 +154,7 @@ export class EditorBreadcrumbs extends Widget {
|
|
|
153
154
|
|
|
154
155
|
private _context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
|
|
155
156
|
private _commands: CommandRegistry;
|
|
157
|
+
private _trans: TranslationBundle;
|
|
156
158
|
private _container: HTMLElement;
|
|
157
159
|
private _content: HTMLElement;
|
|
158
160
|
}
|