@jupyterlab/fileeditor 4.0.0-alpha.2 → 4.0.0-alpha.21
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/tokens.js +0 -2
- package/lib/tokens.js.map +1 -1
- package/lib/widget.d.ts +12 -35
- package/lib/widget.js +45 -105
- package/lib/widget.js.map +1 -1
- package/package.json +25 -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 -3
- package/style/index.css +4 -0
- package/style/index.js +4 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { Popup, showPopup, TextItem } from '@jupyterlab/statusbar';
|
|
5
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
6
|
+
import { VDomModel, VDomRenderer } from '@jupyterlab/ui-components';
|
|
7
|
+
import { Menu } from '@lumino/widgets';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A namespace for TabSpaceComponent statics.
|
|
12
|
+
*/
|
|
13
|
+
namespace TabSpaceComponent {
|
|
14
|
+
/**
|
|
15
|
+
* The props for TabSpaceComponent.
|
|
16
|
+
*/
|
|
17
|
+
export interface IProps {
|
|
18
|
+
/**
|
|
19
|
+
* The number of spaces for indentation.
|
|
20
|
+
*
|
|
21
|
+
* `null` means use tab character for indentation.
|
|
22
|
+
*/
|
|
23
|
+
tabSpace: number | null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The application language translator.
|
|
27
|
+
*/
|
|
28
|
+
translator?: ITranslator;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A click handler for the TabSpace component. By default
|
|
32
|
+
* opens a menu allowing the user to select tabs vs spaces.
|
|
33
|
+
*/
|
|
34
|
+
handleClick: () => void;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A pure functional component for rendering the TabSpace status.
|
|
40
|
+
*/
|
|
41
|
+
function TabSpaceComponent(
|
|
42
|
+
props: TabSpaceComponent.IProps
|
|
43
|
+
): React.ReactElement<TabSpaceComponent.IProps> {
|
|
44
|
+
const translator = props.translator || nullTranslator;
|
|
45
|
+
const trans = translator.load('jupyterlab');
|
|
46
|
+
const description =
|
|
47
|
+
typeof props.tabSpace === 'number'
|
|
48
|
+
? trans.__('Spaces')
|
|
49
|
+
: trans.__('Tab Indent');
|
|
50
|
+
return (
|
|
51
|
+
<TextItem
|
|
52
|
+
onClick={props.handleClick}
|
|
53
|
+
source={
|
|
54
|
+
typeof props.tabSpace === 'number'
|
|
55
|
+
? `${description}: ${props.tabSpace}`
|
|
56
|
+
: description
|
|
57
|
+
}
|
|
58
|
+
title={trans.__('Change the indentation…')}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A VDomRenderer for a tabs vs. spaces status item.
|
|
65
|
+
*/
|
|
66
|
+
export class TabSpaceStatus extends VDomRenderer<TabSpaceStatus.Model> {
|
|
67
|
+
/**
|
|
68
|
+
* Create a new tab/space status item.
|
|
69
|
+
*/
|
|
70
|
+
constructor(options: TabSpaceStatus.IOptions) {
|
|
71
|
+
super(new TabSpaceStatus.Model());
|
|
72
|
+
this._menu = options.menu;
|
|
73
|
+
this.translator = options.translator || nullTranslator;
|
|
74
|
+
this.addClass('jp-mod-highlighted');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Render the TabSpace status item.
|
|
79
|
+
*/
|
|
80
|
+
render(): React.ReactElement<TabSpaceComponent.IProps> | null {
|
|
81
|
+
if (!this.model?.indentUnit) {
|
|
82
|
+
return null;
|
|
83
|
+
} else {
|
|
84
|
+
const tabSpace =
|
|
85
|
+
this.model.indentUnit === 'Tab'
|
|
86
|
+
? null
|
|
87
|
+
: parseInt(this.model.indentUnit, 10);
|
|
88
|
+
return (
|
|
89
|
+
<TabSpaceComponent
|
|
90
|
+
tabSpace={tabSpace}
|
|
91
|
+
handleClick={() => this._handleClick()}
|
|
92
|
+
translator={this.translator}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Handle a click on the status item.
|
|
100
|
+
*/
|
|
101
|
+
private _handleClick(): void {
|
|
102
|
+
const menu = this._menu;
|
|
103
|
+
if (this._popup) {
|
|
104
|
+
this._popup.dispose();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
menu.aboutToClose.connect(this._menuClosed, this);
|
|
108
|
+
|
|
109
|
+
this._popup = showPopup({
|
|
110
|
+
body: menu,
|
|
111
|
+
anchor: this,
|
|
112
|
+
align: 'right'
|
|
113
|
+
});
|
|
114
|
+
// Update the menu items
|
|
115
|
+
menu.update();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private _menuClosed(): void {
|
|
119
|
+
this.removeClass('jp-mod-clicked');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protected translator: ITranslator;
|
|
123
|
+
private _menu: Menu;
|
|
124
|
+
private _popup: Popup | null = null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A namespace for TabSpace statics.
|
|
129
|
+
*/
|
|
130
|
+
export namespace TabSpaceStatus {
|
|
131
|
+
/**
|
|
132
|
+
* A VDomModel for the TabSpace status item.
|
|
133
|
+
*/
|
|
134
|
+
export class Model extends VDomModel {
|
|
135
|
+
/**
|
|
136
|
+
* Code editor indentation unit
|
|
137
|
+
*/
|
|
138
|
+
get indentUnit(): string | null {
|
|
139
|
+
return this._indentUnit;
|
|
140
|
+
}
|
|
141
|
+
set indentUnit(v: string | null) {
|
|
142
|
+
if (v !== this._indentUnit) {
|
|
143
|
+
this._indentUnit = v;
|
|
144
|
+
this.stateChanged.emit();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private _indentUnit: string | null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Options for creating a TabSpace status item.
|
|
153
|
+
*/
|
|
154
|
+
export interface IOptions {
|
|
155
|
+
/**
|
|
156
|
+
* A menu to open when clicking on the status item. This should allow
|
|
157
|
+
* the user to make a different selection about tabs/spaces.
|
|
158
|
+
*/
|
|
159
|
+
menu: Menu;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Language translator.
|
|
163
|
+
*/
|
|
164
|
+
translator?: ITranslator;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { DocumentRegistry, IDocumentWidget } from '@jupyterlab/docregistry';
|
|
5
|
+
import {
|
|
6
|
+
TableOfContents,
|
|
7
|
+
TableOfContentsFactory,
|
|
8
|
+
TableOfContentsModel
|
|
9
|
+
} from '@jupyterlab/toc';
|
|
10
|
+
import { FileEditor } from '../widget';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Interface describing a file editor heading.
|
|
14
|
+
*/
|
|
15
|
+
export interface IEditorHeading extends TableOfContents.IHeading {
|
|
16
|
+
/**
|
|
17
|
+
* Heading line number.
|
|
18
|
+
*/
|
|
19
|
+
line: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Base table of contents model factory for file editor
|
|
24
|
+
*/
|
|
25
|
+
export abstract class EditorTableOfContentsFactory extends TableOfContentsFactory<
|
|
26
|
+
IDocumentWidget<FileEditor>
|
|
27
|
+
> {
|
|
28
|
+
/**
|
|
29
|
+
* Create a new table of contents model for the widget
|
|
30
|
+
*
|
|
31
|
+
* @param widget - widget
|
|
32
|
+
* @param configuration - Table of contents configuration
|
|
33
|
+
* @returns The table of contents model
|
|
34
|
+
*/
|
|
35
|
+
createNew(
|
|
36
|
+
widget: IDocumentWidget<FileEditor, DocumentRegistry.IModel>,
|
|
37
|
+
configuration?: TableOfContents.IConfig
|
|
38
|
+
): TableOfContentsModel<
|
|
39
|
+
IEditorHeading,
|
|
40
|
+
IDocumentWidget<FileEditor, DocumentRegistry.IModel>
|
|
41
|
+
> {
|
|
42
|
+
const model = super.createNew(
|
|
43
|
+
widget,
|
|
44
|
+
configuration
|
|
45
|
+
) as TableOfContentsModel<
|
|
46
|
+
IEditorHeading,
|
|
47
|
+
IDocumentWidget<FileEditor, DocumentRegistry.IModel>
|
|
48
|
+
>;
|
|
49
|
+
|
|
50
|
+
const onActiveHeadingChanged = (
|
|
51
|
+
model: TableOfContentsModel<
|
|
52
|
+
IEditorHeading,
|
|
53
|
+
IDocumentWidget<FileEditor, DocumentRegistry.IModel>
|
|
54
|
+
>,
|
|
55
|
+
heading: IEditorHeading | null
|
|
56
|
+
) => {
|
|
57
|
+
if (heading) {
|
|
58
|
+
widget.content.editor.setCursorPosition({
|
|
59
|
+
line: heading.line,
|
|
60
|
+
column: 0
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
model.activeHeadingChanged.connect(onActiveHeadingChanged);
|
|
66
|
+
widget.disposed.connect(() => {
|
|
67
|
+
model.activeHeadingChanged.disconnect(onActiveHeadingChanged);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return model;
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/toc/index.ts
ADDED
package/src/toc/latex.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { DocumentRegistry, IDocumentWidget } from '@jupyterlab/docregistry';
|
|
5
|
+
import {
|
|
6
|
+
TableOfContents,
|
|
7
|
+
TableOfContentsModel,
|
|
8
|
+
TableOfContentsUtils
|
|
9
|
+
} from '@jupyterlab/toc';
|
|
10
|
+
import { Widget } from '@lumino/widgets';
|
|
11
|
+
import { FileEditor } from '../widget';
|
|
12
|
+
import { EditorTableOfContentsFactory, IEditorHeading } from './factory';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Maps LaTeX section headings to HTML header levels.
|
|
16
|
+
*
|
|
17
|
+
* ## Notes
|
|
18
|
+
*
|
|
19
|
+
* - As `part` and `chapter` section headings appear to be less common, assign them to heading level 1.
|
|
20
|
+
*
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
const LATEX_LEVELS: { [label: string]: number } = {
|
|
24
|
+
part: 1, // Only available for report and book classes
|
|
25
|
+
chapter: 1, // Only available for report and book classes
|
|
26
|
+
section: 1,
|
|
27
|
+
subsection: 2,
|
|
28
|
+
subsubsection: 3,
|
|
29
|
+
paragraph: 4,
|
|
30
|
+
subparagraph: 5
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Regular expression to create the outline
|
|
35
|
+
*/
|
|
36
|
+
const SECTIONS = /^\s*\\(section|subsection|subsubsection){(.+)}/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Table of content model for LaTeX files.
|
|
40
|
+
*/
|
|
41
|
+
export class LaTeXTableOfContentsModel extends TableOfContentsModel<
|
|
42
|
+
IEditorHeading,
|
|
43
|
+
IDocumentWidget<FileEditor, DocumentRegistry.IModel>
|
|
44
|
+
> {
|
|
45
|
+
/**
|
|
46
|
+
* Type of document supported by the model.
|
|
47
|
+
*
|
|
48
|
+
* #### Notes
|
|
49
|
+
* A `data-document-type` attribute with this value will be set
|
|
50
|
+
* on the tree view `.jp-TableOfContents-content[data-document-type="..."]`
|
|
51
|
+
*/
|
|
52
|
+
get documentType(): string {
|
|
53
|
+
return 'latex';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* List of configuration options supported by the model.
|
|
58
|
+
*/
|
|
59
|
+
get supportedOptions(): (keyof TableOfContents.IConfig)[] {
|
|
60
|
+
return ['maximalDepth', 'numberHeaders'];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Produce the headings for a document.
|
|
65
|
+
*
|
|
66
|
+
* @returns The list of new headings or `null` if nothing needs to be updated.
|
|
67
|
+
*/
|
|
68
|
+
protected getHeadings(): Promise<IEditorHeading[] | null> {
|
|
69
|
+
if (!this.isActive) {
|
|
70
|
+
return Promise.resolve(null);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Split the text into lines:
|
|
74
|
+
const lines = this.widget.content.model.sharedModel
|
|
75
|
+
.getSource()
|
|
76
|
+
.split('\n') as Array<string>;
|
|
77
|
+
|
|
78
|
+
const levels = new Array<number>();
|
|
79
|
+
let previousLevel = levels.length;
|
|
80
|
+
const headings = new Array<IEditorHeading>();
|
|
81
|
+
for (let i = 0; i < lines.length; i++) {
|
|
82
|
+
const match = lines[i].match(SECTIONS);
|
|
83
|
+
if (match) {
|
|
84
|
+
const level = LATEX_LEVELS[match[1]];
|
|
85
|
+
if (level <= this.configuration.maximalDepth) {
|
|
86
|
+
const prefix = TableOfContentsUtils.getPrefix(
|
|
87
|
+
level,
|
|
88
|
+
previousLevel,
|
|
89
|
+
levels,
|
|
90
|
+
{
|
|
91
|
+
...this.configuration,
|
|
92
|
+
// Force base numbering and numbering first level
|
|
93
|
+
baseNumbering: 1,
|
|
94
|
+
numberingH1: true
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
previousLevel = level;
|
|
98
|
+
|
|
99
|
+
headings.push({
|
|
100
|
+
text: match[2],
|
|
101
|
+
prefix: prefix,
|
|
102
|
+
level,
|
|
103
|
+
line: i
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return Promise.resolve(headings);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Table of content model factory for LaTeX files.
|
|
114
|
+
*/
|
|
115
|
+
export class LaTeXTableOfContentsFactory extends EditorTableOfContentsFactory {
|
|
116
|
+
/**
|
|
117
|
+
* Whether the factory can handle the widget or not.
|
|
118
|
+
*
|
|
119
|
+
* @param widget - widget
|
|
120
|
+
* @returns boolean indicating a ToC can be generated
|
|
121
|
+
*/
|
|
122
|
+
isApplicable(widget: Widget): boolean {
|
|
123
|
+
const isApplicable = super.isApplicable(widget);
|
|
124
|
+
|
|
125
|
+
if (isApplicable) {
|
|
126
|
+
let mime = (widget as any).content?.model?.mimeType;
|
|
127
|
+
return mime && (mime === 'text/x-latex' || mime === 'text/x-stex');
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a new table of contents model for the widget
|
|
134
|
+
*
|
|
135
|
+
* @param widget - widget
|
|
136
|
+
* @param configuration - Table of contents configuration
|
|
137
|
+
* @returns The table of contents model
|
|
138
|
+
*/
|
|
139
|
+
protected _createNew(
|
|
140
|
+
widget: IDocumentWidget<FileEditor, DocumentRegistry.IModel>,
|
|
141
|
+
configuration?: TableOfContents.IConfig
|
|
142
|
+
): LaTeXTableOfContentsModel {
|
|
143
|
+
return new LaTeXTableOfContentsModel(widget, configuration);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { DocumentRegistry, IDocumentWidget } from '@jupyterlab/docregistry';
|
|
5
|
+
import {
|
|
6
|
+
TableOfContents,
|
|
7
|
+
TableOfContentsModel,
|
|
8
|
+
TableOfContentsUtils
|
|
9
|
+
} from '@jupyterlab/toc';
|
|
10
|
+
import { Widget } from '@lumino/widgets';
|
|
11
|
+
import { FileEditor } from '../widget';
|
|
12
|
+
import { EditorTableOfContentsFactory, IEditorHeading } from './factory';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Table of content model for Markdown files.
|
|
16
|
+
*/
|
|
17
|
+
export class MarkdownTableOfContentsModel extends TableOfContentsModel<
|
|
18
|
+
IEditorHeading,
|
|
19
|
+
IDocumentWidget<FileEditor, DocumentRegistry.IModel>
|
|
20
|
+
> {
|
|
21
|
+
/**
|
|
22
|
+
* Type of document supported by the model.
|
|
23
|
+
*
|
|
24
|
+
* #### Notes
|
|
25
|
+
* A `data-document-type` attribute with this value will be set
|
|
26
|
+
* on the tree view `.jp-TableOfContents-content[data-document-type="..."]`
|
|
27
|
+
*/
|
|
28
|
+
get documentType(): string {
|
|
29
|
+
return 'markdown';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Produce the headings for a document.
|
|
34
|
+
*
|
|
35
|
+
* @returns The list of new headings or `null` if nothing needs to be updated.
|
|
36
|
+
*/
|
|
37
|
+
protected getHeadings(): Promise<IEditorHeading[] | null> {
|
|
38
|
+
if (!this.isActive) {
|
|
39
|
+
return Promise.resolve(null);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const content = this.widget.content.model.sharedModel.getSource();
|
|
43
|
+
|
|
44
|
+
const headings = TableOfContentsUtils.filterHeadings(
|
|
45
|
+
TableOfContentsUtils.Markdown.getHeadings(content),
|
|
46
|
+
{
|
|
47
|
+
...this.configuration,
|
|
48
|
+
// Force removing numbering as they cannot be displayed
|
|
49
|
+
// in the document
|
|
50
|
+
numberHeaders: false
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
return Promise.resolve(headings);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Table of content model factory for Markdown files.
|
|
59
|
+
*/
|
|
60
|
+
export class MarkdownTableOfContentsFactory extends EditorTableOfContentsFactory {
|
|
61
|
+
/**
|
|
62
|
+
* Whether the factory can handle the widget or not.
|
|
63
|
+
*
|
|
64
|
+
* @param widget - widget
|
|
65
|
+
* @returns boolean indicating a ToC can be generated
|
|
66
|
+
*/
|
|
67
|
+
isApplicable(widget: Widget): boolean {
|
|
68
|
+
const isApplicable = super.isApplicable(widget);
|
|
69
|
+
|
|
70
|
+
if (isApplicable) {
|
|
71
|
+
let mime = (widget as any).content?.model?.mimeType;
|
|
72
|
+
return mime && TableOfContentsUtils.Markdown.isMarkdown(mime);
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a new table of contents model for the widget
|
|
79
|
+
*
|
|
80
|
+
* @param widget - widget
|
|
81
|
+
* @param configuration - Table of contents configuration
|
|
82
|
+
* @returns The table of contents model
|
|
83
|
+
*/
|
|
84
|
+
protected _createNew(
|
|
85
|
+
widget: IDocumentWidget<FileEditor, DocumentRegistry.IModel>,
|
|
86
|
+
configuration?: TableOfContents.IConfig
|
|
87
|
+
): MarkdownTableOfContentsModel {
|
|
88
|
+
return new MarkdownTableOfContentsModel(widget, configuration);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -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
|
+
);
|