Skip to content

Commit c4257b5

Browse files
authored
Merge pull request #321 from krassowski/transclusion-refactor
Make code extractors and replacements modular
2 parents 01ea6a9 + dbdf2aa commit c4257b5

35 files changed

+915
-322
lines changed

docs/Extending.ipynb

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"#### How to override the default implementation of a feature?\n",
4747
"\n",
4848
"You can specify a list of extensions to be disabled the the feature manager\n",
49-
"passing their plugin identifiers in `superseds` field of `IFeatureOptions`."
49+
"passing their plugin identifiers in `supersedes` field of `IFeatureOptions`."
5050
]
5151
},
5252
{
@@ -64,7 +64,7 @@
6464
"\n",
6565
"#### Why virtual editor?\n",
6666
"\n",
67-
"The vitual editor takes multiple instances of your editor (e.g. in a notebook)\n",
67+
"The virtual editor takes multiple instances of your editor (e.g. in a notebook)\n",
6868
"and makes them act like a single editor. For example, when \"onKeyPress\" event is\n",
6969
"bound on the VirtualEditor instance, it should be bound onto each actual code\n",
7070
"editor; this allows the features to be implemented without the knowledge about\n",
@@ -89,7 +89,7 @@
8989
"`IDocumentWidget` interface. Each such widget has to adapted by a\n",
9090
"`WidgetAdapter` to enable its use with the LSP extension. The role of the\n",
9191
"`WidgetAdapter` is to extract the document metadata (language, mimetype) and the\n",
92-
"underyling code editor (e.g. CodeMirror or Monaco) instances so that other parts\n",
92+
"underlying code editor (e.g. CodeMirror or Monaco) instances so that other parts\n",
9393
"of the LSP extension can interface with them without knowing about the\n",
9494
"implementation details of the DocumentWidget (or even about the existence of a\n",
9595
"Notebook construct!).\n",
@@ -108,8 +108,18 @@
108108
"source": [
109109
"### How to add a custom magic or foreign extractor?\n",
110110
"\n",
111-
"The ability to define custom magics or register foreign extractors is currently\n",
112-
"a work-in-progress."
111+
"It is now possible to register custom code replacements using\n",
112+
"`ILSPCodeOverridesManager` token and to register custom foreign code extractors\n",
113+
"using `ILSPCodeExtractorsManager` token, however this API is considered\n",
114+
"provisional and subject to change.\n",
115+
"\n",
116+
"#### Future plans for transclusions handling\n",
117+
"\n",
118+
"We will strive to make it possible for kernels to register their custom\n",
119+
"syntax/code transformations easily, but the frontend API will remain available\n",
120+
"for the end-users who write their custom syntax modifications with actionable\n",
121+
"side-effects (e.g. a custom IPython magic which copies a variable from the host\n",
122+
"document to the embedded document)."
113123
]
114124
},
115125
{
@@ -135,7 +145,7 @@
135145
"2. Prepare `CompletionKind` → `IconSvgString` mapping for the light (and\n",
136146
" optionally dark) theme, implementing the `ICompletionIconSet` interface. We\n",
137147
" have an additional `Kernel` completion kind that is used for completions\n",
138-
" provided by kernel that had no recognisable type provided.\n",
148+
" provided by kernel that had no recognizable type provided.\n",
139149
"\n",
140150
"3. Provide all other metadata required by the `ICompletionTheme` interface and\n",
141151
" register it on `ILSPCompletionThemeManager` instance using `register_theme()`\n",
@@ -144,7 +154,7 @@
144154
"4. Provide any additional CSS styling targeting the JupyterLab completer\n",
145155
" elements inside of `.lsp-completer-theme-{id}`, e.g.\n",
146156
" `.lsp-completer-theme-material .jp-Completer-icon svg` for the material\n",
147-
" theme. Rememmber to include the styles by importing the in one of the source\n",
157+
" theme. Remember to include the styles by importing the in one of the source\n",
148158
" files.\n",
149159
"\n",
150160
"For an example of a complete theme see\n",

packages/jupyterlab-lsp/src/adapters/adapter.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ILSPExtension } from '../index';
1717
import { IFeatureEditorIntegration, IFeature } from '../feature';
1818
import { EditorAdapter } from '../editor_integration/editor_adapter';
1919
import IEditor = CodeEditor.IEditor;
20+
import { LanguageIdentifier } from '../lsp';
2021

2122
export class StatusMessage {
2223
/**
@@ -126,6 +127,14 @@ export abstract class WidgetAdapter<T extends IDocumentWidget> {
126127
this.widget.disposed.connect(this.dispose, this);
127128
}
128129

130+
protected get foreign_code_extractors() {
131+
return this.extension.foreign_code_extractors;
132+
}
133+
134+
protected get code_overrides() {
135+
return this.extension.code_overrides;
136+
}
137+
129138
on_connection_closed(
130139
manager: DocumentConnectionManager,
131140
{ virtual_document }: IDocumentConnectionData
@@ -187,8 +196,9 @@ export abstract class WidgetAdapter<T extends IDocumentWidget> {
187196
return this.widget.id;
188197
}
189198

190-
get language(): string {
191-
// the values should follow https://microsoft.github.io/language-server-protocol/specification guidelines
199+
get language(): LanguageIdentifier {
200+
// the values should follow https://microsoft.github.io/language-server-protocol/specification guidelines,
201+
// see the table in https://microsoft.github.io/language-server-protocol/specification#textDocumentItem
192202
if (mime_type_language_map.hasOwnProperty(this.mime_type)) {
193203
return mime_type_language_map[this.mime_type] as string;
194204
} else {

packages/jupyterlab-lsp/src/adapters/notebook/notebook.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { WidgetAdapter } from '../adapter';
22
import { Notebook, NotebookPanel } from '@jupyterlab/notebook';
33
import { until_ready } from '../../utils';
4-
import { language_specific_overrides } from '../../magics/defaults';
5-
import { foreign_code_extractors } from '../../extractors/defaults';
64
import { Cell } from '@jupyterlab/cells';
75
import * as nbformat from '@jupyterlab/nbformat';
86
import ILanguageInfoMetadata = nbformat.ILanguageInfoMetadata;
@@ -165,8 +163,8 @@ export class NotebookAdapter extends WidgetAdapter<NotebookPanel> {
165163
return new VirtualDocument({
166164
language: this.language,
167165
path: this.document_path,
168-
overrides_registry: language_specific_overrides,
169-
foreign_code_extractors: foreign_code_extractors,
166+
overrides_registry: this.code_overrides,
167+
foreign_code_extractors: this.foreign_code_extractors,
170168
file_extension: this.language_file_extension,
171169
// notebooks are continuous, each cell is dependent on the previous one
172170
standalone: false,

packages/jupyterlab-lsp/src/editor_integration/feature.spec.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
} from './testutils';
1010
import * as lsProtocol from 'vscode-languageserver-protocol';
1111
import * as nbformat from '@jupyterlab/nbformat';
12-
import { language_specific_overrides } from '../magics/defaults';
13-
import { foreign_code_extractors } from '../extractors/defaults';
12+
import { overrides } from '../transclusions/ipython/overrides';
13+
import { foreign_code_extractors } from '../transclusions/ipython/extractors';
1414
import { NotebookModel } from '@jupyterlab/notebook';
1515
import { PageConfig } from '@jupyterlab/coreutils';
1616
import { CodeMirrorIntegration } from './codemirror';
@@ -183,7 +183,12 @@ describe('Feature', () => {
183183

184184
beforeEach(() => {
185185
environment = new NotebookFeatureTestEnvironment({
186-
overrides_registry: language_specific_overrides,
186+
overrides_registry: {
187+
python: {
188+
cell: overrides.filter(override => override.scope == 'cell'),
189+
line: overrides.filter(override => override.scope == 'line')
190+
}
191+
},
187192
foreign_code_extractors: foreign_code_extractors
188193
});
189194

packages/jupyterlab-lsp/src/editor_integration/testutils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ import { ServiceManager } from '@jupyterlab/services';
4444
import { FeatureManager, ILSPExtension } from '../index';
4545
import { JupyterFrontEnd } from '@jupyterlab/application';
4646
import { IFeatureSettings } from '../feature';
47+
import { IForeignCodeExtractorsRegistry } from '../extractors/types';
48+
import { ICodeOverridesRegistry } from '../overrides/tokens';
4749
import { Signal } from '@lumino/signaling';
4850

4951
export interface ITestEnvironment {
@@ -94,6 +96,8 @@ export class MockExtension implements ILSPExtension {
9496
language_server_manager: LanguageServerManager;
9597
feature_manager: ILSPFeatureManager;
9698
editor_type_manager: ILSPVirtualEditorManager;
99+
foreign_code_extractors: IForeignCodeExtractorsRegistry;
100+
code_overrides: ICodeOverridesRegistry;
97101

98102
constructor() {
99103
this.app = null;
@@ -108,6 +112,8 @@ export class MockExtension implements ILSPExtension {
108112
name: 'CodeMirrorEditor',
109113
supports: CodeMirrorEditor
110114
});
115+
this.foreign_code_extractors = {};
116+
this.code_overrides = {};
111117
}
112118
}
113119

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2+
import { ILSPCodeExtractorsManager } from '../tokens';
3+
import { IForeignCodeExtractor, IForeignCodeExtractorsRegistry } from './types';
4+
5+
export class CodeExtractorsManager implements ILSPCodeExtractorsManager {
6+
registry: IForeignCodeExtractorsRegistry;
7+
8+
constructor() {
9+
this.registry = {};
10+
}
11+
12+
/**
13+
* Register an extractor to extract foreign code from host documents of specified language.
14+
*/
15+
register(extractor: IForeignCodeExtractor, host_language: string): void {
16+
if (!this.registry.hasOwnProperty(host_language)) {
17+
this.registry[host_language] = [];
18+
}
19+
this.registry[host_language].push(extractor);
20+
}
21+
}
22+
23+
export const CODE_EXTRACTORS_MANAGER: JupyterFrontEndPlugin<ILSPCodeExtractorsManager> = {
24+
id: ILSPCodeExtractorsManager.name,
25+
requires: [],
26+
activate: app => {
27+
return new CodeExtractorsManager();
28+
},
29+
provides: ILSPCodeExtractorsManager,
30+
autoStart: true
31+
};

packages/jupyterlab-lsp/src/extractors/regexp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { IExtractedCode, IForeignCodeExtractor } from './types';
22
import { position_at_offset } from '../positioning';
3-
import { replacer } from '../magics/overrides';
3+
import { replacer } from '../overrides/tokens';
44
import { CodeEditor } from '@jupyterlab/codeeditor';
55

66
export class RegExpForeignCodeExtractor implements IForeignCodeExtractor {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { CodeEditor } from '@jupyterlab/codeeditor';
2+
import { IVirtualDocumentBlock, VirtualDocument } from '../virtual/document';
3+
import { expect } from 'chai';
4+
5+
export function extract_code(document: VirtualDocument, code: string) {
6+
return document.extract_foreign_code(
7+
{ value: code, ce_editor: null },
8+
{
9+
line: 0,
10+
column: 0
11+
}
12+
);
13+
}
14+
15+
export function get_the_only_pair(
16+
foreign_document_map: Map<CodeEditor.IRange, IVirtualDocumentBlock>
17+
) {
18+
expect(foreign_document_map.size).to.equal(1);
19+
20+
let range = foreign_document_map.keys().next().value;
21+
let { virtual_document } = foreign_document_map.get(range);
22+
23+
return { range, virtual_document };
24+
}
25+
26+
export function get_the_only_virtual(
27+
foreign_document_map: Map<CodeEditor.IRange, IVirtualDocumentBlock>
28+
) {
29+
let { virtual_document } = get_the_only_pair(foreign_document_map);
30+
return virtual_document;
31+
}
32+
33+
export function wrap_in_python_lines(line: string) {
34+
return 'print("some code before")\n' + line + '\n' + 'print("and after")\n';
35+
}

packages/jupyterlab-lsp/src/extractors/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CodeEditor } from '@jupyterlab/codeeditor';
2+
import { LanguageIdentifier } from '../lsp';
23

34
export interface IExtractedCode {
45
/**
@@ -42,7 +43,7 @@ export interface IForeignCodeExtractor {
4243
/**
4344
* The foreign language.
4445
*/
45-
language: string;
46+
language: LanguageIdentifier;
4647

4748
/**
4849
* Split the code into the host and foreign code (if any foreign code was detected)

packages/jupyterlab-lsp/src/features/diagnostics/diagnostics.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import {
1111
} from '../../editor_integration/testutils';
1212
import { CodeMirrorEditor } from '@jupyterlab/codemirror';
1313
import { is_equal } from '../../positioning';
14-
import { language_specific_overrides } from '../../magics/defaults';
15-
import { foreign_code_extractors } from '../../extractors/defaults';
14+
import { foreign_code_extractors } from '../../transclusions/ipython/extractors';
1615
import * as lsProtocol from 'vscode-languageserver-protocol';
1716
import { CodeDiagnostics as LSPDiagnosticsSettings } from '../../_diagnostics';
1817
import { message_without_code } from './listing';
@@ -141,7 +140,7 @@ describe('Diagnostics', () => {
141140

142141
beforeEach(() => {
143142
env = new NotebookFeatureTestEnvironment({
144-
overrides_registry: language_specific_overrides,
143+
overrides_registry: {},
145144
foreign_code_extractors
146145
});
147146
feature = env.init_integration({

0 commit comments

Comments
 (0)