Skip to content

Commit f7def18

Browse files
authored
Merge pull request #696 from Systems-Modeling/ST6RI-766
ST6RI-766 Update to JupyterLab version 4.x in the Jupyter deployment
2 parents 291b6ec + e4400de commit f7def18

15 files changed

Lines changed: 335 additions & 2257 deletions

File tree

org.omg.sysml.jupyter.installer/install.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ call java -version || goto :error
4141

4242
echo --- Step 3: Installing Jupyter SysML kernel and dependencies ---
4343
call jupyter kernelspec remove sysml -f >nul 2>&1
44-
call conda install "jupyter-sysml-kernel=%SYSML_VERSION%" python=3.* jupyterlab=3.* graphviz=2.* nodejs="<17" -c conda-forge -y || goto:error
44+
call conda install "jupyter-sysml-kernel=%SYSML_VERSION%" python=3.* jupyterlab=4.* graphviz=2.* nodejs -c conda-forge -y || goto:error
4545

4646
echo --- Step 4: Installing JupyterLab SysML extension ---
4747
call jupyter labextension uninstall @systems-modeling/jupyterlab-sysml

org.omg.sysml.jupyter.installer/install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ java -version
3434

3535
echo "--- Step 3: Installing Jupyter SysML kernel and dependencies ---"
3636
jupyter kernelspec remove sysml -f > /dev/null 2>&1 || true
37-
conda install "jupyter-sysml-kernel=$SYSML_VERSION" python=3.* jupyterlab=3.* graphviz=2.* nodejs="<17" -c conda-forge -y
37+
conda install "jupyter-sysml-kernel=$SYSML_VERSION" python=3.* jupyterlab=4.* graphviz=2.* nodejs -c conda-forge -y
3838

3939
echo "--- Step 4: Installing JupyterLab SysML extension ---"
4040
jupyter labextension uninstall @systems-modeling/jupyterlab-sysml > /dev/null 2>&1 || true

org.omg.sysml.jupyter.jupyterlab/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ build
55
data
66
log
77
scrap
8-
.gradle
8+
.gradle
9+
yarn.lock

org.omg.sysml.jupyter.jupyterlab/package.json

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,26 @@
55
"repository": "github:Systems-Modeling/SysML-v2-Pilot-Implementation",
66
"author": "SysML v2 Submission Team",
77
"license": "LGPL-3.0-or-later",
8+
"main": "build/plugin.js",
89
"keywords": [
910
"jupyter",
1011
"jupyterlab",
1112
"jupyterlab-extension"
1213
],
1314
"dependencies": {
14-
"@jupyterlab/application": "3.x"
15+
"@jupyterlab/application": "4.x"
1516
},
1617
"devDependencies": {
17-
"@types/codemirror": "^0.0.98",
18+
"@codemirror/legacy-modes": "^6.3.2",
19+
"@jupyterlab/codemirror": ">=4.0",
1820
"@types/json-schema": "*",
19-
"typescript": "<4.4.0"
20-
},
21-
"resolutions": {
22-
"@lumino/coreutils": "^1.11.0",
23-
"@lumino/widgets": "^1.37.2",
24-
"lib0": "0.2.108"
25-
},
26-
"peerDependencies": {
27-
"codemirror": "^5.58.1"
21+
"typescript": "~5.8.3"
2822
},
2923
"files": [
3024
"build/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}"
3125
],
3226
"jupyterlab": {
33-
"extension": "build/plugin.js"
27+
"extension": true
3428
},
3529
"scripts": {
3630
"build": "tsc",
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* SysML 2 Pilot Implementation
3+
* Copyright (c) 2020-2025 Mgnite Inc.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Lesser General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Lesser General Public License for more details.
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*
17+
* @license LGPL-3.0-or-later <http://spdx.org/licenses/LGPL-3.0-or-later>
18+
*/
19+
20+
import { IEditorExtensionFactory } from "@jupyterlab/codemirror";
21+
import { EditorState, Extension } from '@codemirror/state';
22+
import { foldService, syntaxTree, language } from '@codemirror/language';
23+
import { SyntaxNode } from '@lezer/common';
24+
25+
function isInStringCommentOrVariable(state: EditorState, pos: number): boolean {
26+
const tree = syntaxTree(state);
27+
let node: SyntaxNode | null = tree.resolveInner(pos, 1);
28+
29+
while (node) {
30+
const nodeType = node.type.name;
31+
// Check if we're in a string, comment, or other non-code context
32+
// Note that 'variableName' correesponds to a quoted name
33+
if (nodeType === 'String' ||
34+
nodeType === 'Comment' ||
35+
nodeType === 'BlockComment' ||
36+
nodeType === 'LineComment' ||
37+
nodeType === 'variableName' ||
38+
nodeType.toLowerCase().includes('string') ||
39+
nodeType.toLowerCase().includes('comment')) {
40+
return true;
41+
}
42+
node = node.parent;
43+
}
44+
return false;
45+
}
46+
47+
function findMatchingCloseBrace(state: EditorState, openPos: number): number | null {
48+
const docLength = state.doc.length;
49+
let nest = 1;
50+
let pos = openPos + 1;
51+
52+
while (pos < docLength && nest > 0) {
53+
const char = state.sliceDoc(pos, pos + 1);
54+
// Skip if we're in a string, comment, or variable
55+
if (!isInStringCommentOrVariable(state, pos)) {
56+
if (char === '{') {
57+
nest++;
58+
} else if (char === '}') {
59+
nest--;
60+
if (nest === 0) {
61+
return pos;
62+
}
63+
}
64+
}
65+
pos++;
66+
}
67+
68+
return null;
69+
}
70+
71+
function computeFoldRange(state: EditorState, lineStart: number, lineEnd: number) {
72+
// Check the language first
73+
const lang = state.facet(language);
74+
if (!lang || lang.name !== 'sysml') return null;
75+
76+
const lineText = state.sliceDoc(lineStart, lineEnd);
77+
78+
for (let i = 0; i < lineText.length; i++) {
79+
const char = lineText[i];
80+
if (char === '{') {
81+
const absolutePos = lineStart + i;
82+
83+
// Check if this brace is in a string, comment, or variable
84+
if (isInStringCommentOrVariable(state, absolutePos)) {
85+
continue;
86+
}
87+
88+
// Find the matching closing brace
89+
const closePos = findMatchingCloseBrace(state, absolutePos);
90+
if (closePos === null) {
91+
return null;
92+
}
93+
94+
// Check if the fold spans multiple lines
95+
const openLine = state.doc.lineAt(absolutePos);
96+
const closeLine = state.doc.lineAt(closePos);
97+
98+
if (openLine.number === closeLine.number) {
99+
/* Do not fold the same line */
100+
return null;
101+
}
102+
103+
return { from: absolutePos, to: closePos };
104+
}
105+
}
106+
107+
return null;
108+
}
109+
110+
export function sysmlFoldServiceSelection(options: IEditorExtensionFactory.IOptions): Extension {
111+
const mimeType = options.model.mimeType;
112+
if (mimeType === 'text/x-sysml'
113+
// In the newly created notebook, the first cell is initialized as 'text/plain'
114+
// We check the language in the foldService as well to avoid misapplication.
115+
|| mimeType === 'text/plain') {
116+
return [ foldService.of(computeFoldRange) ];
117+
} else {
118+
return [];
119+
}
120+
}

org.omg.sysml.jupyter.jupyterlab/src/main/mode.ts

Lines changed: 60 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -23,90 +23,68 @@
2323
* $GIT_REPO_DIR/tool-support/syntax-highlighting/jupyter/mode_template.ts
2424
*/
2525

26-
// tslint:disable-next-line
27-
import 'codemirror/addon/mode/simple';
28-
29-
import * as CodeMirror from 'codemirror';
30-
31-
const SI_MODE = 'sysml';
32-
const P_MIME = 'text/x-sysml'
26+
import {StringStream} from "@codemirror/language"
27+
import { clike } from '@codemirror/legacy-modes/mode/clike';
3328

3429
const f_wordify = (h: any, s: string) => ({...h, [s]: true});
35-
36-
export function defineSysMLv2Mode(): void {
37-
CodeMirror.defineMode(SI_MODE, (gc_mode, gc_parser) => {
38-
return CodeMirror.getMode(gc_mode, {
39-
name: 'clike',
40-
keywords: [
41-
"about", "abstract", "accept", "action", "actor", "after", "alias", "all", "allocate", "allocation",
42-
"analysis", "and", "as", "assert", "assign", "assume", "at", "attribute", "bind", "binding", "by",
43-
"calc", "case", "comment", "concern", "connect", "connection", "constant", "constraint", "crosses",
44-
"decide", "def", "default", "defined", "dependency", "derived", "do", "doc", "else", "end", "entry",
45-
"enum", "event", "exhibit", "exit", "expose", "filter", "first", "flow", "for", "fork", "frame", "from",
46-
"hastype", "if", "implies", "import", "in", "include", "individual", "inout", "interface", "istype",
47-
"item", "join", "language", "library", "locale", "loop", "merge", "message", "meta", "metadata", "new",
48-
"nonunique", "not", "objective", "occurrence", "of", "or", "ordered", "out", "package", "parallel",
49-
"part", "perform", "port", "private", "protected", "public", "redefines", "ref", "references", "render",
50-
"rendering", "rep", "require", "requirement", "return", "satisfy", "send", "snapshot", "specializes",
51-
"stakeholder", "standard", "state", "subject", "subsets", "succession", "terminate", "then",
52-
"timeslice", "to", "transition", "until", "use", "variant", "variation", "verification", "verify",
53-
"via", "view", "viewpoint", "when", "while", "xor"
54-
].reduce(f_wordify, {}),
55-
defKeywords: [
56-
"action", "allocation", "analysis", "attribute", "binding", "calc", "case", "comment", "concern",
57-
"connection", "constraint", "def", "doc", "enum", "flow", "interface", "item", "metadata", "objective",
58-
"occurrence", "package", "part", "port", "ref", "rendering", "rep", "requirement", "snapshot", "state",
59-
"subject", "succession", "timeslice", "transition", "verification", "view", "viewpoint"
60-
].reduce(f_wordify, {}),
61-
typeFirstDefinitions: true,
62-
atoms: ['true', 'false', 'null'].reduce(f_wordify),
63-
number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
64-
modeProps: {
65-
fold: ['brace'],
30+
export const sysmlparser = clike({
31+
name: 'sysml',
32+
keywords: [
33+
"about", "abstract", "accept", "action", "actor", "after", "alias", "all", "allocate", "allocation",
34+
"analysis", "and", "as", "assert", "assign", "assume", "at", "attribute", "bind", "binding", "by",
35+
"calc", "case", "comment", "concern", "connect", "connection", "constant", "constraint", "crosses",
36+
"decide", "def", "default", "defined", "dependency", "derived", "do", "doc", "else", "end", "entry",
37+
"enum", "event", "exhibit", "exit", "expose", "filter", "first", "flow", "for", "fork", "frame", "from",
38+
"hastype", "if", "implies", "import", "in", "include", "individual", "inout", "interface", "istype",
39+
"item", "join", "language", "library", "locale", "loop", "merge", "message", "meta", "metadata", "new",
40+
"nonunique", "not", "objective", "occurrence", "of", "or", "ordered", "out", "package", "parallel",
41+
"part", "perform", "port", "private", "protected", "public", "redefines", "ref", "references", "render",
42+
"rendering", "rep", "require", "requirement", "return", "satisfy", "send", "snapshot", "specializes",
43+
"stakeholder", "standard", "state", "subject", "subsets", "succession", "terminate", "then",
44+
"timeslice", "to", "transition", "until", "use", "variant", "variation", "verification", "verify",
45+
"via", "view", "viewpoint", "when", "while", "xor"
46+
].reduce(f_wordify, {}),
47+
types: [
48+
"action", "allocation", "analysis", "attribute", "binding", "calc", "case", "comment", "concern",
49+
"connection", "constraint", "def", "doc", "enum", "flow", "interface", "item", "metadata", "objective",
50+
"occurrence", "package", "part", "port", "ref", "rendering", "rep", "requirement", "snapshot", "state",
51+
"subject", "succession", "timeslice", "transition", "verification", "view", "viewpoint"
52+
].reduce(f_wordify, {}),
53+
atoms: ['true', 'false', 'null'].reduce(f_wordify, {}),
54+
number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
55+
hooks: {
56+
"'": function(stream: StringStream) {
57+
let b_escaped = false;
58+
let s_next;
59+
while(s_next = stream.next()) {
60+
if(s_next === "'" && !b_escaped) break;
61+
b_escaped = !b_escaped && s_next === '\\';
62+
}
63+
return 'variable';
6664
},
67-
hooks: {
68-
"'": function(stream: CodeMirror.StringStream) {
69-
let b_escaped = false;
70-
let s_next;
71-
while(s_next = stream.next()) {
72-
if(s_next === "'" && !b_escaped) break;
73-
b_escaped = !b_escaped && s_next === '\\';
65+
'/': function(stream: StringStream) {
66+
if(stream.match('/*', false)) stream.next();
67+
return false;
68+
},
69+
"#": function(stream: StringStream) {
70+
let b_first = true;
71+
do {
72+
if (stream.match("'", true)) {
73+
let b_escaped = false;
74+
let s_next;
75+
while(s_next = stream.next()) {
76+
if(s_next === "'" && !b_escaped) break;
77+
b_escaped = !b_escaped && s_next === '\\';
78+
}
79+
} else if (stream.match(/\w/, true)) {
80+
stream.eatWhile(/\w/);
81+
} else if (b_first) {
82+
return 'operator';
7483
}
75-
return 'variable';
76-
},
77-
'/': function(stream: CodeMirror.StringStream) {
78-
if(stream.match('/*', false)) stream.next();
79-
return false;
80-
},
81-
"#": function(stream: CodeMirror.StringStream) {
82-
let b_first = true;
83-
do {
84-
if (stream.match("'", true)) {
85-
let b_escaped = false;
86-
let s_next;
87-
while(s_next = stream.next()) {
88-
if(s_next === "'" && !b_escaped) break;
89-
b_escaped = !b_escaped && s_next === '\\';
90-
}
91-
} else if (stream.match(/\w/, true)) {
92-
stream.eatWhile(/\w/);
93-
} else if (b_first) {
94-
return 'operator';
95-
}
96-
b_first = false;
97-
} while (stream.match('::', true))
98-
return 'keyword';
99-
},
84+
b_first = false;
85+
} while (stream.match('::', true))
86+
return 'keyword';
10087
},
101-
});
102-
});
103-
104-
CodeMirror.defineMIME(P_MIME, SI_MODE);
105-
106-
(CodeMirror as any).modeInfo.push({
107-
ext: ['sysml'],
108-
mime: P_MIME,
109-
mode: SI_MODE,
110-
name: 'sysml',
111-
});
112-
}
88+
},
89+
}
90+
);

0 commit comments

Comments
 (0)