jupyterlab_show_commands_reference_extension 1.0.4
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/LICENSE +29 -0
- package/README.md +44 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +56 -0
- package/lib/widget.d.ts +55 -0
- package/lib/widget.js +254 -0
- package/package.json +198 -0
- package/src/__tests__/jupyterlab_show_commands_reference_extension.spec.ts +9 -0
- package/src/index.ts +74 -0
- package/src/widget.ts +321 -0
- package/style/base.css +141 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Stellars Henson
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# jupyterlab_show_commands_reference_extension
|
|
2
|
+
|
|
3
|
+
[](https://github.com/stellarshenson/jupyterlab_show_commands_reference_extension/actions/workflows/build.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/jupyterlab_show_commands_reference_extension)
|
|
5
|
+
[](https://pypi.org/project/jupyterlab-show-commands-reference-extension/)
|
|
6
|
+
[](https://pepy.tech/project/jupyterlab-show-commands-reference-extension)
|
|
7
|
+
[](https://jupyterlab.readthedocs.io/en/stable/)
|
|
8
|
+
[](https://kolomolo.com)
|
|
9
|
+
[](https://www.paypal.com/donate/?hosted_button_id=B4KPBJDLLXTSA)
|
|
10
|
+
|
|
11
|
+
Display all available JupyterLab commands with their full reference IDs and arguments in a dedicated tab. A reference help page for developers working with JupyterLab commands.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Command reference tab** - Opens a new tab listing all registered JupyterLab commands
|
|
20
|
+
- **Full command IDs** - Shows complete command identifiers (e.g., `iframe:open`, `filebrowser:copy`)
|
|
21
|
+
- **Argument inspection** - Displays command arguments extracted from the application in realtime
|
|
22
|
+
- **Searchable list** - Filter commands by name or description
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- JupyterLab >= 4.0.0
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
make install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or via pip:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install jupyterlab_show_commands_reference_extension
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Uninstall
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip uninstall jupyterlab_show_commands_reference_extension
|
|
44
|
+
```
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ICommandPalette, MainAreaWidget } from '@jupyterlab/apputils';
|
|
2
|
+
import { CommandsReferenceWidget } from './widget';
|
|
3
|
+
/**
|
|
4
|
+
* Command IDs for the extension
|
|
5
|
+
*/
|
|
6
|
+
var CommandIDs;
|
|
7
|
+
(function (CommandIDs) {
|
|
8
|
+
CommandIDs.open = 'jupyterlab-commands-reference:open';
|
|
9
|
+
})(CommandIDs || (CommandIDs = {}));
|
|
10
|
+
/**
|
|
11
|
+
* Track the widget instance to prevent duplicates
|
|
12
|
+
*/
|
|
13
|
+
let widget = null;
|
|
14
|
+
/**
|
|
15
|
+
* Initialization data for the jupyterlab_show_commands_reference_extension extension.
|
|
16
|
+
*/
|
|
17
|
+
const plugin = {
|
|
18
|
+
id: 'jupyterlab_show_commands_reference_extension:plugin',
|
|
19
|
+
description: 'Display all available JupyterLab commands with their full reference IDs and arguments in a dedicated tab',
|
|
20
|
+
autoStart: true,
|
|
21
|
+
requires: [ICommandPalette],
|
|
22
|
+
activate: (app, palette) => {
|
|
23
|
+
console.log('JupyterLab extension jupyterlab_show_commands_reference_extension is activated!');
|
|
24
|
+
// Register the command
|
|
25
|
+
app.commands.addCommand(CommandIDs.open, {
|
|
26
|
+
label: 'Show Commands Reference',
|
|
27
|
+
caption: 'Display all available JupyterLab commands with their IDs and arguments',
|
|
28
|
+
execute: () => {
|
|
29
|
+
// Create widget if it doesn't exist or was disposed
|
|
30
|
+
if (!widget || widget.isDisposed) {
|
|
31
|
+
const content = new CommandsReferenceWidget(app.commands);
|
|
32
|
+
widget = new MainAreaWidget({ content });
|
|
33
|
+
widget.id = 'jp-commands-reference';
|
|
34
|
+
widget.title.label = 'Commands Reference';
|
|
35
|
+
widget.title.closable = true;
|
|
36
|
+
// Clear reference when widget is disposed
|
|
37
|
+
widget.disposed.connect(() => {
|
|
38
|
+
widget = null;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Add to main area if not already attached
|
|
42
|
+
if (!widget.isAttached) {
|
|
43
|
+
app.shell.add(widget, 'main');
|
|
44
|
+
}
|
|
45
|
+
// Activate the widget
|
|
46
|
+
app.shell.activateById(widget.id);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// Add command to palette under Help category
|
|
50
|
+
palette.addItem({
|
|
51
|
+
command: CommandIDs.open,
|
|
52
|
+
category: 'Help'
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
export default plugin;
|
package/lib/widget.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Widget } from '@lumino/widgets';
|
|
2
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
3
|
+
/**
|
|
4
|
+
* A widget that displays all registered JupyterLab commands
|
|
5
|
+
*/
|
|
6
|
+
export declare class CommandsReferenceWidget extends Widget {
|
|
7
|
+
private _commands;
|
|
8
|
+
private _searchInput;
|
|
9
|
+
private _countSpan;
|
|
10
|
+
private _tbody;
|
|
11
|
+
private _commandsCache;
|
|
12
|
+
private _argsCache;
|
|
13
|
+
constructor(commands: CommandRegistry);
|
|
14
|
+
/**
|
|
15
|
+
* Build the header with search input and count
|
|
16
|
+
*/
|
|
17
|
+
private _buildHeader;
|
|
18
|
+
/**
|
|
19
|
+
* Build the content area with commands table
|
|
20
|
+
*/
|
|
21
|
+
private _buildContent;
|
|
22
|
+
/**
|
|
23
|
+
* Load all commands from the registry
|
|
24
|
+
*/
|
|
25
|
+
private _loadCommands;
|
|
26
|
+
/**
|
|
27
|
+
* Load command arguments asynchronously
|
|
28
|
+
*/
|
|
29
|
+
private _loadArguments;
|
|
30
|
+
/**
|
|
31
|
+
* Format arguments object as readable string
|
|
32
|
+
* Handles JSON Schema format: { type: "object", properties: { argName: { type: "string" } } }
|
|
33
|
+
*/
|
|
34
|
+
private _formatArgs;
|
|
35
|
+
/**
|
|
36
|
+
* Update a specific args cell in the table
|
|
37
|
+
*/
|
|
38
|
+
private _updateArgsCell;
|
|
39
|
+
/**
|
|
40
|
+
* Render the commands table
|
|
41
|
+
*/
|
|
42
|
+
private _renderTable;
|
|
43
|
+
/**
|
|
44
|
+
* Filter commands based on search input
|
|
45
|
+
*/
|
|
46
|
+
private _filterCommands;
|
|
47
|
+
/**
|
|
48
|
+
* Update the command count display
|
|
49
|
+
*/
|
|
50
|
+
private _updateCount;
|
|
51
|
+
/**
|
|
52
|
+
* Refresh the commands list
|
|
53
|
+
*/
|
|
54
|
+
refresh(): void;
|
|
55
|
+
}
|
package/lib/widget.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { Widget } from '@lumino/widgets';
|
|
2
|
+
/**
|
|
3
|
+
* A widget that displays all registered JupyterLab commands
|
|
4
|
+
*/
|
|
5
|
+
export class CommandsReferenceWidget extends Widget {
|
|
6
|
+
constructor(commands) {
|
|
7
|
+
super();
|
|
8
|
+
this._commandsCache = [];
|
|
9
|
+
this._argsCache = new Map();
|
|
10
|
+
this._commands = commands;
|
|
11
|
+
this.addClass('jp-CommandsReferenceWidget');
|
|
12
|
+
// Build DOM structure
|
|
13
|
+
this._buildHeader();
|
|
14
|
+
this._buildContent();
|
|
15
|
+
// Initial load
|
|
16
|
+
void this._loadCommands();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the header with search input and count
|
|
20
|
+
*/
|
|
21
|
+
_buildHeader() {
|
|
22
|
+
const header = document.createElement('div');
|
|
23
|
+
header.className = 'jp-CommandsReferenceWidget-header';
|
|
24
|
+
// Search input
|
|
25
|
+
this._searchInput = document.createElement('input');
|
|
26
|
+
this._searchInput.type = 'text';
|
|
27
|
+
this._searchInput.placeholder = 'Filter commands...';
|
|
28
|
+
this._searchInput.className = 'jp-CommandsReferenceWidget-search';
|
|
29
|
+
this._searchInput.addEventListener('input', () => {
|
|
30
|
+
this._filterCommands();
|
|
31
|
+
});
|
|
32
|
+
header.appendChild(this._searchInput);
|
|
33
|
+
// Command count
|
|
34
|
+
this._countSpan = document.createElement('span');
|
|
35
|
+
this._countSpan.className = 'jp-CommandsReferenceWidget-count';
|
|
36
|
+
header.appendChild(this._countSpan);
|
|
37
|
+
this.node.appendChild(header);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build the content area with commands table
|
|
41
|
+
*/
|
|
42
|
+
_buildContent() {
|
|
43
|
+
const content = document.createElement('div');
|
|
44
|
+
content.className = 'jp-CommandsReferenceWidget-content';
|
|
45
|
+
const table = document.createElement('table');
|
|
46
|
+
table.className = 'jp-CommandsReferenceWidget-table';
|
|
47
|
+
// Table header
|
|
48
|
+
const thead = document.createElement('thead');
|
|
49
|
+
const headerRow = document.createElement('tr');
|
|
50
|
+
const headers = ['Command ID', 'Label', 'Description', 'Arguments'];
|
|
51
|
+
for (const headerText of headers) {
|
|
52
|
+
const th = document.createElement('th');
|
|
53
|
+
th.textContent = headerText;
|
|
54
|
+
headerRow.appendChild(th);
|
|
55
|
+
}
|
|
56
|
+
thead.appendChild(headerRow);
|
|
57
|
+
table.appendChild(thead);
|
|
58
|
+
// Table body
|
|
59
|
+
this._tbody = document.createElement('tbody');
|
|
60
|
+
table.appendChild(this._tbody);
|
|
61
|
+
content.appendChild(table);
|
|
62
|
+
this.node.appendChild(content);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Load all commands from the registry
|
|
66
|
+
*/
|
|
67
|
+
async _loadCommands() {
|
|
68
|
+
try {
|
|
69
|
+
const commandIds = this._commands.listCommands();
|
|
70
|
+
console.log(`Commands Reference: Found ${commandIds.length} commands`);
|
|
71
|
+
this._commandsCache = [];
|
|
72
|
+
for (const id of commandIds) {
|
|
73
|
+
let label = '';
|
|
74
|
+
let caption = '';
|
|
75
|
+
// Some commands have label/caption functions that may fail without args
|
|
76
|
+
try {
|
|
77
|
+
label = this._commands.label(id) || '';
|
|
78
|
+
}
|
|
79
|
+
catch (_a) {
|
|
80
|
+
label = '';
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
caption = this._commands.caption(id) || '';
|
|
84
|
+
}
|
|
85
|
+
catch (_b) {
|
|
86
|
+
caption = '';
|
|
87
|
+
}
|
|
88
|
+
const info = {
|
|
89
|
+
id,
|
|
90
|
+
label,
|
|
91
|
+
caption,
|
|
92
|
+
args: null
|
|
93
|
+
};
|
|
94
|
+
this._commandsCache.push(info);
|
|
95
|
+
}
|
|
96
|
+
// Sort by command ID
|
|
97
|
+
this._commandsCache.sort((a, b) => a.id.localeCompare(b.id));
|
|
98
|
+
// Render initial table
|
|
99
|
+
this._renderTable(this._commandsCache);
|
|
100
|
+
// Load arguments asynchronously
|
|
101
|
+
void this._loadArguments();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Commands Reference: Error loading commands', error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Load command arguments asynchronously
|
|
109
|
+
*/
|
|
110
|
+
async _loadArguments() {
|
|
111
|
+
const batchSize = 50;
|
|
112
|
+
const commands = this._commandsCache;
|
|
113
|
+
for (let i = 0; i < commands.length; i += batchSize) {
|
|
114
|
+
const batch = commands.slice(i, i + batchSize);
|
|
115
|
+
await Promise.all(batch.map(async (cmd) => {
|
|
116
|
+
try {
|
|
117
|
+
const description = await this._commands.describedBy(cmd.id);
|
|
118
|
+
if (description && description.args) {
|
|
119
|
+
const argsStr = this._formatArgs(description.args);
|
|
120
|
+
cmd.args = argsStr;
|
|
121
|
+
this._argsCache.set(cmd.id, argsStr);
|
|
122
|
+
// Update the cell if visible
|
|
123
|
+
this._updateArgsCell(cmd.id, argsStr);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (_a) {
|
|
127
|
+
// Command may not have describedBy implemented
|
|
128
|
+
}
|
|
129
|
+
}));
|
|
130
|
+
// Yield to allow UI updates
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Format arguments object as readable string
|
|
136
|
+
* Handles JSON Schema format: { type: "object", properties: { argName: { type: "string" } } }
|
|
137
|
+
*/
|
|
138
|
+
_formatArgs(args) {
|
|
139
|
+
if (!args || typeof args !== 'object') {
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
const schema = args;
|
|
143
|
+
// JSON Schema format: extract from 'properties' object
|
|
144
|
+
if (schema.properties && typeof schema.properties === 'object') {
|
|
145
|
+
const properties = schema.properties;
|
|
146
|
+
const propKeys = Object.keys(properties);
|
|
147
|
+
if (propKeys.length === 0) {
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
const formatted = propKeys.map(key => {
|
|
151
|
+
const prop = properties[key];
|
|
152
|
+
if (prop && typeof prop === 'object') {
|
|
153
|
+
const propObj = prop;
|
|
154
|
+
const typeInfo = propObj.type;
|
|
155
|
+
if (typeInfo) {
|
|
156
|
+
return `${key}: ${typeInfo}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return key;
|
|
160
|
+
});
|
|
161
|
+
return formatted.join(', ');
|
|
162
|
+
}
|
|
163
|
+
// Fallback: direct key-value format (non-schema)
|
|
164
|
+
const keys = Object.keys(schema).filter(k => k !== 'type' && k !== '$schema');
|
|
165
|
+
if (keys.length === 0) {
|
|
166
|
+
return '';
|
|
167
|
+
}
|
|
168
|
+
return keys.join(', ');
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Update a specific args cell in the table
|
|
172
|
+
*/
|
|
173
|
+
_updateArgsCell(commandId, argsStr) {
|
|
174
|
+
const rows = Array.from(this._tbody.querySelectorAll('tr'));
|
|
175
|
+
for (const row of rows) {
|
|
176
|
+
const idCell = row.querySelector('td:first-child');
|
|
177
|
+
if (idCell && idCell.textContent === commandId) {
|
|
178
|
+
const argsCell = row.querySelector('td:nth-child(4)');
|
|
179
|
+
if (argsCell) {
|
|
180
|
+
argsCell.textContent = argsStr;
|
|
181
|
+
argsCell.title = argsStr;
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Render the commands table
|
|
189
|
+
*/
|
|
190
|
+
_renderTable(commands) {
|
|
191
|
+
this._tbody.innerHTML = '';
|
|
192
|
+
for (const cmd of commands) {
|
|
193
|
+
const row = document.createElement('tr');
|
|
194
|
+
// Command ID cell
|
|
195
|
+
const idCell = document.createElement('td');
|
|
196
|
+
idCell.className = 'jp-CommandsReferenceWidget-commandId';
|
|
197
|
+
idCell.textContent = cmd.id;
|
|
198
|
+
idCell.title = cmd.id;
|
|
199
|
+
row.appendChild(idCell);
|
|
200
|
+
// Label cell
|
|
201
|
+
const labelCell = document.createElement('td');
|
|
202
|
+
labelCell.textContent = cmd.label;
|
|
203
|
+
labelCell.title = cmd.label;
|
|
204
|
+
row.appendChild(labelCell);
|
|
205
|
+
// Description cell
|
|
206
|
+
const descCell = document.createElement('td');
|
|
207
|
+
descCell.textContent = cmd.caption;
|
|
208
|
+
descCell.title = cmd.caption;
|
|
209
|
+
row.appendChild(descCell);
|
|
210
|
+
// Arguments cell
|
|
211
|
+
const argsCell = document.createElement('td');
|
|
212
|
+
const cachedArgs = this._argsCache.get(cmd.id);
|
|
213
|
+
argsCell.textContent = cachedArgs || cmd.args || '';
|
|
214
|
+
argsCell.title = cachedArgs || cmd.args || '';
|
|
215
|
+
row.appendChild(argsCell);
|
|
216
|
+
this._tbody.appendChild(row);
|
|
217
|
+
}
|
|
218
|
+
this._updateCount(commands.length, this._commandsCache.length);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Filter commands based on search input
|
|
222
|
+
*/
|
|
223
|
+
_filterCommands() {
|
|
224
|
+
const query = this._searchInput.value.toLowerCase().trim();
|
|
225
|
+
if (!query) {
|
|
226
|
+
this._renderTable(this._commandsCache);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const filtered = this._commandsCache.filter(cmd => {
|
|
230
|
+
return (cmd.id.toLowerCase().includes(query) ||
|
|
231
|
+
cmd.label.toLowerCase().includes(query) ||
|
|
232
|
+
cmd.caption.toLowerCase().includes(query));
|
|
233
|
+
});
|
|
234
|
+
this._renderTable(filtered);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Update the command count display
|
|
238
|
+
*/
|
|
239
|
+
_updateCount(shown, total) {
|
|
240
|
+
if (shown === total) {
|
|
241
|
+
this._countSpan.textContent = `${total} commands`;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this._countSpan.textContent = `${shown} / ${total} commands`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Refresh the commands list
|
|
249
|
+
*/
|
|
250
|
+
refresh() {
|
|
251
|
+
this._argsCache.clear();
|
|
252
|
+
void this._loadCommands();
|
|
253
|
+
}
|
|
254
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jupyterlab_show_commands_reference_extension",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "Display all available JupyterLab commands with their full reference IDs and arguments in a dedicated tab",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jupyter",
|
|
7
|
+
"jupyterlab",
|
|
8
|
+
"jupyterlab-extension"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/stellarshenson/jupyterlab_show_commands_reference_extension",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/stellarshenson/jupyterlab_show_commands_reference_extension/issues"
|
|
13
|
+
},
|
|
14
|
+
"license": "BSD-3-Clause",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Stellars Henson",
|
|
17
|
+
"email": "konrad.jelen@gmail.com"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
|
21
|
+
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
|
|
22
|
+
"src/**/*.{ts,tsx}"
|
|
23
|
+
],
|
|
24
|
+
"main": "lib/index.js",
|
|
25
|
+
"types": "lib/index.d.ts",
|
|
26
|
+
"style": "style/index.css",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/stellarshenson/jupyterlab_show_commands_reference_extension.git"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
33
|
+
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
34
|
+
"build:labextension": "jupyter labextension build .",
|
|
35
|
+
"build:labextension:dev": "jupyter labextension build --development True .",
|
|
36
|
+
"build:lib": "tsc --sourceMap",
|
|
37
|
+
"build:lib:prod": "tsc",
|
|
38
|
+
"clean": "jlpm clean:lib",
|
|
39
|
+
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
|
|
40
|
+
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
|
|
41
|
+
"clean:labextension": "rimraf jupyterlab_show_commands_reference_extension/labextension jupyterlab_show_commands_reference_extension/_version.py",
|
|
42
|
+
"clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
|
|
43
|
+
"eslint": "jlpm eslint:check --fix",
|
|
44
|
+
"eslint:check": "eslint . --cache --ext .ts,.tsx",
|
|
45
|
+
"install:extension": "jlpm build",
|
|
46
|
+
"lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
|
|
47
|
+
"lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
|
|
48
|
+
"prettier": "jlpm prettier:base --write --list-different",
|
|
49
|
+
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
|
|
50
|
+
"prettier:check": "jlpm prettier:base --check",
|
|
51
|
+
"stylelint": "jlpm stylelint:check --fix",
|
|
52
|
+
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
|
|
53
|
+
"test": "jest --coverage",
|
|
54
|
+
"watch": "run-p watch:src watch:labextension",
|
|
55
|
+
"watch:src": "tsc -w --sourceMap",
|
|
56
|
+
"watch:labextension": "jupyter labextension watch ."
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@jupyterlab/application": "^4.0.0",
|
|
60
|
+
"@jupyterlab/apputils": "^4.0.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@jupyterlab/builder": "^4.0.0",
|
|
64
|
+
"@jupyterlab/testutils": "^4.0.0",
|
|
65
|
+
"@types/jest": "^29.2.0",
|
|
66
|
+
"@types/json-schema": "^7.0.11",
|
|
67
|
+
"@types/react": "^18.0.26",
|
|
68
|
+
"@types/react-addons-linked-state-mixin": "^0.14.22",
|
|
69
|
+
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
|
70
|
+
"@typescript-eslint/parser": "^6.1.0",
|
|
71
|
+
"css-loader": "^6.7.1",
|
|
72
|
+
"eslint": "^8.36.0",
|
|
73
|
+
"eslint-config-prettier": "^8.8.0",
|
|
74
|
+
"eslint-plugin-prettier": "^5.0.0",
|
|
75
|
+
"jest": "^29.2.0",
|
|
76
|
+
"npm-run-all2": "^7.0.1",
|
|
77
|
+
"prettier": "^3.0.0",
|
|
78
|
+
"rimraf": "^5.0.1",
|
|
79
|
+
"source-map-loader": "^1.0.2",
|
|
80
|
+
"style-loader": "^3.3.1",
|
|
81
|
+
"stylelint": "^15.10.1",
|
|
82
|
+
"stylelint-config-recommended": "^13.0.0",
|
|
83
|
+
"stylelint-config-standard": "^34.0.0",
|
|
84
|
+
"stylelint-csstree-validator": "^3.0.0",
|
|
85
|
+
"stylelint-prettier": "^4.0.0",
|
|
86
|
+
"typescript": "~5.5.4",
|
|
87
|
+
"yjs": "^13.5.0"
|
|
88
|
+
},
|
|
89
|
+
"resolutions": {
|
|
90
|
+
"lib0": "0.2.111"
|
|
91
|
+
},
|
|
92
|
+
"sideEffects": [
|
|
93
|
+
"style/*.css",
|
|
94
|
+
"style/index.js"
|
|
95
|
+
],
|
|
96
|
+
"styleModule": "style/index.js",
|
|
97
|
+
"publishConfig": {
|
|
98
|
+
"access": "public"
|
|
99
|
+
},
|
|
100
|
+
"jupyterlab": {
|
|
101
|
+
"extension": true,
|
|
102
|
+
"outputDir": "jupyterlab_show_commands_reference_extension/labextension"
|
|
103
|
+
},
|
|
104
|
+
"eslintIgnore": [
|
|
105
|
+
"node_modules",
|
|
106
|
+
"dist",
|
|
107
|
+
"coverage",
|
|
108
|
+
"**/*.d.ts",
|
|
109
|
+
"tests",
|
|
110
|
+
"**/__tests__",
|
|
111
|
+
"ui-tests"
|
|
112
|
+
],
|
|
113
|
+
"eslintConfig": {
|
|
114
|
+
"extends": [
|
|
115
|
+
"eslint:recommended",
|
|
116
|
+
"plugin:@typescript-eslint/eslint-recommended",
|
|
117
|
+
"plugin:@typescript-eslint/recommended",
|
|
118
|
+
"plugin:prettier/recommended"
|
|
119
|
+
],
|
|
120
|
+
"parser": "@typescript-eslint/parser",
|
|
121
|
+
"parserOptions": {
|
|
122
|
+
"project": "tsconfig.json",
|
|
123
|
+
"sourceType": "module"
|
|
124
|
+
},
|
|
125
|
+
"plugins": [
|
|
126
|
+
"@typescript-eslint"
|
|
127
|
+
],
|
|
128
|
+
"rules": {
|
|
129
|
+
"@typescript-eslint/naming-convention": [
|
|
130
|
+
"error",
|
|
131
|
+
{
|
|
132
|
+
"selector": "interface",
|
|
133
|
+
"format": [
|
|
134
|
+
"PascalCase"
|
|
135
|
+
],
|
|
136
|
+
"custom": {
|
|
137
|
+
"regex": "^I[A-Z]",
|
|
138
|
+
"match": true
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"@typescript-eslint/no-unused-vars": [
|
|
143
|
+
"warn",
|
|
144
|
+
{
|
|
145
|
+
"args": "none"
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
149
|
+
"@typescript-eslint/no-namespace": "off",
|
|
150
|
+
"@typescript-eslint/no-use-before-define": "off",
|
|
151
|
+
"@typescript-eslint/quotes": [
|
|
152
|
+
"error",
|
|
153
|
+
"single",
|
|
154
|
+
{
|
|
155
|
+
"avoidEscape": true,
|
|
156
|
+
"allowTemplateLiterals": false
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
"curly": [
|
|
160
|
+
"error",
|
|
161
|
+
"all"
|
|
162
|
+
],
|
|
163
|
+
"eqeqeq": "error",
|
|
164
|
+
"prefer-arrow-callback": "error"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"prettier": {
|
|
168
|
+
"singleQuote": true,
|
|
169
|
+
"trailingComma": "none",
|
|
170
|
+
"arrowParens": "avoid",
|
|
171
|
+
"endOfLine": "auto",
|
|
172
|
+
"overrides": [
|
|
173
|
+
{
|
|
174
|
+
"files": "package.json",
|
|
175
|
+
"options": {
|
|
176
|
+
"tabWidth": 4
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"stylelint": {
|
|
182
|
+
"extends": [
|
|
183
|
+
"stylelint-config-recommended",
|
|
184
|
+
"stylelint-config-standard",
|
|
185
|
+
"stylelint-prettier/recommended"
|
|
186
|
+
],
|
|
187
|
+
"plugins": [
|
|
188
|
+
"stylelint-csstree-validator"
|
|
189
|
+
],
|
|
190
|
+
"rules": {
|
|
191
|
+
"csstree/validator": true,
|
|
192
|
+
"property-no-vendor-prefix": null,
|
|
193
|
+
"selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$",
|
|
194
|
+
"selector-no-vendor-prefix": null,
|
|
195
|
+
"value-no-vendor-prefix": null
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JupyterFrontEnd,
|
|
3
|
+
JupyterFrontEndPlugin
|
|
4
|
+
} from '@jupyterlab/application';
|
|
5
|
+
|
|
6
|
+
import { ICommandPalette, MainAreaWidget } from '@jupyterlab/apputils';
|
|
7
|
+
|
|
8
|
+
import { CommandsReferenceWidget } from './widget';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Command IDs for the extension
|
|
12
|
+
*/
|
|
13
|
+
namespace CommandIDs {
|
|
14
|
+
export const open = 'jupyterlab-commands-reference:open';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Track the widget instance to prevent duplicates
|
|
19
|
+
*/
|
|
20
|
+
let widget: MainAreaWidget<CommandsReferenceWidget> | null = null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialization data for the jupyterlab_show_commands_reference_extension extension.
|
|
24
|
+
*/
|
|
25
|
+
const plugin: JupyterFrontEndPlugin<void> = {
|
|
26
|
+
id: 'jupyterlab_show_commands_reference_extension:plugin',
|
|
27
|
+
description:
|
|
28
|
+
'Display all available JupyterLab commands with their full reference IDs and arguments in a dedicated tab',
|
|
29
|
+
autoStart: true,
|
|
30
|
+
requires: [ICommandPalette],
|
|
31
|
+
activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
|
|
32
|
+
console.log(
|
|
33
|
+
'JupyterLab extension jupyterlab_show_commands_reference_extension is activated!'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// Register the command
|
|
37
|
+
app.commands.addCommand(CommandIDs.open, {
|
|
38
|
+
label: 'Show Commands Reference',
|
|
39
|
+
caption:
|
|
40
|
+
'Display all available JupyterLab commands with their IDs and arguments',
|
|
41
|
+
execute: () => {
|
|
42
|
+
// Create widget if it doesn't exist or was disposed
|
|
43
|
+
if (!widget || widget.isDisposed) {
|
|
44
|
+
const content = new CommandsReferenceWidget(app.commands);
|
|
45
|
+
widget = new MainAreaWidget({ content });
|
|
46
|
+
widget.id = 'jp-commands-reference';
|
|
47
|
+
widget.title.label = 'Commands Reference';
|
|
48
|
+
widget.title.closable = true;
|
|
49
|
+
|
|
50
|
+
// Clear reference when widget is disposed
|
|
51
|
+
widget.disposed.connect(() => {
|
|
52
|
+
widget = null;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add to main area if not already attached
|
|
57
|
+
if (!widget.isAttached) {
|
|
58
|
+
app.shell.add(widget, 'main');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Activate the widget
|
|
62
|
+
app.shell.activateById(widget.id);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Add command to palette under Help category
|
|
67
|
+
palette.addItem({
|
|
68
|
+
command: CommandIDs.open,
|
|
69
|
+
category: 'Help'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default plugin;
|
package/src/widget.ts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { Widget } from '@lumino/widgets';
|
|
2
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface for cached command information
|
|
6
|
+
*/
|
|
7
|
+
interface ICommandInfo {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
caption: string;
|
|
11
|
+
args: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A widget that displays all registered JupyterLab commands
|
|
16
|
+
*/
|
|
17
|
+
export class CommandsReferenceWidget extends Widget {
|
|
18
|
+
private _commands: CommandRegistry;
|
|
19
|
+
private _searchInput!: HTMLInputElement;
|
|
20
|
+
private _countSpan!: HTMLSpanElement;
|
|
21
|
+
private _tbody!: HTMLTableSectionElement;
|
|
22
|
+
private _commandsCache: ICommandInfo[] = [];
|
|
23
|
+
private _argsCache: Map<string, string> = new Map();
|
|
24
|
+
|
|
25
|
+
constructor(commands: CommandRegistry) {
|
|
26
|
+
super();
|
|
27
|
+
this._commands = commands;
|
|
28
|
+
this.addClass('jp-CommandsReferenceWidget');
|
|
29
|
+
|
|
30
|
+
// Build DOM structure
|
|
31
|
+
this._buildHeader();
|
|
32
|
+
this._buildContent();
|
|
33
|
+
|
|
34
|
+
// Initial load
|
|
35
|
+
void this._loadCommands();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build the header with search input and count
|
|
40
|
+
*/
|
|
41
|
+
private _buildHeader(): void {
|
|
42
|
+
const header = document.createElement('div');
|
|
43
|
+
header.className = 'jp-CommandsReferenceWidget-header';
|
|
44
|
+
|
|
45
|
+
// Search input
|
|
46
|
+
this._searchInput = document.createElement('input');
|
|
47
|
+
this._searchInput.type = 'text';
|
|
48
|
+
this._searchInput.placeholder = 'Filter commands...';
|
|
49
|
+
this._searchInput.className = 'jp-CommandsReferenceWidget-search';
|
|
50
|
+
this._searchInput.addEventListener('input', () => {
|
|
51
|
+
this._filterCommands();
|
|
52
|
+
});
|
|
53
|
+
header.appendChild(this._searchInput);
|
|
54
|
+
|
|
55
|
+
// Command count
|
|
56
|
+
this._countSpan = document.createElement('span');
|
|
57
|
+
this._countSpan.className = 'jp-CommandsReferenceWidget-count';
|
|
58
|
+
header.appendChild(this._countSpan);
|
|
59
|
+
|
|
60
|
+
this.node.appendChild(header);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build the content area with commands table
|
|
65
|
+
*/
|
|
66
|
+
private _buildContent(): void {
|
|
67
|
+
const content = document.createElement('div');
|
|
68
|
+
content.className = 'jp-CommandsReferenceWidget-content';
|
|
69
|
+
|
|
70
|
+
const table = document.createElement('table');
|
|
71
|
+
table.className = 'jp-CommandsReferenceWidget-table';
|
|
72
|
+
|
|
73
|
+
// Table header
|
|
74
|
+
const thead = document.createElement('thead');
|
|
75
|
+
const headerRow = document.createElement('tr');
|
|
76
|
+
const headers = ['Command ID', 'Label', 'Description', 'Arguments'];
|
|
77
|
+
for (const headerText of headers) {
|
|
78
|
+
const th = document.createElement('th');
|
|
79
|
+
th.textContent = headerText;
|
|
80
|
+
headerRow.appendChild(th);
|
|
81
|
+
}
|
|
82
|
+
thead.appendChild(headerRow);
|
|
83
|
+
table.appendChild(thead);
|
|
84
|
+
|
|
85
|
+
// Table body
|
|
86
|
+
this._tbody = document.createElement('tbody');
|
|
87
|
+
table.appendChild(this._tbody);
|
|
88
|
+
|
|
89
|
+
content.appendChild(table);
|
|
90
|
+
this.node.appendChild(content);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Load all commands from the registry
|
|
95
|
+
*/
|
|
96
|
+
private async _loadCommands(): Promise<void> {
|
|
97
|
+
try {
|
|
98
|
+
const commandIds = this._commands.listCommands();
|
|
99
|
+
console.log(`Commands Reference: Found ${commandIds.length} commands`);
|
|
100
|
+
this._commandsCache = [];
|
|
101
|
+
|
|
102
|
+
for (const id of commandIds) {
|
|
103
|
+
let label = '';
|
|
104
|
+
let caption = '';
|
|
105
|
+
|
|
106
|
+
// Some commands have label/caption functions that may fail without args
|
|
107
|
+
try {
|
|
108
|
+
label = this._commands.label(id) || '';
|
|
109
|
+
} catch {
|
|
110
|
+
label = '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
caption = this._commands.caption(id) || '';
|
|
115
|
+
} catch {
|
|
116
|
+
caption = '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const info: ICommandInfo = {
|
|
120
|
+
id,
|
|
121
|
+
label,
|
|
122
|
+
caption,
|
|
123
|
+
args: null
|
|
124
|
+
};
|
|
125
|
+
this._commandsCache.push(info);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Sort by command ID
|
|
129
|
+
this._commandsCache.sort((a, b) => a.id.localeCompare(b.id));
|
|
130
|
+
|
|
131
|
+
// Render initial table
|
|
132
|
+
this._renderTable(this._commandsCache);
|
|
133
|
+
|
|
134
|
+
// Load arguments asynchronously
|
|
135
|
+
void this._loadArguments();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Commands Reference: Error loading commands', error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Load command arguments asynchronously
|
|
143
|
+
*/
|
|
144
|
+
private async _loadArguments(): Promise<void> {
|
|
145
|
+
const batchSize = 50;
|
|
146
|
+
const commands = this._commandsCache;
|
|
147
|
+
|
|
148
|
+
for (let i = 0; i < commands.length; i += batchSize) {
|
|
149
|
+
const batch = commands.slice(i, i + batchSize);
|
|
150
|
+
|
|
151
|
+
await Promise.all(
|
|
152
|
+
batch.map(async cmd => {
|
|
153
|
+
try {
|
|
154
|
+
const description = await this._commands.describedBy(cmd.id);
|
|
155
|
+
if (description && description.args) {
|
|
156
|
+
const argsStr = this._formatArgs(description.args);
|
|
157
|
+
cmd.args = argsStr;
|
|
158
|
+
this._argsCache.set(cmd.id, argsStr);
|
|
159
|
+
|
|
160
|
+
// Update the cell if visible
|
|
161
|
+
this._updateArgsCell(cmd.id, argsStr);
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// Command may not have describedBy implemented
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Yield to allow UI updates
|
|
170
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Format arguments object as readable string
|
|
176
|
+
* Handles JSON Schema format: { type: "object", properties: { argName: { type: "string" } } }
|
|
177
|
+
*/
|
|
178
|
+
private _formatArgs(args: unknown): string {
|
|
179
|
+
if (!args || typeof args !== 'object') {
|
|
180
|
+
return '';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const schema = args as Record<string, unknown>;
|
|
184
|
+
|
|
185
|
+
// JSON Schema format: extract from 'properties' object
|
|
186
|
+
if (schema.properties && typeof schema.properties === 'object') {
|
|
187
|
+
const properties = schema.properties as Record<string, unknown>;
|
|
188
|
+
const propKeys = Object.keys(properties);
|
|
189
|
+
|
|
190
|
+
if (propKeys.length === 0) {
|
|
191
|
+
return '';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const formatted = propKeys.map(key => {
|
|
195
|
+
const prop = properties[key];
|
|
196
|
+
if (prop && typeof prop === 'object') {
|
|
197
|
+
const propObj = prop as Record<string, unknown>;
|
|
198
|
+
const typeInfo = propObj.type;
|
|
199
|
+
if (typeInfo) {
|
|
200
|
+
return `${key}: ${typeInfo}`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return key;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return formatted.join(', ');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Fallback: direct key-value format (non-schema)
|
|
210
|
+
const keys = Object.keys(schema).filter(
|
|
211
|
+
k => k !== 'type' && k !== '$schema'
|
|
212
|
+
);
|
|
213
|
+
if (keys.length === 0) {
|
|
214
|
+
return '';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return keys.join(', ');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Update a specific args cell in the table
|
|
222
|
+
*/
|
|
223
|
+
private _updateArgsCell(commandId: string, argsStr: string): void {
|
|
224
|
+
const rows = Array.from(this._tbody.querySelectorAll('tr'));
|
|
225
|
+
for (const row of rows) {
|
|
226
|
+
const idCell = row.querySelector('td:first-child');
|
|
227
|
+
if (idCell && idCell.textContent === commandId) {
|
|
228
|
+
const argsCell = row.querySelector(
|
|
229
|
+
'td:nth-child(4)'
|
|
230
|
+
) as HTMLElement | null;
|
|
231
|
+
if (argsCell) {
|
|
232
|
+
argsCell.textContent = argsStr;
|
|
233
|
+
argsCell.title = argsStr;
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Render the commands table
|
|
242
|
+
*/
|
|
243
|
+
private _renderTable(commands: ICommandInfo[]): void {
|
|
244
|
+
this._tbody.innerHTML = '';
|
|
245
|
+
|
|
246
|
+
for (const cmd of commands) {
|
|
247
|
+
const row = document.createElement('tr');
|
|
248
|
+
|
|
249
|
+
// Command ID cell
|
|
250
|
+
const idCell = document.createElement('td');
|
|
251
|
+
idCell.className = 'jp-CommandsReferenceWidget-commandId';
|
|
252
|
+
idCell.textContent = cmd.id;
|
|
253
|
+
idCell.title = cmd.id;
|
|
254
|
+
row.appendChild(idCell);
|
|
255
|
+
|
|
256
|
+
// Label cell
|
|
257
|
+
const labelCell = document.createElement('td');
|
|
258
|
+
labelCell.textContent = cmd.label;
|
|
259
|
+
labelCell.title = cmd.label;
|
|
260
|
+
row.appendChild(labelCell);
|
|
261
|
+
|
|
262
|
+
// Description cell
|
|
263
|
+
const descCell = document.createElement('td');
|
|
264
|
+
descCell.textContent = cmd.caption;
|
|
265
|
+
descCell.title = cmd.caption;
|
|
266
|
+
row.appendChild(descCell);
|
|
267
|
+
|
|
268
|
+
// Arguments cell
|
|
269
|
+
const argsCell = document.createElement('td');
|
|
270
|
+
const cachedArgs = this._argsCache.get(cmd.id);
|
|
271
|
+
argsCell.textContent = cachedArgs || cmd.args || '';
|
|
272
|
+
argsCell.title = cachedArgs || cmd.args || '';
|
|
273
|
+
row.appendChild(argsCell);
|
|
274
|
+
|
|
275
|
+
this._tbody.appendChild(row);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this._updateCount(commands.length, this._commandsCache.length);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Filter commands based on search input
|
|
283
|
+
*/
|
|
284
|
+
private _filterCommands(): void {
|
|
285
|
+
const query = this._searchInput.value.toLowerCase().trim();
|
|
286
|
+
|
|
287
|
+
if (!query) {
|
|
288
|
+
this._renderTable(this._commandsCache);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const filtered = this._commandsCache.filter(cmd => {
|
|
293
|
+
return (
|
|
294
|
+
cmd.id.toLowerCase().includes(query) ||
|
|
295
|
+
cmd.label.toLowerCase().includes(query) ||
|
|
296
|
+
cmd.caption.toLowerCase().includes(query)
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
this._renderTable(filtered);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Update the command count display
|
|
305
|
+
*/
|
|
306
|
+
private _updateCount(shown: number, total: number): void {
|
|
307
|
+
if (shown === total) {
|
|
308
|
+
this._countSpan.textContent = `${total} commands`;
|
|
309
|
+
} else {
|
|
310
|
+
this._countSpan.textContent = `${shown} / ${total} commands`;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Refresh the commands list
|
|
316
|
+
*/
|
|
317
|
+
refresh(): void {
|
|
318
|
+
this._argsCache.clear();
|
|
319
|
+
void this._loadCommands();
|
|
320
|
+
}
|
|
321
|
+
}
|
package/style/base.css
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the JupyterLab Developer Guide for useful CSS Patterns:
|
|
3
|
+
|
|
4
|
+
https://jupyterlab.readthedocs.io/en/stable/developer/css.html
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* Main widget container */
|
|
8
|
+
.jp-CommandsReferenceWidget {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
height: 100%;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Header with search and count */
|
|
16
|
+
.jp-CommandsReferenceWidget-header {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 12px;
|
|
20
|
+
padding: 8px 12px;
|
|
21
|
+
background: var(--jp-layout-color1);
|
|
22
|
+
border-bottom: 1px solid var(--jp-border-color2);
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Search input */
|
|
27
|
+
.jp-CommandsReferenceWidget-search {
|
|
28
|
+
flex: 1;
|
|
29
|
+
padding: 6px 10px;
|
|
30
|
+
border: 1px solid var(--jp-border-color1);
|
|
31
|
+
border-radius: 4px;
|
|
32
|
+
background: var(--jp-layout-color0);
|
|
33
|
+
color: var(--jp-ui-font-color1);
|
|
34
|
+
font-size: var(--jp-ui-font-size1);
|
|
35
|
+
outline: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.jp-CommandsReferenceWidget-search:focus {
|
|
39
|
+
border-color: var(--jp-brand-color1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.jp-CommandsReferenceWidget-search::placeholder {
|
|
43
|
+
color: var(--jp-ui-font-color3);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Command count */
|
|
47
|
+
.jp-CommandsReferenceWidget-count {
|
|
48
|
+
color: var(--jp-ui-font-color2);
|
|
49
|
+
font-size: var(--jp-ui-font-size0);
|
|
50
|
+
white-space: nowrap;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Scrollable content area */
|
|
54
|
+
.jp-CommandsReferenceWidget-content {
|
|
55
|
+
flex: 1;
|
|
56
|
+
overflow: auto;
|
|
57
|
+
padding: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Commands table */
|
|
61
|
+
.jp-CommandsReferenceWidget-table {
|
|
62
|
+
width: 100%;
|
|
63
|
+
border-collapse: collapse;
|
|
64
|
+
font-size: var(--jp-ui-font-size1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Table header */
|
|
68
|
+
.jp-CommandsReferenceWidget-table thead {
|
|
69
|
+
position: sticky;
|
|
70
|
+
top: 0;
|
|
71
|
+
background: var(--jp-layout-color2);
|
|
72
|
+
z-index: 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.jp-CommandsReferenceWidget-table th {
|
|
76
|
+
padding: 8px 12px;
|
|
77
|
+
text-align: left;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
color: var(--jp-ui-font-color1);
|
|
80
|
+
border-bottom: 2px solid var(--jp-border-color1);
|
|
81
|
+
white-space: nowrap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Table cells */
|
|
85
|
+
.jp-CommandsReferenceWidget-table td {
|
|
86
|
+
padding: 6px 12px;
|
|
87
|
+
border-bottom: 1px solid var(--jp-border-color2);
|
|
88
|
+
color: var(--jp-ui-font-color1);
|
|
89
|
+
vertical-align: top;
|
|
90
|
+
max-width: 300px;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
text-overflow: ellipsis;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Command ID column - monospace and selectable */
|
|
96
|
+
.jp-CommandsReferenceWidget-commandId {
|
|
97
|
+
font-family: var(--jp-code-font-family);
|
|
98
|
+
font-size: var(--jp-code-font-size);
|
|
99
|
+
color: var(--jp-brand-color1);
|
|
100
|
+
user-select: text;
|
|
101
|
+
white-space: nowrap;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Alternating row colors */
|
|
105
|
+
.jp-CommandsReferenceWidget-table tbody tr:nth-child(even) {
|
|
106
|
+
background: var(--jp-layout-color1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.jp-CommandsReferenceWidget-table tbody tr:nth-child(odd) {
|
|
110
|
+
background: var(--jp-layout-color0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Row hover effect */
|
|
114
|
+
.jp-CommandsReferenceWidget-table tbody tr:hover {
|
|
115
|
+
background: var(--jp-layout-color2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Column widths */
|
|
119
|
+
.jp-CommandsReferenceWidget-table th:nth-child(1),
|
|
120
|
+
.jp-CommandsReferenceWidget-table td:nth-child(1) {
|
|
121
|
+
width: 30%;
|
|
122
|
+
min-width: 200px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.jp-CommandsReferenceWidget-table th:nth-child(2),
|
|
126
|
+
.jp-CommandsReferenceWidget-table td:nth-child(2) {
|
|
127
|
+
width: 20%;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.jp-CommandsReferenceWidget-table th:nth-child(3),
|
|
131
|
+
.jp-CommandsReferenceWidget-table td:nth-child(3) {
|
|
132
|
+
width: 30%;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.jp-CommandsReferenceWidget-table th:nth-child(4),
|
|
136
|
+
.jp-CommandsReferenceWidget-table td:nth-child(4) {
|
|
137
|
+
width: 20%;
|
|
138
|
+
font-family: var(--jp-code-font-family);
|
|
139
|
+
font-size: var(--jp-code-font-size);
|
|
140
|
+
color: var(--jp-ui-font-color2);
|
|
141
|
+
}
|
package/style/index.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import url('base.css');
|
package/style/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './base.css';
|