@jupyterlab/fileeditor 4.0.0-alpha.9 → 4.0.0-beta.0
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/lib/fileeditorlspadapter.d.ts +96 -0
- package/lib/fileeditorlspadapter.js +175 -0
- package/lib/fileeditorlspadapter.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -1
- package/lib/searchprovider.d.ts +60 -0
- package/lib/searchprovider.js +80 -0
- package/lib/searchprovider.js.map +1 -0
- package/lib/syntaxstatus.d.ts +74 -0
- package/lib/syntaxstatus.js +151 -0
- package/lib/syntaxstatus.js.map +1 -0
- package/lib/tabspacestatus.d.ts +8 -12
- package/lib/tabspacestatus.js +23 -27
- package/lib/tabspacestatus.js.map +1 -1
- package/lib/toc/factory.d.ts +25 -0
- package/lib/toc/factory.js +32 -0
- package/lib/toc/factory.js.map +1 -0
- package/lib/toc/index.d.ts +4 -0
- package/lib/toc/index.js +7 -0
- package/lib/toc/index.js.map +1 -0
- package/lib/toc/latex.d.ts +48 -0
- package/lib/toc/latex.js +117 -0
- package/lib/toc/latex.js.map +1 -0
- package/lib/toc/markdown.d.ts +44 -0
- package/lib/toc/markdown.js +68 -0
- package/lib/toc/markdown.js.map +1 -0
- package/lib/toc/python.d.ts +44 -0
- package/lib/toc/python.js +116 -0
- package/lib/toc/python.js.map +1 -0
- package/lib/widget.d.ts +4 -0
- package/lib/widget.js +21 -4
- package/lib/widget.js.map +1 -1
- package/package.json +26 -21
- package/src/fileeditorlspadapter.ts +225 -0
- package/src/index.ts +14 -0
- package/src/searchprovider.ts +115 -0
- package/src/syntaxstatus.tsx +217 -0
- package/src/tabspacestatus.tsx +166 -0
- package/src/toc/factory.ts +72 -0
- package/src/toc/index.ts +7 -0
- package/src/toc/latex.ts +145 -0
- package/src/toc/markdown.ts +90 -0
- package/src/toc/python.ts +143 -0
- package/src/tokens.ts +20 -0
- package/src/widget.ts +267 -0
- package/style/base.css +8 -0
- package/style/index.css +4 -0
- package/style/index.js +4 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
/*eslint no-invalid-regexp: ["error", { "allowConstructorFlags": ["d"] }]*/
|
|
5
|
+
|
|
6
|
+
import { DocumentRegistry, IDocumentWidget } from '@jupyterlab/docregistry';
|
|
7
|
+
import { TableOfContents, TableOfContentsModel } from '@jupyterlab/toc';
|
|
8
|
+
import { Widget } from '@lumino/widgets';
|
|
9
|
+
import { FileEditor } from '../widget';
|
|
10
|
+
import { EditorTableOfContentsFactory, IEditorHeading } from './factory';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Regular expression to create the outline
|
|
14
|
+
*/
|
|
15
|
+
let KEYWORDS: RegExp;
|
|
16
|
+
try {
|
|
17
|
+
// https://github.com/tc39/proposal-regexp-match-indices was accepted
|
|
18
|
+
// in May 2021 (https://github.com/tc39/proposals/blob/main/finished-proposals.md)
|
|
19
|
+
// So we will fallback to the polyfill regexp-match-indices if not available
|
|
20
|
+
KEYWORDS = new RegExp('^\\s*(class |def |from |import )', 'd');
|
|
21
|
+
} catch {
|
|
22
|
+
KEYWORDS = new RegExp('^\\s*(class |def |from |import )');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Table of content model for Python files.
|
|
27
|
+
*/
|
|
28
|
+
export class PythonTableOfContentsModel extends TableOfContentsModel<
|
|
29
|
+
IEditorHeading,
|
|
30
|
+
IDocumentWidget<FileEditor, DocumentRegistry.IModel>
|
|
31
|
+
> {
|
|
32
|
+
/**
|
|
33
|
+
* Type of document supported by the model.
|
|
34
|
+
*
|
|
35
|
+
* #### Notes
|
|
36
|
+
* A `data-document-type` attribute with this value will be set
|
|
37
|
+
* on the tree view `.jp-TableOfContents-content[data-document-type="..."]`
|
|
38
|
+
*/
|
|
39
|
+
get documentType(): string {
|
|
40
|
+
return 'python';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Produce the headings for a document.
|
|
45
|
+
*
|
|
46
|
+
* @returns The list of new headings or `null` if nothing needs to be updated.
|
|
47
|
+
*/
|
|
48
|
+
protected async getHeadings(): Promise<IEditorHeading[] | null> {
|
|
49
|
+
if (!this.isActive) {
|
|
50
|
+
return Promise.resolve(null);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Split the text into lines:
|
|
54
|
+
const lines = this.widget.content.model.sharedModel
|
|
55
|
+
.getSource()
|
|
56
|
+
.split('\n') as Array<string>;
|
|
57
|
+
|
|
58
|
+
// Iterate over the lines to get the heading level and text for each line:
|
|
59
|
+
let headings = new Array<IEditorHeading>();
|
|
60
|
+
let processingImports = false;
|
|
61
|
+
|
|
62
|
+
let indent = 1;
|
|
63
|
+
|
|
64
|
+
let lineIdx = -1;
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
lineIdx++;
|
|
67
|
+
let hasKeyword: RegExpExecArray | null;
|
|
68
|
+
if (KEYWORDS.flags.includes('d')) {
|
|
69
|
+
hasKeyword = KEYWORDS.exec(line);
|
|
70
|
+
} else {
|
|
71
|
+
const { default: execWithIndices } = await import(
|
|
72
|
+
'regexp-match-indices'
|
|
73
|
+
);
|
|
74
|
+
hasKeyword = execWithIndices(KEYWORDS, line);
|
|
75
|
+
}
|
|
76
|
+
if (hasKeyword) {
|
|
77
|
+
// Index 0 contains the spaces, index 1 is the keyword group
|
|
78
|
+
const [start] = (hasKeyword as any).indices[1];
|
|
79
|
+
if (indent === 1 && start > 0) {
|
|
80
|
+
indent = start;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isImport = ['from ', 'import '].includes(hasKeyword[1]);
|
|
84
|
+
if (isImport && processingImports) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
processingImports = isImport;
|
|
88
|
+
|
|
89
|
+
const level = 1 + start / indent;
|
|
90
|
+
|
|
91
|
+
if (level > this.configuration.maximalDepth) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
headings.push({
|
|
96
|
+
text: line.slice(start),
|
|
97
|
+
level,
|
|
98
|
+
line: lineIdx
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return Promise.resolve(headings);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Table of content model factory for Python files.
|
|
109
|
+
*/
|
|
110
|
+
export class PythonTableOfContentsFactory extends EditorTableOfContentsFactory {
|
|
111
|
+
/**
|
|
112
|
+
* Whether the factory can handle the widget or not.
|
|
113
|
+
*
|
|
114
|
+
* @param widget - widget
|
|
115
|
+
* @returns boolean indicating a ToC can be generated
|
|
116
|
+
*/
|
|
117
|
+
isApplicable(widget: Widget): boolean {
|
|
118
|
+
const isApplicable = super.isApplicable(widget);
|
|
119
|
+
|
|
120
|
+
if (isApplicable) {
|
|
121
|
+
let mime = (widget as any).content?.model?.mimeType;
|
|
122
|
+
return (
|
|
123
|
+
mime &&
|
|
124
|
+
(mime === 'application/x-python-code' || mime === 'text/x-python')
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create a new table of contents model for the widget
|
|
132
|
+
*
|
|
133
|
+
* @param widget - widget
|
|
134
|
+
* @param configuration - Table of contents configuration
|
|
135
|
+
* @returns The table of contents model
|
|
136
|
+
*/
|
|
137
|
+
protected _createNew(
|
|
138
|
+
widget: IDocumentWidget<FileEditor>,
|
|
139
|
+
configuration?: TableOfContents.IConfig
|
|
140
|
+
): PythonTableOfContentsModel {
|
|
141
|
+
return new PythonTableOfContentsModel(widget, configuration);
|
|
142
|
+
}
|
|
143
|
+
}
|
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { IWidgetTracker } from '@jupyterlab/apputils';
|
|
5
|
+
import { IDocumentWidget } from '@jupyterlab/docregistry';
|
|
6
|
+
import { Token } from '@lumino/coreutils';
|
|
7
|
+
import { FileEditor } from './widget';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A class that tracks editor widgets.
|
|
11
|
+
*/
|
|
12
|
+
export interface IEditorTracker
|
|
13
|
+
extends IWidgetTracker<IDocumentWidget<FileEditor>> {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The editor tracker token.
|
|
17
|
+
*/
|
|
18
|
+
export const IEditorTracker = new Token<IEditorTracker>(
|
|
19
|
+
'@jupyterlab/fileeditor:IEditorTracker'
|
|
20
|
+
);
|
package/src/widget.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CodeEditor,
|
|
6
|
+
CodeEditorWrapper,
|
|
7
|
+
IEditorMimeTypeService,
|
|
8
|
+
IEditorServices
|
|
9
|
+
} from '@jupyterlab/codeeditor';
|
|
10
|
+
import {
|
|
11
|
+
ABCWidgetFactory,
|
|
12
|
+
DocumentRegistry,
|
|
13
|
+
DocumentWidget,
|
|
14
|
+
IDocumentWidget
|
|
15
|
+
} from '@jupyterlab/docregistry';
|
|
16
|
+
import { textEditorIcon } from '@jupyterlab/ui-components';
|
|
17
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
18
|
+
import { Message } from '@lumino/messaging';
|
|
19
|
+
import { StackedLayout, Widget } from '@lumino/widgets';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The data attribute added to a widget that can run code.
|
|
23
|
+
*/
|
|
24
|
+
const CODE_RUNNER = 'jpCodeRunner';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The data attribute added to a widget that can undo.
|
|
28
|
+
*/
|
|
29
|
+
const UNDOER = 'jpUndoer';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A widget for editors.
|
|
33
|
+
*/
|
|
34
|
+
export class FileEditor extends Widget {
|
|
35
|
+
/**
|
|
36
|
+
* Construct a new editor widget.
|
|
37
|
+
*/
|
|
38
|
+
constructor(options: FileEditor.IOptions) {
|
|
39
|
+
super();
|
|
40
|
+
this.addClass('jp-FileEditor');
|
|
41
|
+
|
|
42
|
+
const context = (this._context = options.context);
|
|
43
|
+
this._mimeTypeService = options.mimeTypeService;
|
|
44
|
+
|
|
45
|
+
const editorWidget = (this._editorWidget = new CodeEditorWrapper({
|
|
46
|
+
factory: options.factory,
|
|
47
|
+
model: context.model,
|
|
48
|
+
editorOptions: {
|
|
49
|
+
config: FileEditor.defaultEditorConfig
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
this._editorWidget.addClass('jp-FileEditorCodeWrapper');
|
|
53
|
+
this._editorWidget.node.dataset[CODE_RUNNER] = 'true';
|
|
54
|
+
this._editorWidget.node.dataset[UNDOER] = 'true';
|
|
55
|
+
|
|
56
|
+
this.editor = editorWidget.editor;
|
|
57
|
+
this.model = editorWidget.model;
|
|
58
|
+
|
|
59
|
+
void context.ready.then(() => {
|
|
60
|
+
this._onContextReady();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Listen for changes to the path.
|
|
64
|
+
this._onPathChanged();
|
|
65
|
+
context.pathChanged.connect(this._onPathChanged, this);
|
|
66
|
+
|
|
67
|
+
const layout = (this.layout = new StackedLayout());
|
|
68
|
+
layout.addWidget(editorWidget);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the context for the editor widget.
|
|
73
|
+
*/
|
|
74
|
+
get context(): DocumentRegistry.Context {
|
|
75
|
+
return this._context;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A promise that resolves when the file editor is ready.
|
|
80
|
+
*/
|
|
81
|
+
get ready(): Promise<void> {
|
|
82
|
+
return this._ready.promise;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Handle the DOM events for the widget.
|
|
87
|
+
*
|
|
88
|
+
* @param event - The DOM event sent to the widget.
|
|
89
|
+
*
|
|
90
|
+
* #### Notes
|
|
91
|
+
* This method implements the DOM `EventListener` interface and is
|
|
92
|
+
* called in response to events on the widget's node. It should
|
|
93
|
+
* not be called directly by user code.
|
|
94
|
+
*/
|
|
95
|
+
handleEvent(event: Event): void {
|
|
96
|
+
if (!this.model) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
switch (event.type) {
|
|
100
|
+
case 'mousedown':
|
|
101
|
+
this._ensureFocus();
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Handle `after-attach` messages for the widget.
|
|
110
|
+
*/
|
|
111
|
+
protected onAfterAttach(msg: Message): void {
|
|
112
|
+
super.onAfterAttach(msg);
|
|
113
|
+
const node = this.node;
|
|
114
|
+
node.addEventListener('mousedown', this);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handle `before-detach` messages for the widget.
|
|
119
|
+
*/
|
|
120
|
+
protected onBeforeDetach(msg: Message): void {
|
|
121
|
+
const node = this.node;
|
|
122
|
+
node.removeEventListener('mousedown', this);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Handle `'activate-request'` messages.
|
|
127
|
+
*/
|
|
128
|
+
protected onActivateRequest(msg: Message): void {
|
|
129
|
+
this._ensureFocus();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Ensure that the widget has focus.
|
|
134
|
+
*/
|
|
135
|
+
private _ensureFocus(): void {
|
|
136
|
+
if (!this.editor.hasFocus()) {
|
|
137
|
+
this.editor.focus();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Handle actions that should be taken when the context is ready.
|
|
143
|
+
*/
|
|
144
|
+
private _onContextReady(): void {
|
|
145
|
+
if (this.isDisposed) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Prevent the initial loading from disk from being in the editor history.
|
|
150
|
+
this.editor.clearHistory();
|
|
151
|
+
// Resolve the ready promise.
|
|
152
|
+
this._ready.resolve(undefined);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Handle a change to the path.
|
|
157
|
+
*/
|
|
158
|
+
private _onPathChanged(): void {
|
|
159
|
+
const editor = this.editor;
|
|
160
|
+
const localPath = this._context.localPath;
|
|
161
|
+
|
|
162
|
+
editor.model.mimeType =
|
|
163
|
+
this._mimeTypeService.getMimeTypeByFilePath(localPath);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
model: CodeEditor.IModel;
|
|
167
|
+
editor: CodeEditor.IEditor;
|
|
168
|
+
private _context: DocumentRegistry.Context;
|
|
169
|
+
private _editorWidget: CodeEditorWrapper;
|
|
170
|
+
private _mimeTypeService: IEditorMimeTypeService;
|
|
171
|
+
private _ready = new PromiseDelegate<void>();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The namespace for editor widget statics.
|
|
176
|
+
*/
|
|
177
|
+
export namespace FileEditor {
|
|
178
|
+
/**
|
|
179
|
+
* The options used to create an editor widget.
|
|
180
|
+
*/
|
|
181
|
+
export interface IOptions {
|
|
182
|
+
/**
|
|
183
|
+
* A code editor factory.
|
|
184
|
+
*/
|
|
185
|
+
factory: CodeEditor.Factory;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The mime type service for the editor.
|
|
189
|
+
*/
|
|
190
|
+
mimeTypeService: IEditorMimeTypeService;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The document context associated with the editor.
|
|
194
|
+
*/
|
|
195
|
+
context: DocumentRegistry.CodeContext;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* File editor default configuration.
|
|
200
|
+
*/
|
|
201
|
+
export const defaultEditorConfig: Record<string, any> = {
|
|
202
|
+
lineNumbers: true,
|
|
203
|
+
scrollPastEnd: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* A widget factory for editors.
|
|
209
|
+
*/
|
|
210
|
+
export class FileEditorFactory extends ABCWidgetFactory<
|
|
211
|
+
IDocumentWidget<FileEditor>,
|
|
212
|
+
DocumentRegistry.ICodeModel
|
|
213
|
+
> {
|
|
214
|
+
/**
|
|
215
|
+
* Construct a new editor widget factory.
|
|
216
|
+
*/
|
|
217
|
+
constructor(options: FileEditorFactory.IOptions) {
|
|
218
|
+
super(options.factoryOptions);
|
|
219
|
+
this._services = options.editorServices;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create a new widget given a context.
|
|
224
|
+
*/
|
|
225
|
+
protected createNewWidget(
|
|
226
|
+
context: DocumentRegistry.CodeContext
|
|
227
|
+
): IDocumentWidget<FileEditor> {
|
|
228
|
+
const func = this._services.factoryService.newDocumentEditor;
|
|
229
|
+
const factory: CodeEditor.Factory = options => {
|
|
230
|
+
// Use same id as document factory
|
|
231
|
+
return func(options);
|
|
232
|
+
};
|
|
233
|
+
const content = new FileEditor({
|
|
234
|
+
factory,
|
|
235
|
+
context,
|
|
236
|
+
mimeTypeService: this._services.mimeTypeService
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
content.title.icon = textEditorIcon;
|
|
240
|
+
const widget = new DocumentWidget({ content, context });
|
|
241
|
+
return widget;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private _services: IEditorServices;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The namespace for `FileEditorFactory` class statics.
|
|
249
|
+
*/
|
|
250
|
+
export namespace FileEditorFactory {
|
|
251
|
+
/**
|
|
252
|
+
* The options used to create an editor widget factory.
|
|
253
|
+
*/
|
|
254
|
+
export interface IOptions {
|
|
255
|
+
/**
|
|
256
|
+
* The editor services used by the factory.
|
|
257
|
+
*/
|
|
258
|
+
editorServices: IEditorServices;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The factory options associated with the factory.
|
|
262
|
+
*/
|
|
263
|
+
factoryOptions: DocumentRegistry.IWidgetFactoryOptions<
|
|
264
|
+
IDocumentWidget<FileEditor>
|
|
265
|
+
>;
|
|
266
|
+
}
|
|
267
|
+
}
|
package/style/base.css
CHANGED
package/style/index.css
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
@import url('~@jupyterlab/ui-components/style/index.css');
|
|
9
9
|
@import url('~@jupyterlab/apputils/style/index.css');
|
|
10
10
|
@import url('~@jupyterlab/codeeditor/style/index.css');
|
|
11
|
+
@import url('~@jupyterlab/documentsearch/style/index.css');
|
|
12
|
+
@import url('~@jupyterlab/codemirror/style/index.css');
|
|
11
13
|
@import url('~@jupyterlab/docregistry/style/index.css');
|
|
14
|
+
@import url('~@jupyterlab/lsp/style/index.css');
|
|
15
|
+
@import url('~@jupyterlab/toc/style/index.css');
|
|
12
16
|
|
|
13
17
|
@import url('./base.css');
|
package/style/index.js
CHANGED
|
@@ -8,6 +8,10 @@ import '@lumino/widgets/style/index.js';
|
|
|
8
8
|
import '@jupyterlab/ui-components/style/index.js';
|
|
9
9
|
import '@jupyterlab/apputils/style/index.js';
|
|
10
10
|
import '@jupyterlab/codeeditor/style/index.js';
|
|
11
|
+
import '@jupyterlab/documentsearch/style/index.js';
|
|
12
|
+
import '@jupyterlab/codemirror/style/index.js';
|
|
11
13
|
import '@jupyterlab/docregistry/style/index.js';
|
|
14
|
+
import '@jupyterlab/lsp/style/index.js';
|
|
15
|
+
import '@jupyterlab/toc/style/index.js';
|
|
12
16
|
|
|
13
17
|
import './base.css';
|