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 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
+ [![GitHub Actions](https://github.com/stellarshenson/jupyterlab_show_commands_reference_extension/actions/workflows/build.yml/badge.svg)](https://github.com/stellarshenson/jupyterlab_show_commands_reference_extension/actions/workflows/build.yml)
4
+ [![npm version](https://img.shields.io/npm/v/jupyterlab_show_commands_reference_extension.svg)](https://www.npmjs.com/package/jupyterlab_show_commands_reference_extension)
5
+ [![PyPI version](https://img.shields.io/pypi/v/jupyterlab-show-commands-reference-extension.svg)](https://pypi.org/project/jupyterlab-show-commands-reference-extension/)
6
+ [![Total PyPI downloads](https://static.pepy.tech/badge/jupyterlab-show-commands-reference-extension)](https://pepy.tech/project/jupyterlab-show-commands-reference-extension)
7
+ [![JupyterLab 4](https://img.shields.io/badge/JupyterLab-4-orange.svg)](https://jupyterlab.readthedocs.io/en/stable/)
8
+ [![Brought To You By KOLOMOLO](https://img.shields.io/badge/Brought%20To%20You%20By-KOLOMOLO-00ffff?style=flat)](https://kolomolo.com)
9
+ [![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-blue?style=flat)](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
+ ![Command Palette](.resources/screenshot-command.png)
14
+
15
+ ![Commands Reference Panel](.resources/screenshot-commands-reference.png)
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
@@ -0,0 +1,6 @@
1
+ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
+ /**
3
+ * Initialization data for the jupyterlab_show_commands_reference_extension extension.
4
+ */
5
+ declare const plugin: JupyterFrontEndPlugin<void>;
6
+ export default plugin;
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;
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests
3
+ */
4
+
5
+ describe('jupyterlab_show_commands_reference_extension', () => {
6
+ it('should be tested', () => {
7
+ expect(1 + 1).toEqual(2);
8
+ });
9
+ });
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
+ }
@@ -0,0 +1 @@
1
+ @import url('base.css');
package/style/index.js ADDED
@@ -0,0 +1 @@
1
+ import './base.css';