Skip to content

Commit b9b97b1

Browse files
authored
Merge pull request #319 from krassowski/syntax_higlighting
Syntax highlighting
2 parents b8f0cc6 + f2ee5eb commit b9b97b1

File tree

14 files changed

+365
-63
lines changed

14 files changed

+365
-63
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
- added icons for all the features and their commands ([#318])
2828
- refactored the codebase with a new architecture which allows dynamic feature, document widget adapter, and code editor registration ([#318])
2929
- the document in the connections list in the statusbar popover are now represented by easy-to-understand DocumentLocator (breadcrumbs) widget rather than an internal id ([bacc006])
30+
- syntax highlighting mode is adjusted to the language with the majority of the code in an editor ([#319])
3031

3132
- bug fixes
3233

@@ -41,6 +42,7 @@
4142
[#301]: https://github.com/krassowski/jupyterlab-lsp/pull/301
4243
[#315]: https://github.com/krassowski/jupyterlab-lsp/pull/315
4344
[#318]: https://github.com/krassowski/jupyterlab-lsp/pull/318
45+
[#319]: https://github.com/krassowski/jupyterlab-lsp/pull/319
4446
[#322]: https://github.com/krassowski/jupyterlab-lsp/pull/322
4547
[00448d0]: https://github.com/krassowski/jupyterlab-lsp/pull/318/commits/00448d0c55e7f9a1e7e0a5322f17610daac47dfe
4648
[bacc006]: https://github.com/krassowski/jupyterlab-lsp/pull/318/commits/bacc0066da0727ff7397574914bf0401e4d8f7cb
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
*** Settings ***
2+
Suite Setup Setup Suite For Screenshots syntax_highlighting
3+
Test Setup Setup Highlighting Test
4+
Test Teardown Clean Up After Working With File Syntax highlighting.ipynb
5+
Force Tags feature:syntax_highlighting
6+
Resource ../Keywords.robot
7+
8+
*** Test Cases ***
9+
Syntax Highlighting Mode Stays Normal In Normal Cells
10+
${mode} = Get Mode Of A Cell 1
11+
should be equal ${mode['name']} ipython
12+
13+
Syntax Highlighting Mode Changes In Cells Dominated By Foreign Documents
14+
${mode} = Get Mode Of A Cell 2
15+
should be equal ${mode['name']} markdown
16+
${mode} = Get Mode Of A Cell 3
17+
should be equal ${mode['name']} xml
18+
${mode} = Get Mode Of A Cell 4
19+
should be equal ${mode['name']} javascript
20+
21+
Highlighing Mode Works For Multiple Documents
22+
${mode} = Get Mode Of A Cell 4
23+
should be equal ${mode['name']} javascript
24+
${mode} = Get Mode Of A Cell 6
25+
should be equal ${mode['name']} javascript
26+
27+
*** Keywords ***
28+
Get Mode Of A Cell
29+
[Arguments] ${cell_nr}
30+
Click Element css:.jp-Cell:nth-child(${cell_nr})
31+
Wait Until Page Contains Element css:.jp-Cell:nth-child(${cell_nr}) .CodeMirror-focused
32+
${mode} = Execute JavaScript return document.querySelector('.jp-Cell:nth-child(${cell_nr}) .CodeMirror').CodeMirror.getMode()
33+
[Return] ${mode}
34+
35+
Setup Highlighting Test
36+
Setup Notebook Python Syntax highlighting.ipynb
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"variable = 1\n",
10+
"\n",
11+
"def function():\n",
12+
" return\n",
13+
"\n",
14+
"# note print is highlighted in Python but function is not\n",
15+
"print(variable, function)"
16+
]
17+
},
18+
{
19+
"cell_type": "code",
20+
"execution_count": null,
21+
"metadata": {},
22+
"outputs": [],
23+
"source": [
24+
"%%markdown\n",
25+
"# header\n",
26+
"**bold**, *italic*\n",
27+
"\n",
28+
"### Heading with wrong level"
29+
]
30+
},
31+
{
32+
"cell_type": "code",
33+
"execution_count": null,
34+
"metadata": {},
35+
"outputs": [],
36+
"source": [
37+
"%%html\n",
38+
"<html style=\"color: green\">\n",
39+
" <!-- this is a comment -->\n",
40+
" <head>\n",
41+
" <title>HTML Example</title>\n",
42+
" </head>\n",
43+
" <body>\n",
44+
" The indentation tries to be <em>somewhat &quot;do what\n",
45+
" I mean&quot;</em>... but might not match your style.\n",
46+
" </body>\n",
47+
"</html>"
48+
]
49+
},
50+
{
51+
"cell_type": "code",
52+
"execution_count": null,
53+
"metadata": {},
54+
"outputs": [],
55+
"source": [
56+
"%%javascript\n",
57+
"// \"print\" is NOT highlighted in javascript, while \"function\" is\n",
58+
"function add_together(a, b) {\n",
59+
" return a + b\n",
60+
"}\n",
61+
"\n",
62+
"print('A')"
63+
]
64+
},
65+
{
66+
"cell_type": "markdown",
67+
"metadata": {},
68+
"source": [
69+
"It should work for the same language of virtual document with multiple occurrences:"
70+
]
71+
},
72+
{
73+
"cell_type": "code",
74+
"execution_count": null,
75+
"metadata": {},
76+
"outputs": [],
77+
"source": [
78+
"%%javascript \n",
79+
"function add_together(a, b) {\n",
80+
" return a + b\n",
81+
"}\n",
82+
"\n",
83+
"print('A')"
84+
]
85+
}
86+
],
87+
"metadata": {
88+
"kernelspec": {
89+
"display_name": "Python 3",
90+
"language": "python",
91+
"name": "python3"
92+
},
93+
"language_info": {
94+
"codemirror_mode": {
95+
"name": "ipython",
96+
"version": 3
97+
},
98+
"file_extension": ".py",
99+
"mimetype": "text/x-python",
100+
"name": "python",
101+
"nbconvert_exporter": "python",
102+
"pygments_lexer": "ipython3",
103+
"version": "3.7.5"
104+
}
105+
},
106+
"nbformat": 4,
107+
"nbformat_minor": 4
108+
}

packages/jupyterlab-lsp/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@
2929
},
3030
"scripts": {
3131
"build": "jlpm build:schema && tsc -b",
32-
"build:schema": "jlpm build:schema-backend && jlpm build:schema-completion && jlpm build:schema-hover && jlpm build:schema-diagnostics",
32+
"build:schema": "jlpm build:schema-backend && jlpm build:schema-completion && jlpm build:schema-hover && jlpm build:schema-diagnostics && jlpm build:schema-syntax_highlighting",
3333
"build:schema-backend": "json2ts ../../py_src/jupyter_lsp/schema/schema.json --unreachableDefinitions | prettier --stdin-filepath _schema.d.ts > src/_schema.d.ts",
3434
"build:schema-completion": "json2ts schema/completion.json | prettier --stdin-filepath _completion.d.ts > src/_completion.d.ts",
3535
"build:schema-hover": "json2ts schema/hover.json | prettier --stdin-filepath _hover.d.ts > src/_hover.d.ts",
3636
"build:schema-diagnostics": "json2ts schema/diagnostics.json | prettier --stdin-filepath _diagnostics.d.ts > src/_diagnostics.d.ts",
37+
"build:schema-syntax_highlighting": "json2ts schema/syntax_highlighting.json | prettier --stdin-filepath _syntax_highlighting.d.ts > src/_syntax_highlighting.d.ts",
3738
"bundle": "npm pack .",
3839
"clean": "rimraf lib",
3940
"lab:link": "jupyter labextension link . --no-build",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"jupyter.lab.setting-icon": "lsp:syntax-highlighting",
3+
"jupyter.lab.setting-icon-label": "Language integration",
4+
"title": "Code Syntax",
5+
"description": "LSP code syntax highlighting settings.",
6+
"type": "object",
7+
"properties": {
8+
"foreignCodeThreshold": {
9+
"title": "Threshold of foreign code coverage for changing the mode in an editor",
10+
"type": "number",
11+
"default": 0.5,
12+
"description": "If a code editor includes a code fragment in another language (for example a %%markdown magic in IPython) with appropriate foreign code extractor defined , and the extend of this code (coverage of the editor) passes the threshold, the syntax highlighting (i.e. the mode) will change to provide highlighting for the language of the foreign code."
13+
}
14+
},
15+
"jupyter.lab.shortcuts": []
16+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,10 @@ export abstract class CodeMirrorIntegration
363363
let raw_value = doc.getValue('\n');
364364
// extract foreign documents and substitute magics,
365365
// as it was done when the shadow virtual document was being created
366-
let { lines } = document.prepare_code_block(raw_value, editor);
366+
let { lines } = document.prepare_code_block({
367+
value: raw_value,
368+
ce_editor: editor
369+
});
367370
let old_value = lines.join('\n');
368371

369372
if (is_whole_document_edit) {

packages/jupyterlab-lsp/src/extractors/defaults.spec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ describe('Default extractors', () => {
1111
let document: VirtualDocument;
1212

1313
function extract(code: string) {
14-
return document.extract_foreign_code(code, null, {
15-
line: 0,
16-
column: 0
17-
});
14+
return document.extract_foreign_code(
15+
{ value: code, ce_editor: null },
16+
{
17+
line: 0,
18+
column: 0
19+
}
20+
);
1821
}
1922

2023
function get_the_only_pair(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface IExtractedCode {
2525
*
2626
* The typical examples are:
2727
* - (X=CSS< Y=HTML), or
28-
* - (X=JavaScrip, Y=HTML),
28+
* - (X=JavaScript, Y=HTML),
2929
*
3030
* while in the data analysis realm, examples include:
3131
* - (X=R, Y=IPython),
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { CodeMirrorIntegration } from '../editor_integration/codemirror';
2+
import {
3+
JupyterFrontEnd,
4+
JupyterFrontEndPlugin
5+
} from '@jupyterlab/application';
6+
import { ILSPFeatureManager, PLUGIN_ID } from '../tokens';
7+
import { LabIcon } from '@jupyterlab/ui-components';
8+
import syntaxSvg from '../../style/icons/syntax-highlight.svg';
9+
import {
10+
FeatureSettings,
11+
IEditorIntegrationOptions,
12+
IFeatureLabIntegration,
13+
IFeatureSettings
14+
} from '../feature';
15+
import { CodeMirrorEditor } from '@jupyterlab/codemirror';
16+
import CodeMirror from 'codemirror';
17+
import { IEditorMimeTypeService } from '@jupyterlab/codeeditor/lib/mimetype';
18+
import { IEditorServices } from '@jupyterlab/codeeditor/lib/tokens';
19+
import { CodeSyntax as LSPSyntaxHighlightingSettings } from '../_syntax_highlighting';
20+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
21+
22+
export const syntaxHighlightingIcon = new LabIcon({
23+
name: 'lsp:syntax-highlighting',
24+
svgstr: syntaxSvg
25+
});
26+
27+
const FEATURE_ID = PLUGIN_ID + ':syntax_highlighting';
28+
29+
export class CMSyntaxHighlighting extends CodeMirrorIntegration {
30+
lab_integration: SyntaxLabIntegration;
31+
settings: IFeatureSettings<LSPSyntaxHighlightingSettings>;
32+
33+
constructor(options: IEditorIntegrationOptions) {
34+
super(options);
35+
this.virtual_document.changed.connect(this.update_mode.bind(this), this);
36+
}
37+
38+
private get_mode(language: string) {
39+
let mimetype = this.lab_integration.mimeTypeService.getMimeTypeByLanguage({
40+
name: language
41+
});
42+
43+
if (!mimetype || mimetype == 'text/plain') {
44+
// if a mimetype cannot be found it will be 'text/plain', therefore do
45+
// not change mode to text/plain, as this could be a step backwards for
46+
// the user experience
47+
return;
48+
}
49+
50+
return CodeMirror.findModeByMIME(mimetype);
51+
}
52+
53+
update_mode() {
54+
let root = this.virtual_document;
55+
for (let map of root.foreign_document_maps) {
56+
for (let [range, block] of map.entries()) {
57+
let ce_editor = block.editor;
58+
let editor = (ce_editor as CodeMirrorEditor).editor;
59+
let lines = editor.getValue('\n');
60+
let total_area = lines.concat('').length;
61+
62+
let covered_area =
63+
ce_editor.getOffsetAt(range.end) - ce_editor.getOffsetAt(range.start);
64+
65+
let coverage = covered_area / total_area;
66+
67+
let language = block.virtual_document.language;
68+
69+
let mode = this.get_mode(language);
70+
71+
// if not highlighting mode available, skip this editor
72+
if (typeof mode === 'undefined') {
73+
continue;
74+
}
75+
76+
// change the mode if the majority of the code is the foreign code
77+
if (coverage > this.settings.composite.foreignCodeThreshold) {
78+
editor.setOption('mode', mode.mime);
79+
}
80+
}
81+
}
82+
}
83+
}
84+
85+
class SyntaxLabIntegration implements IFeatureLabIntegration {
86+
// TODO: we could accept custom mimetype mapping from settings
87+
settings: IFeatureSettings<LSPSyntaxHighlightingSettings>;
88+
89+
constructor(public mimeTypeService: IEditorMimeTypeService) {}
90+
}
91+
92+
export const SYNTAX_HIGHLIGHTING_PLUGIN: JupyterFrontEndPlugin<void> = {
93+
id: FEATURE_ID,
94+
requires: [ILSPFeatureManager, IEditorServices, ISettingRegistry],
95+
autoStart: true,
96+
activate: (
97+
app: JupyterFrontEnd,
98+
featureManager: ILSPFeatureManager,
99+
editorServices: IEditorServices,
100+
settingRegistry: ISettingRegistry
101+
) => {
102+
const settings = new FeatureSettings(settingRegistry, FEATURE_ID);
103+
104+
featureManager.register({
105+
feature: {
106+
editorIntegrationFactory: new Map([
107+
['CodeMirrorEditor', CMSyntaxHighlighting]
108+
]),
109+
commands: [],
110+
id: FEATURE_ID,
111+
name: 'Syntax highlighting',
112+
labIntegration: new SyntaxLabIntegration(
113+
editorServices.mimeTypeService
114+
),
115+
settings: settings
116+
}
117+
});
118+
}
119+
};

packages/jupyterlab-lsp/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { LabIcon } from '@jupyterlab/ui-components';
3535
import codeCheckSvg from '../style/icons/code-check.svg';
3636
import { DIAGNOSTICS_PLUGIN } from './features/diagnostics';
3737
import { COMPLETION_PLUGIN } from './features/completion';
38+
import { SYNTAX_HIGHLIGHTING_PLUGIN } from './features/syntax_highlighting';
3839
import { COMPLETION_THEME_MANAGER } from '@krassowski/completion-theme';
3940
import { plugin as THEME_VSCODE } from '@krassowski/theme-vscode';
4041
import { plugin as THEME_MATERIAL } from '@krassowski/theme-material';
@@ -217,7 +218,8 @@ const default_features: JupyterFrontEndPlugin<void>[] = [
217218
HOVER_PLUGIN,
218219
RENAME_PLUGIN,
219220
HIGHLIGHTS_PLUGIN,
220-
DIAGNOSTICS_PLUGIN
221+
DIAGNOSTICS_PLUGIN,
222+
SYNTAX_HIGHLIGHTING_PLUGIN
221223
];
222224

223225
const plugins: JupyterFrontEndPlugin<any>[] = [

0 commit comments

Comments
 (0)