diff --git a/.vscode/settings.json b/.vscode/settings.json index e1c7ca9c0..be54e51e9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "editor.tabSize": 2, "editor.insertSpaces": true, "editor.formatOnSave": true, + "git.alwaysSignOff": true, "typescript.tsc.autoDetect": "off", "typescript.preferences.quoteStyle": "single" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 96aaddbc9..f55ed822b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Add the ability to sort columns in data viewer and view column properties ([#1622](https://github.com/sassoftware/vscode-sas-extension/pull/1622)) - Add code comment collapsing ([#1638](https://github.com/sassoftware/vscode-sas-extension/pull/1638)) - Add ability to view dataset properties ([#1631](https://github.com/sassoftware/vscode-sas-extension/pull/1631)) +- Add R language support for PROC RLANG (syntax highlighting, notebook cells, code formatting preservation) ### Fixed diff --git a/README.md b/README.md index 122544562..d514502b9 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,20 @@ The SAS extension includes many [features](https://sassoftware.github.io/vscode- - SAS syntax highlighting and help, code completion, and code snippets - Navigate SAS Content and libraries, including table viewer -- Create notebooks for SAS, SQL, Python and other languages +- Create notebooks for SAS, SQL, Python, R, and other languages +## Language Support + +The SAS extension provides enhanced language support for embedded languages in SAS notebooks and code files: + +- **Python**: Full IntelliSense support (code completion, hover, signature help) is included via Pyright +- **R**: + - **In VS Code Desktop**: Basic IntelliSense support (code completion, hover) for common R functions - no installation required + - **In Browser (VS Code for Web)**: Basic IntelliSense support provided automatically via WebR (no installation required) +- **SQL** and **Lua**: Syntax highlighting and execution support + ## Support ### SAS Communities diff --git a/client/package-lock.json b/client/package-lock.json index 8dc506818..60a83aeb9 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -47,6 +47,7 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -865,6 +866,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -874,6 +876,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, diff --git a/client/src/components/ContentNavigator/convert.ts b/client/src/components/ContentNavigator/convert.ts index 4a8783e2a..80a03568e 100644 --- a/client/src/components/ContentNavigator/convert.ts +++ b/client/src/components/ContentNavigator/convert.ts @@ -20,12 +20,14 @@ const stepRef: Record = { sas: "a7190700-f59c-4a94-afe2-214ce639fcde", sql: "a7190700-f59c-4a94-afe2-214ce639fcde", python: "ab59f8c4-af9a-4608-a5d5-a8365357bb99", + rlang: "ab59f8c4-af9a-4608-a5d5-a8365357bb99", }; const stepTitle: Record = { sas: l10n.t("SAS Program"), sql: l10n.t("SQL Program"), python: l10n.t("Python Program"), + rlang: l10n.t("R Program"), }; const NODE_SPACING = 150; @@ -305,7 +307,7 @@ function generateCodeListFromSASNotebook(content: string): Entry[] { let code = cell.value; if (code !== "") { const language = cell.language; - if (["python", "sas", "sql"].includes(language)) { + if (["python", "sas", "sql", "rlang"].includes(language)) { if (language === "sql") { code = `PROC SQL; ${code}; diff --git a/client/src/components/notebook/Controller.ts b/client/src/components/notebook/Controller.ts index c23bab5b7..26a161609 100644 --- a/client/src/components/notebook/Controller.ts +++ b/client/src/components/notebook/Controller.ts @@ -11,7 +11,7 @@ export class NotebookController { readonly controllerId = "sas-notebook-controller-id"; readonly notebookType = "sas-notebook"; readonly label = "SAS Notebook"; - readonly supportedLanguages = ["sas", "sql", "python"]; + readonly supportedLanguages = ["sas", "sql", "python", "r"]; private readonly _controller: vscode.NotebookController; private _executionOrder = 0; diff --git a/client/src/components/notebook/exporters/toHTML.ts b/client/src/components/notebook/exporters/toHTML.ts index c8c910eae..917b85184 100644 --- a/client/src/components/notebook/exporters/toHTML.ts +++ b/client/src/components/notebook/exporters/toHTML.ts @@ -17,6 +17,7 @@ import { import { readFileSync } from "fs"; import hljs from "highlight.js/lib/core"; import python from "highlight.js/lib/languages/python"; +import r from "highlight.js/lib/languages/r"; import sql from "highlight.js/lib/languages/sql"; import { marked } from "marked"; import path from "path"; @@ -27,6 +28,7 @@ import { includeLogInNotebookExport } from "../../utils/settings"; const templatesDir = path.resolve(__dirname, "../notebook/exporters/templates"); hljs.registerLanguage("python", python); +hljs.registerLanguage("r", r); hljs.registerLanguage("sql", sql); export const exportToHTML = async ( diff --git a/client/src/components/notebook/exporters/toSAS.ts b/client/src/components/notebook/exporters/toSAS.ts index 9bd136054..d90674bf1 100644 --- a/client/src/components/notebook/exporters/toSAS.ts +++ b/client/src/components/notebook/exporters/toSAS.ts @@ -15,6 +15,8 @@ const exportCell = (cell: NotebookCell) => { return text; case "python": return wrapPython(text); + case "r": + return wrapRlang(text); case "sql": return wrapSQL(text); case "markdown": @@ -36,3 +38,9 @@ submit; ${code} endsubmit; run;`; + +const wrapRlang = (code: string) => `proc rlang; +submit; +${code} +endsubmit; +run;`; diff --git a/client/src/components/utils/SASCodeDocument.ts b/client/src/components/utils/SASCodeDocument.ts index 4ab4b054d..c15cedf23 100644 --- a/client/src/components/utils/SASCodeDocument.ts +++ b/client/src/components/utils/SASCodeDocument.ts @@ -180,6 +180,14 @@ endsubmit; run;`; } + private wrapRlang(code: string) { + return `proc rlang; +submit; +${code} +endsubmit; +run;`; + } + private insertLogStartIndicator(code: string): string { // add a comment line at the top of code, // this comment line will be used as indicator to the beginning of log related with this code @@ -198,6 +206,13 @@ ${code}`; wrapped = this.wrapPython(wrapped); } + if ( + this.parameters.languageId === "r" || + this.parameters.languageId === "rlang" + ) { + wrapped = this.wrapRlang(wrapped); + } + wrapped = this.wrapCodeWithSASProgramFileName(wrapped); wrapped = this.wrapCodeWithPreambleAndPostamble(wrapped); diff --git a/client/test/components/util/SASCodeDocument.test.ts b/client/test/components/util/SASCodeDocument.test.ts index e4cef507e..dbc4640c8 100644 --- a/client/test/components/util/SASCodeDocument.test.ts +++ b/client/test/components/util/SASCodeDocument.test.ts @@ -38,6 +38,40 @@ run; assert.equal(sasCodeDoc.getWrappedCode(), expected); }); + it("wrap rlang code", () => { + const parameters: SASCodeDocumentParameters = { + languageId: "r", + code: `for (x in 1:6) { + print(x) +} +print("test")`, + selectedCode: "", + htmlStyle: "Illuminate", + outputHtml: true, + uuid: "519058ad-d33b-4b5c-9d23-4cc8d6ffb163", + checkKeyword: async () => false, + }; + + const sasCodeDoc = new SASCodeDocument(parameters); + + const expected = `/** LOG_START_INDICATOR **/ +title;footnote;ods _all_ close; +ods graphics on; +ods html5(id=vscode) style=Illuminate options(bitmap_mode='inline' svg_mode='inline') body="519058ad-d33b-4b5c-9d23-4cc8d6ffb163.htm"; +proc rlang; +submit; +for (x in 1:6) { + print(x) +} +print("test") +endsubmit; +run; +;*';*";*/;run;quit;ods html5(id=vscode) close; +`; + + assert.equal(sasCodeDoc.getWrappedCode(), expected); + }); + it("wrap sql code", () => { const parameters: SASCodeDocumentParameters = { languageId: "sql", diff --git a/client/testFixture/formatter/expected.sas b/client/testFixture/formatter/expected.sas index 7cb36d0c7..813c09385 100644 --- a/client/testFixture/formatter/expected.sas +++ b/client/testFixture/formatter/expected.sas @@ -46,6 +46,16 @@ def my_function(): endsubmit; run; +proc rlang; +submit; +# Reference to variable defined in previous PROC RLANG call +print(paste("x =", x)) +my_function <- function() { + print("Inside the proc step") +} +endsubmit; +run; + proc lua; submit; local dsid = sas.open("sashelp.company") -- open for input @@ -131,6 +141,17 @@ print('first statement after for loop') endinteractive; run; +proc rlang; +submit; +fruits <- c("apple", "banana", "cherry") +for (x in fruits) { + print(x) +} + +print('first statement after for loop') +endsubmit; +run; + proc lua; submit; diff --git a/client/testFixture/formatter/unformatted.sas b/client/testFixture/formatter/unformatted.sas index 923c7adaf..739699876 100644 --- a/client/testFixture/formatter/unformatted.sas +++ b/client/testFixture/formatter/unformatted.sas @@ -40,6 +40,15 @@ def my_function(): print("Inside the proc step") endsubmit; run; +proc rlang; +submit; +# Reference to variable defined in previous PROC RLANG call +print(paste("x =", x)) +my_function <- function() { + print("Inside the proc step") +} +endsubmit; +run; proc lua; submit; local dsid = sas.open("sashelp.company") -- open for input @@ -126,6 +135,17 @@ print('first statement after for loop') endinteractive; run; +proc rlang; +submit; +fruits <- c("apple", "banana", "cherry") +for (x in fruits) { + print(x) +} + +print('first statement after for loop') +endsubmit; +run; + proc lua; submit; diff --git a/client/testFixture/sasnb_export.sas b/client/testFixture/sasnb_export.sas index 088afec00..6b436bff7 100644 --- a/client/testFixture/sasnb_export.sas +++ b/client/testFixture/sasnb_export.sas @@ -19,6 +19,23 @@ print("Result: ", a*10 + b) endsubmit; run; +/* +## R Code + +This is some R code +*/ + +/* +This is a separate note in **Markdown** format. +*/ + +proc rlang; +submit; +die <- 1:6 +paste("Die Maths: ", die[3]*4 + die[6]) +endsubmit; +run; + /* ## SAS Code */ diff --git a/client/testFixture/sasnb_export.sasnb b/client/testFixture/sasnb_export.sasnb index 7ed0c4ad8..6f1183941 100644 --- a/client/testFixture/sasnb_export.sasnb +++ b/client/testFixture/sasnb_export.sasnb @@ -1 +1 @@ -[{"kind":1,"language":"markdown","value":"# Notebook to SAS Test","outputs":[]},{"kind":1,"language":"markdown","value":"## Python Code\n\nThis is some Python code","outputs":[]},{"kind":1,"language":"markdown","value":"This is a separate note in **Markdown** format.","outputs":[]},{"kind":2,"language":"python","value":"a, b = 4, 2\nprint(\"Result: \", a*10 + b)","outputs":[{"items":[{"data":"[\n\t{\n\t\t\"line\": \"44 ods html5;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Writing HTML5 Body file: sashtml4.htm\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"45 proc python;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"46 submit\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Resuming Python state from previous PROC PYTHON invocation.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"46 ! ;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"47 a, b = 4, 2\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"48 print(\\\"Result: \\\", a*10 + b)\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"49 endsubmit;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"50 run;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \">>>\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"Result: 42\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \">>> \",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: PROCEDURE PYTHON used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 0.00 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.00 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"51 ;run;quit;ods html5 close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t}\n]","mime":"application/vnd.sas.compute.log.lines"}]}]},{"kind":1,"language":"markdown","value":"## SAS Code","outputs":[]},{"kind":2,"language":"sas","value":"data work.prdsale;\n\tset sashelp.PRDSALE;\nrun;\n\nproc means data=work.prdsale;\nrun;","outputs":[{"items":[{"data":"\n\n\n\n\nSAS Output\n\n\n\n
\n
\n

The SAS System

\n
\n
\n

The MEANS Procedure

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VariableLabelNMeanStd DevMinimumMaximum
\n
\n
ACTUAL
\n
PREDICT
\n
QUARTER
\n
YEAR
\n
MONTH
\n
\n
\n
\n
Actual Sales
\n
Predicted Sales
\n
Quarter
\n
Year
\n
Month
\n
\n
\n
\n
1440
\n
1440
\n
1440
\n
1440
\n
1440
\n
\n
\n
\n
507.1784722
\n
490.4826389
\n
2.5000000
\n
1993.50
\n
12403.00
\n
\n
\n
\n
287.0313065
\n
285.7667904
\n
1.1184224
\n
0.5001737
\n
210.6291578
\n
\n
\n
\n
3.0000000
\n
0
\n
1.0000000
\n
1993.00
\n
12054.00
\n
\n
\n
\n
1000.00
\n
1000.00
\n
4.0000000
\n
1994.00
\n
12753.00
\n
\n
\n
\n
\n\n\n","mime":"application/vnd.sas.ods.html5"},{"data":"[\n\t{\n\t\t\"line\": \"16 ods html5;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Writing HTML5 Body file: sashtml1.htm\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"17 data work.prdsale;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"18 \\tset sashelp.PRDSALE;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"19 run;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: There were 1440 observations read from the data set SASHELP.PRDSALE.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: The data set WORK.PRDSALE has 1440 observations and 10 variables.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: DATA statement used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 0.00 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.01 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"20 \",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"21 proc means data=work.prdsale;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"22 run;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: There were 1440 observations read from the data set WORK.PRDSALE.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: The PROCEDURE MEANS printed page 1.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: PROCEDURE MEANS used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 0.04 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.05 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"23 ;run;quit;ods html5 close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t}\n]","mime":"application/vnd.sas.compute.log.lines"}]}]},{"kind":1,"language":"markdown","value":"## SQL Code","outputs":[]},{"kind":2,"language":"sql","value":"CREATE TABLE WORK.QUERY_PRDSALE AS\n SELECT\n (t1.COUNTRY) LABEL='Country' FORMAT=$CHAR10.,\n (SUM(t1.ACTUAL)) FORMAT=DOLLAR12.2 LENGTH=8 AS SUM_ACTUAL\n FROM\n WORK.PRDSALE t1\n GROUP BY\n t1.COUNTRY","outputs":[{"items":[{"data":"[\n\t{\n\t\t\"line\": \"24 ods html5;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Writing HTML5 Body file: sashtml2.htm\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"25 proc sql;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"26 CREATE TABLE WORK.QUERY_PRDSALE AS\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"27 SELECT\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"28 (t1.COUNTRY) LABEL='Country' FORMAT=$CHAR10.,\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"29 (SUM(t1.ACTUAL)) FORMAT=DOLLAR12.2 LENGTH=8 AS SUM_ACTUAL\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"30 FROM\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"31 WORK.PRDSALE t1\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"32 GROUP BY\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"33 t1.COUNTRY\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"34 ;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Table WORK.QUERY_PRDSALE created, with 3 rows and 2 columns.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"34 ! quit;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: PROCEDURE SQL used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 0.00 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.00 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"6 The SAS System Monday, August 21, 2023 02:56:00 PM\",\n\t\t\"type\": \"title\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"title\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"35 ;run;quit;ods html5 close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t}\n]","mime":"application/vnd.sas.compute.log.lines"}]}]},{"kind":1,"language":"markdown","value":"A last comment in Markdown at the end of the document","outputs":[]},{"kind":1,"language":"markdown","value":"","outputs":[]}] \ No newline at end of file +[{"kind":1,"language":"markdown","value":"# Notebook to SAS Test","outputs":[]},{"kind":1,"language":"markdown","value":"## Python Code\n\nThis is some Python code","outputs":[]},{"kind":1,"language":"markdown","value":"This is a separate note in **Markdown** format.","outputs":[]},{"kind":2,"language":"python","value":"a, b = 4, 2\nprint(\"Result: \", a*10 + b)","outputs":[{"items":[{"data":"[\n\t{\n\t\t\"line\": \"14 /** LOG_START_INDICATOR **/\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"15 title;footnote;ods _all_ close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"16 ods graphics on;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"17 ods html5(id=vscode) style=Ignite options(bitmap_mode='inline' svg_mode='inline');\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Writing HTML5(VSCODE) Body file: sashtml1.htm\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"18 %let _SASPROGRAMFILE = %nrquote(%nrstr(/Users/elreid/personalGit/vscode-sas-extension/client/testFixture/sasnb_export.sasnb));\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"19 proc python;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"20 submit\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Python initialized.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"Python 3.11.10 (main, Nov 30 2025, 14:30:37) [GCC 11.5.0 20240719 (Red Hat 11.5.0-11)] on linux\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"Type \\\"help\\\", \\\"copyright\\\", \\\"credits\\\" or \\\"license\\\" for more information.\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \">>>\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \">>> \",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"20 ! ;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"21 a, b = 4, 2\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"22 print(\\\"Result: \\\", a*10 + b)\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"23 endsubmit;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"24 run;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \">>>\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"Result: 42\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \">>> \",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: PROCEDURE PYTHON used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 1.99 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.08 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"25 ;*';*\\\";*/;run;quit;ods html5(id=vscode) close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"26 \",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t}\n]","mime":"application/vnd.sas.compute.log.lines"}]}]},{"kind":1,"language":"markdown","value":"## R Code\n\nThis is some R code","outputs":[]},{"kind":1,"language":"markdown","value":"This is a separate note in **Markdown** format.","outputs":[]},{"kind":2,"language":"r","value":"die <- 1:6\npaste(\"Die Maths: \", die[3]*4 + die[6])","outputs":[{"items":[{"data":"[\n\t{\n\t\t\"line\": \"27 /** LOG_START_INDICATOR **/\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"28 title;footnote;ods _all_ close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"29 ods graphics on;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"30 ods html5(id=vscode) style=Ignite options(bitmap_mode='inline' svg_mode='inline');\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Writing HTML5(VSCODE) Body file: sashtml2.htm\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"31 %let _SASPROGRAMFILE = %nrquote(%nrstr(/Users/elreid/personalGit/vscode-sas-extension/client/testFixture/sasnb_export.sasnb));\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"32 proc rlang;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"33 submit\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Resuming Python state from previous PROC PYTHON invocation.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"33 ! ;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"34 die <- 1:6\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"35 paste(\\\"Die Maths: \\\", die[3]*4 + die[6])\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"36 endsubmit;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"37 run;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"[1] \\\"Die Maths: 18\\\"\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: PROCEDURE RLANG used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 0.00 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.00 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"38 ;*';*\\\";*/;run;quit;ods html5(id=vscode) close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"39 \",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t}\n]","mime":"application/vnd.sas.compute.log.lines"}]}]},{"kind":1,"language":"markdown","value":"## SAS Code","outputs":[]},{"kind":2,"language":"sas","value":"data work.prdsale;\n\tset sashelp.PRDSALE;\nrun;\n\nproc means data=work.prdsale;\nrun;","outputs":[{"items":[{"data":"\n\n\n\n\nSAS Output\n\n\n\n
\n
\n

The MEANS Procedure

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VariableLabelNMeanStd DevMinimumMaximum
\n
\n
ACTUAL
\n
PREDICT
\n
QUARTER
\n
YEAR
\n
MONTH
\n
\n
\n
\n
Actual Sales
\n
Predicted Sales
\n
Quarter
\n
Year
\n
Month
\n
\n
\n
\n
1440
\n
1440
\n
1440
\n
1440
\n
1440
\n
\n
\n
\n
507.1784722
\n
490.4826389
\n
2.5000000
\n
1993.50
\n
12403.00
\n
\n
\n
\n
287.0313065
\n
285.7667904
\n
1.1184224
\n
0.5001737
\n
210.6291578
\n
\n
\n
\n
3.0000000
\n
0
\n
1.0000000
\n
1993.00
\n
12054.00
\n
\n
\n
\n
1000.00
\n
1000.00
\n
4.0000000
\n
1994.00
\n
12753.00
\n
\n
\n
\n
\n\n\n","mime":"application/vnd.sas.ods.html5"},{"data":"[\n\t{\n\t\t\"line\": \"40 /** LOG_START_INDICATOR **/\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"3 The SAS System Friday, 5 December 2025 14:39:00\",\n\t\t\"type\": \"title\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"title\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"41 title;footnote;ods _all_ close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"42 ods graphics on;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"43 ods html5(id=vscode) style=Ignite options(bitmap_mode='inline' svg_mode='inline');\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Writing HTML5(VSCODE) Body file: sashtml3.htm\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"44 %let _SASPROGRAMFILE = %nrquote(%nrstr(/Users/elreid/personalGit/vscode-sas-extension/client/testFixture/sasnb_export.sasnb));\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"45 data work.prdsale;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"46 \\tset sashelp.PRDSALE;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"47 run;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: There were 1440 observations read from the data set SASHELP.PRDSALE.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: The data set WORK.PRDSALE has 1440 observations and 10 variables.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: DATA statement used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 0.00 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.02 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"48 \",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"49 proc means data=work.prdsale;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"50 run;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: There were 1440 observations read from the data set WORK.PRDSALE.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: PROCEDURE MEANS used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 0.05 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.05 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"51 ;*';*\\\";*/;run;quit;ods html5(id=vscode) close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"52 \",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t}\n]","mime":"application/vnd.sas.compute.log.lines"}]}]},{"kind":1,"language":"markdown","value":"## SQL Code","outputs":[]},{"kind":2,"language":"sql","value":"CREATE TABLE WORK.QUERY_PRDSALE AS\n SELECT\n (t1.COUNTRY) LABEL='Country' FORMAT=$CHAR10.,\n (SUM(t1.ACTUAL)) FORMAT=DOLLAR12.2 LENGTH=8 AS SUM_ACTUAL\n FROM\n WORK.PRDSALE t1\n GROUP BY\n t1.COUNTRY","outputs":[{"items":[{"data":"[\n\t{\n\t\t\"line\": \"53 /** LOG_START_INDICATOR **/\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"54 title;footnote;ods _all_ close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"55 ods graphics on;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"56 ods html5(id=vscode) style=Ignite options(bitmap_mode='inline' svg_mode='inline');\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Writing HTML5(VSCODE) Body file: sashtml4.htm\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"57 %let _SASPROGRAMFILE = %nrquote(%nrstr(/Users/elreid/personalGit/vscode-sas-extension/client/testFixture/sasnb_export.sasnb));\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"58 proc sql;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"59 CREATE TABLE WORK.QUERY_PRDSALE AS\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"60 SELECT\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"61 (t1.COUNTRY) LABEL='Country' FORMAT=$CHAR10.,\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"62 (SUM(t1.ACTUAL)) FORMAT=DOLLAR12.2 LENGTH=8 AS SUM_ACTUAL\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"63 FROM\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"64 WORK.PRDSALE t1\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"65 GROUP BY\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"66 t1.COUNTRY\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"67 ;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: Table WORK.QUERY_PRDSALE created, with 3 rows and 2 columns.\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"normal\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"67 ! quit;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"NOTE: PROCEDURE SQL used (Total process time):\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" real time 0.01 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" cpu time 0.01 seconds\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \" \",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"68 ;*';*\\\";*/;run;quit;ods html5(id=vscode) close;\",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"69 \",\n\t\t\"type\": \"source\",\n\t\t\"version\": 1\n\t},\n\t{\n\t\t\"line\": \"\",\n\t\t\"type\": \"note\",\n\t\t\"version\": 1\n\t}\n]","mime":"application/vnd.sas.compute.log.lines"}]}]},{"kind":1,"language":"markdown","value":"A last comment in Markdown at the end of the document","outputs":[]},{"kind":1,"language":"markdown","value":"","outputs":[]}] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 95217ee7c..2d726c809 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sas-lsp", - "version": "1.17.0", + "version": "1.18.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sas-lsp", - "version": "1.17.0", + "version": "1.18.0", "hasInstallScript": true, "license": "Apache-2.0", "devDependencies": { @@ -200,6 +200,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1557,6 +1558,7 @@ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1633,6 +1635,7 @@ "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", @@ -2157,6 +2160,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2519,6 +2523,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3410,6 +3415,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5731,6 +5737,7 @@ "integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -6075,6 +6082,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6618,6 +6626,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6942,6 +6951,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7052,6 +7062,7 @@ "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -7100,6 +7111,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -7428,6 +7440,7 @@ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index e0af38778..7146789fc 100644 --- a/package.json +++ b/package.json @@ -1296,7 +1296,8 @@ "embeddedLanguages": { "source.python": "python", "source.lua": "lua", - "source.sql": "sql" + "source.sql": "sql", + "source.r": "r" } } ], diff --git a/server/package-lock.json b/server/package-lock.json index 6dfc4ed8c..71750061a 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -12,22 +12,566 @@ "pyright-internal-browser": "^1.1.367", "pyright-internal-node": "^1.1.367", "vscode-languageserver": "^10.0.0-next.2", - "vscode-languageserver-textdocument": "1.0.11" + "vscode-languageserver-textdocument": "1.0.11", + "webr": "^0.4.2" }, "engines": { "node": "*" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", + "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", + "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.8", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", + "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@iarna/toml": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" }, + "node_modules/@lezer/common": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz", + "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.4.tgz", + "integrity": "sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@msgpack/msgpack": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", + "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/emscripten": { "version": "1.39.12", "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.12.tgz", "integrity": "sha512-AQImDBgudQfMqUBfrjZYilRxoHDzTBp+ejh+g1fY67eSMalwIKtBXofjpyI0JBgNpHGzxeGAR2QDya0wxW9zbA==" }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT", + "peer": true + }, "node_modules/@yarnpkg/fslib": { "version": "2.10.4", "resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.10.4.tgz", @@ -52,6 +596,15 @@ "node": ">=12 <14 || 14.2 - 14.9 || >14.10.0" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -151,6 +704,38 @@ "fsevents": "~2.3.2" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT", + "peer": true + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/codemirror-lang-r": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/codemirror-lang-r/-/codemirror-lang-r-0.1.1.tgz", + "integrity": "sha512-ke9Bm7IPKOoEk0p8LxZJaRlqp8CGOOZns9eKyj/WUaNV58h4uEeWbMpWeJJhVIPvfiuXYkv4FG1hD70gguWJLQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.10.3", + "lezer-r": "^0.1.3" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -181,6 +766,74 @@ "node": ">=4.0.0" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -189,101 +842,432 @@ "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/lezer-r": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/lezer-r/-/lezer-r-0.1.3.tgz", + "integrity": "sha512-tk+7Q54+ZYHKlLZj69GuZNC8+nMYPIFhGjrSe2fTyQAk9GyUsxgRsmF8V4r7cUiB65+lRu5/SrePeTEKQx5ZAQ==", + "license": "MIT", + "dependencies": { + "@lezer/highlight": "^1.2.1", + "@lezer/lr": "^1.4.2" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dependencies": { - "array-back": "^3.0.1" - }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.12.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lodash.camelcase": { @@ -291,6 +1275,18 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -299,6 +1295,21 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -310,6 +1321,24 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/pyright-internal-browser": { "version": "1.1.367", "resolved": "https://registry.npmjs.org/pyright-internal-browser/-/pyright-internal-browser-1.1.367.tgz", @@ -354,6 +1383,91 @@ "vscode-uri": "^3.0.8" } }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-accessible-treeview": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/react-accessible-treeview/-/react-accessible-treeview-2.11.2.tgz", + "integrity": "sha512-qui0g/gBDpP7VbtqelgJezAzAjKOY3IVi1Rq1NRJ7Z627RXKyKiQ4ooxLK2yauxTvNyU0ke9S0a2d9YUMbJJbA==", + "license": "MIT", + "peerDependencies": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-data-grid": { + "version": "7.0.0-beta.59", + "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-7.0.0-beta.59.tgz", + "integrity": "sha512-iAp/UYWjfmXYFsyKDtGDMP1IvhwtQSjCP6G/wFEbMNuumWGOEZF8Ut1S2Bp4XxVpOrBkEVKXn+QC3rs14AcB7A==", + "license": "MIT", + "peerDependencies": { + "react": "^19.2", + "react-dom": "^19.2" + } + }, + "node_modules/react-dom": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.1" + } + }, + "node_modules/react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-resizable-panels": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz", + "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -365,6 +1479,33 @@ "node": ">=8.10.0" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -382,6 +1523,47 @@ "source-map": "^0.6.0" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -418,6 +1600,25 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/typical": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", @@ -426,6 +1627,12 @@ "node": ">=8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/vscode-jsonrpc": { "version": "9.0.0-next.4", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz", @@ -472,6 +1679,120 @@ "version": "3.0.8", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/webr": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/webr/-/webr-0.4.4.tgz", + "integrity": "sha512-hm1cKoa1S64LKQy/ZO7t7wK5hX7V5icFyVwPDB5tNk9KMDhYjQVdwupieal5B4qA2QCpNU9jj1F/CTvBB7MHYg==", + "license": "SEE LICENSE IN LICENCE.md", + "dependencies": { + "@codemirror/autocomplete": "^6.8.1", + "@codemirror/commands": "^6.2.4", + "@codemirror/state": "^6.2.1", + "@codemirror/view": "^6.15.0", + "@msgpack/msgpack": "^2.8.0", + "classnames": "^2.2.6", + "codemirror": "^6.0.1", + "codemirror-lang-r": "^0.1.0-2", + "jszip": "^3.10.1", + "lezer-r": "^0.1.1", + "lightningcss": "^1.21.5", + "pako": "^2.1.0", + "prop-types": "^15.7.2", + "react": "^18.2.0", + "react-accessible-treeview": "^2.6.1", + "react-data-grid": "^7.0.0-beta.44", + "react-dom": "^18.2.0", + "react-icons": "^4.10.1", + "react-resizable-panels": "^2.0.19", + "tsx": "^4.0.0", + "xmlhttprequest-ssl": "^2.1.0", + "xterm": "^5.1.0", + "xterm-addon-fit": "^0.7.0", + "xterm-readline": "^1.1.1" + }, + "engines": { + "node": ">=17.0.0" + } + }, + "node_modules/webr/node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webr/node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/webr/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xterm": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz", + "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==", + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "license": "MIT", + "peer": true + }, + "node_modules/xterm-addon-fit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz", + "integrity": "sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==", + "deprecated": "This package is now deprecated. Move to @xterm/addon-fit instead.", + "license": "MIT", + "peerDependencies": { + "xterm": "^5.0.0" + } + }, + "node_modules/xterm-readline": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/xterm-readline/-/xterm-readline-1.1.2.tgz", + "integrity": "sha512-1+W2nVuQvCYz9OUYwFBiolrSQUui51aDDyacKXt4PuxeBHqzvabQEJ2kwdBDzsmOjz5BwlDTAjJmYpH2OGqLFA==", + "license": "MIT", + "dependencies": { + "string-width": "^4" + }, + "peerDependencies": { + "@xterm/xterm": "^5.5.0" + } } } } diff --git a/server/package.json b/server/package.json index 3609e4fef..8907e59ed 100644 --- a/server/package.json +++ b/server/package.json @@ -11,6 +11,7 @@ "pyright-internal-node": "^1.1.367", "pyright-internal-browser": "^1.1.367", "vscode-languageserver": "^10.0.0-next.2", - "vscode-languageserver-textdocument": "1.0.11" + "vscode-languageserver-textdocument": "1.0.11", + "webr": "^0.4.2" } } \ No newline at end of file diff --git a/server/src/browser/server.ts b/server/src/browser/server.ts index 82f4a96ac..6ca8156cc 100644 --- a/server/src/browser/server.ts +++ b/server/src/browser/server.ts @@ -8,6 +8,7 @@ import { } from "vscode-languageserver/browser"; import { PyrightLanguageProviderBrowser } from "../python/browser/PyrightLanguageProviderBrowser"; +import { RLanguageProviderBrowser } from "../r/browser/RLanguageProviderBrowser"; import { runServer } from "../server"; /* browser specific setup code */ @@ -16,4 +17,8 @@ const messageWriter = new BrowserMessageWriter(self); const connection: Connection = createConnection(messageReader, messageWriter); -runServer(connection, new PyrightLanguageProviderBrowser(connection, 1)); +runServer( + connection, + new PyrightLanguageProviderBrowser(connection, 1), + new RLanguageProviderBrowser(connection), +); diff --git a/server/src/node/server.ts b/server/src/node/server.ts index c01771b0c..e9992df8a 100644 --- a/server/src/node/server.ts +++ b/server/src/node/server.ts @@ -4,8 +4,13 @@ import { Connection } from "vscode-languageserver"; import { ProposedFeatures, createConnection } from "vscode-languageserver/node"; import { PyrightLanguageProviderNode } from "../python/node/PyrightLanguageProviderNode"; +import { RLanguageProviderNode } from "../r/node/RLanguageProviderNode"; import { runServer } from "../server"; const connection: Connection = createConnection(ProposedFeatures.all); -runServer(connection, new PyrightLanguageProviderNode(connection, 1)); +runServer( + connection, + new PyrightLanguageProviderNode(connection, 1), + new RLanguageProviderNode(connection), +); diff --git a/server/src/r/RLanguageProvider.ts b/server/src/r/RLanguageProvider.ts new file mode 100644 index 000000000..779874ec9 --- /dev/null +++ b/server/src/r/RLanguageProvider.ts @@ -0,0 +1,14 @@ +// Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import type { RLanguageProviderBrowser } from "./browser/RLanguageProviderBrowser"; +import type { RLanguageProviderNode } from "./node/RLanguageProviderNode"; + +/** + * Union type for R Language Provider implementations. + * + * - Node: Full R language server support (spawns external R process) + * - Browser: Stub implementation (R not available in WASM yet) + */ +export type RLanguageProvider = + | RLanguageProviderNode + | RLanguageProviderBrowser; diff --git a/server/src/r/browser/RLanguageProviderBrowser.ts b/server/src/r/browser/RLanguageProviderBrowser.ts new file mode 100644 index 000000000..1a208c652 --- /dev/null +++ b/server/src/r/browser/RLanguageProviderBrowser.ts @@ -0,0 +1,385 @@ +// Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { + CancellationToken, + CompletionItem, + CompletionItemKind, + CompletionList, + CompletionParams, + Connection, + Definition, + DefinitionLink, + DidCloseTextDocumentParams, + DidOpenTextDocumentParams, + Hover, + HoverParams, + InitializeParams, + InitializeResult, + MarkupContent, + MarkupKind, + SignatureHelp, + SignatureHelpParams, + TextDocumentPositionParams, +} from "vscode-languageserver"; +import { TextDocument } from "vscode-languageserver-textdocument"; + +import { WebR } from "webr"; + +import { LanguageServiceProvider } from "../../sas/LanguageServiceProvider"; +import { extractRCodes } from "../utils"; + +/** + * R Language Provider for Browser environment using WebR. + * + * WebR allows R to run directly in the browser via WebAssembly, + * providing basic language features like code completion and hover info. + */ +export class RLanguageProviderBrowser { + protected sasLspProvider?: (uri: string) => LanguageServiceProvider; + protected connection: Connection; + protected webR?: WebR; + protected rDocuments: Map = new Map(); + protected isInitialized = false; + + // Common R functions for autocomplete + private readonly commonRFunctions = [ + { label: "mean", detail: "mean(x, ...)", documentation: "Arithmetic Mean" }, + { + label: "median", + detail: "median(x, ...)", + documentation: "Median Value", + }, + { + label: "sum", + detail: "sum(...)", + documentation: "Sum of Vector Elements", + }, + { + label: "length", + detail: "length(x)", + documentation: "Length of an Object", + }, + { label: "print", detail: "print(x, ...)", documentation: "Print Values" }, + { + label: "cat", + detail: "cat(...)", + documentation: "Concatenate and Print", + }, + { + label: "c", + detail: "c(...)", + documentation: "Combine Values into a Vector", + }, + { label: "list", detail: "list(...)", documentation: "Create a List" }, + { + label: "data.frame", + detail: "data.frame(...)", + documentation: "Create a Data Frame", + }, + { + label: "matrix", + detail: "matrix(data, nrow, ncol)", + documentation: "Create a Matrix", + }, + { + label: "plot", + detail: "plot(x, y, ...)", + documentation: "Generic X-Y Plotting", + }, + { + label: "head", + detail: "head(x, n = 6)", + documentation: "Return First Parts of an Object", + }, + { + label: "tail", + detail: "tail(x, n = 6)", + documentation: "Return Last Parts of an Object", + }, + { + label: "str", + detail: "str(object, ...)", + documentation: "Display Structure of an Object", + }, + { + label: "summary", + detail: "summary(object, ...)", + documentation: "Object Summary", + }, + { + label: "lm", + detail: "lm(formula, data, ...)", + documentation: "Fit Linear Model", + }, + { + label: "glm", + detail: "glm(formula, family, data, ...)", + documentation: "Fit Generalized Linear Model", + }, + { + label: "read.csv", + detail: "read.csv(file, ...)", + documentation: "Read CSV File", + }, + { + label: "write.csv", + detail: "write.csv(x, file, ...)", + documentation: "Write CSV File", + }, + ]; + + constructor(connection: Connection) { + this.connection = connection; + } + + public setSasLspProvider( + provider: (uri: string) => LanguageServiceProvider, + ): void { + this.sasLspProvider = provider; + } + + public async initialize( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: InitializeParams, + ): Promise { + return { + capabilities: { + hoverProvider: true, + completionProvider: { + triggerCharacters: [".", "$"], + }, + }, + }; + } + + public onInitialized(): void { + // Initialize WebR asynchronously + this.initializeWebR().catch((err) => { + this.connection.console.error(`Failed to initialize WebR: ${err}`); + }); + } + + private async initializeWebR(): Promise { + try { + this.connection.console.log("Initializing WebR..."); + this.webR = new WebR(); + await this.webR.init(); + this.isInitialized = true; + this.connection.console.log("WebR initialized successfully"); + } catch (error) { + this.connection.console.error(`WebR initialization failed: ${error}`); + this.isInitialized = false; + } + } + + public addContentChange(doc: TextDocument): void { + if (!this.sasLspProvider) { + return; + } + + const lsp = this.sasLspProvider(doc.uri); + const rCode = extractRCodes(doc, lsp); + + if (rCode) { + this.rDocuments.set(doc.uri, rCode); + } else { + this.rDocuments.delete(doc.uri); + } + } + + public async onDidOpenTextDocument( + params: DidOpenTextDocumentParams, + ): Promise { + const doc = TextDocument.create( + params.textDocument.uri, + params.textDocument.languageId, + params.textDocument.version, + params.textDocument.text, + ); + this.addContentChange(doc); + } + + public async onDidCloseTextDocument( + params: DidCloseTextDocumentParams, + ): Promise { + this.rDocuments.delete(params.textDocument.uri); + } + + public async onHover( + params: HoverParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + if (!this.isInitialized || !this.webR) { + return null; + } + + const doc = this.getDocument(params.textDocument.uri); + if (!doc) { + return null; + } + + // Get word at position + const word = this.getWordAtPosition( + doc, + params.position.line, + params.position.character, + ); + if (!word) { + return null; + } + + try { + // Try to get help for the function/object + const helpResult = await this.webR.evalRString( + `paste(capture.output(tryCatch(help('${word}'), error = function(e) '')), collapse = '\\n')`, + ); + + if (helpResult && helpResult.trim()) { + const contents: MarkupContent = { + kind: MarkupKind.Markdown, + value: `\`\`\`r\n${word}\n\`\`\`\n\n${helpResult.substring(0, 500)}...`, + }; + return { contents }; + } + + // Fallback: try to evaluate and show type + const typeResult = await this.webR.evalRString( + `tryCatch(class(${word}), error = function(e) '')`, + ); + + if (typeResult && typeResult.trim()) { + const contents: MarkupContent = { + kind: MarkupKind.Markdown, + value: `\`\`\`r\n${word}\n\`\`\`\n\nType: ${typeResult}`, + }; + return { contents }; + } + } catch { + // Silently fail - word might not be a valid R object + } + + return null; + } + + public async onCompletion( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: CompletionParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + if (!this.isInitialized || !this.webR) { + return null; + } + + const items: CompletionItem[] = this.commonRFunctions.map((fn) => ({ + label: fn.label, + kind: CompletionItemKind.Function, + detail: fn.detail, + documentation: fn.documentation, + })); + + // Try to get installed packages for additional completions + try { + const packages = await this.webR.evalRString( + `paste(.packages(), collapse = ',')`, + ); + + if (packages) { + packages.split(",").forEach((pkg: string) => { + if (pkg.trim()) { + items.push({ + label: pkg.trim(), + kind: CompletionItemKind.Module, + detail: "Package", + }); + } + }); + } + } catch { + // Silently fail - completions still work with common functions + } + + return { + isIncomplete: false, + items, + }; + } + + public async onCompletionResolve( + item: CompletionItem, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + return item; + } + + public async onSignatureHelp( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: SignatureHelpParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + return null; + } + + public async onDefinition( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: TextDocumentPositionParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + return null; + } + + public async onShutdown( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + if (this.webR) { + try { + await this.webR.close(); + } catch (error) { + this.connection.console.error(`Error closing WebR: ${error}`); + } + } + this.rDocuments.clear(); + } + + private getDocument(uri: string): string | undefined { + return this.rDocuments.get(uri); + } + + private getWordAtPosition( + doc: string, + line: number, + character: number, + ): string | null { + const lines = doc.split("\n"); + if (line >= lines.length) { + return null; + } + + const lineText = lines[line]; + if (character >= lineText.length) { + return null; + } + + // Find word boundaries + let start = character; + let end = character; + + // Move start back to beginning of word + while (start > 0 && /[a-zA-Z0-9_.]/.test(lineText[start - 1])) { + start--; + } + + // Move end forward to end of word + while (end < lineText.length && /[a-zA-Z0-9_.]/.test(lineText[end])) { + end++; + } + + const word = lineText.substring(start, end); + return word.length > 0 ? word : null; + } +} diff --git a/server/src/r/node/RLanguageProviderNode.ts b/server/src/r/node/RLanguageProviderNode.ts new file mode 100644 index 000000000..5784be7c4 --- /dev/null +++ b/server/src/r/node/RLanguageProviderNode.ts @@ -0,0 +1,531 @@ +// Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { + CancellationToken, + CompletionItem, + CompletionItemKind, + CompletionList, + CompletionParams, + Connection, + Definition, + DefinitionLink, + DidCloseTextDocumentParams, + DidOpenTextDocumentParams, + Hover, + HoverParams, + InitializeParams, + InitializeResult, + MarkupContent, + MarkupKind, + Position, + SignatureHelp, + SignatureHelpParams, + TextDocumentPositionParams, +} from "vscode-languageserver"; +import { TextDocument } from "vscode-languageserver-textdocument"; + +import { CodeZoneManager } from "../../sas/CodeZoneManager"; +import { LanguageServiceProvider } from "../../sas/LanguageServiceProvider"; +import { extractRCodes } from "../utils"; + +interface RFunctionInfo { + label: string; + detail: string; + documentation: string; + signature?: string; +} + +/** + * R Language Provider for Node environment. + * + * Provides basic R language features like code completion and hover info + * using static function definitions. No R installation required. + */ +export class RLanguageProviderNode { + protected sasLspProvider?: (uri: string) => LanguageServiceProvider; + protected connection: Connection; + protected rDocuments: Map = new Map(); + + // Common R functions with documentation + private readonly rFunctions: Map = new Map([ + [ + "mean", + { + label: "mean", + detail: "mean(x, trim = 0, na.rm = FALSE, ...)", + documentation: + "**Arithmetic Mean**\n\nCompute the arithmetic mean of a numeric vector.\n\n**Arguments:**\n- `x`: An R object\n- `trim`: Fraction of observations to be trimmed\n- `na.rm`: Logical. Should missing values be removed?", + signature: "mean(x, trim = 0, na.rm = FALSE, ...)", + }, + ], + [ + "median", + { + label: "median", + detail: "median(x, na.rm = FALSE, ...)", + documentation: + "**Median Value**\n\nCompute the sample median.\n\n**Arguments:**\n- `x`: An R object\n- `na.rm`: Logical. Should missing values be removed?", + signature: "median(x, na.rm = FALSE, ...)", + }, + ], + [ + "sum", + { + label: "sum", + detail: "sum(..., na.rm = FALSE)", + documentation: + "**Sum of Vector Elements**\n\nReturns the sum of all the values present in its arguments.\n\n**Arguments:**\n- `...`: Numeric or complex or logical vectors\n- `na.rm`: Logical. Should missing values be removed?", + signature: "sum(..., na.rm = FALSE)", + }, + ], + [ + "length", + { + label: "length", + detail: "length(x)", + documentation: + "**Length of an Object**\n\nGet or set the length of vectors (including lists) and factors.\n\n**Arguments:**\n- `x`: An R object", + signature: "length(x)", + }, + ], + [ + "print", + { + label: "print", + detail: "print(x, ...)", + documentation: + "**Print Values**\n\nPrints its argument and returns it invisibly.\n\n**Arguments:**\n- `x`: An R object\n- `...`: Further arguments passed to methods", + signature: "print(x, ...)", + }, + ], + [ + "cat", + { + label: "cat", + detail: "cat(..., sep = ' ')", + documentation: + "**Concatenate and Print**\n\nOutputs the objects, concatenating the representations.\n\n**Arguments:**\n- `...`: R objects\n- `sep`: Character string to separate arguments", + signature: "cat(..., sep = ' ')", + }, + ], + [ + "c", + { + label: "c", + detail: "c(...)", + documentation: + "**Combine Values into a Vector**\n\nCombine Values into a Vector or List.\n\n**Arguments:**\n- `...`: Objects to be concatenated", + signature: "c(...)", + }, + ], + [ + "list", + { + label: "list", + detail: "list(...)", + documentation: + "**Create a List**\n\nFunctions to construct, coerce and check for both kinds of R lists.\n\n**Arguments:**\n- `...`: Objects, possibly named", + signature: "list(...)", + }, + ], + [ + "data.frame", + { + label: "data.frame", + detail: "data.frame(..., row.names = NULL)", + documentation: + "**Create a Data Frame**\n\nCreates data frames, tightly coupled collections of variables.\n\n**Arguments:**\n- `...`: Column vectors\n- `row.names`: NULL or character vector", + signature: "data.frame(..., row.names = NULL)", + }, + ], + [ + "matrix", + { + label: "matrix", + detail: "matrix(data = NA, nrow = 1, ncol = 1)", + documentation: + "**Create a Matrix**\n\nCreates a matrix from the given set of values.\n\n**Arguments:**\n- `data`: Data vector\n- `nrow`: Number of rows\n- `ncol`: Number of columns", + signature: "matrix(data = NA, nrow = 1, ncol = 1)", + }, + ], + [ + "plot", + { + label: "plot", + detail: "plot(x, y, ...)", + documentation: + "**Generic X-Y Plotting**\n\nGeneric function for plotting of R objects.\n\n**Arguments:**\n- `x`: X coordinates\n- `y`: Y coordinates\n- `...`: Graphical parameters", + signature: "plot(x, y, ...)", + }, + ], + [ + "head", + { + label: "head", + detail: "head(x, n = 6L)", + documentation: + "**Return First Parts of an Object**\n\nReturns the first parts of a vector, matrix, table, data frame or function.\n\n**Arguments:**\n- `x`: An object\n- `n`: Integer. Number of elements to extract", + signature: "head(x, n = 6L)", + }, + ], + [ + "tail", + { + label: "tail", + detail: "tail(x, n = 6L)", + documentation: + "**Return Last Parts of an Object**\n\nReturns the last parts of a vector, matrix, table, data frame or function.\n\n**Arguments:**\n- `x`: An object\n- `n`: Integer. Number of elements to extract", + signature: "tail(x, n = 6L)", + }, + ], + [ + "str", + { + label: "str", + detail: "str(object, ...)", + documentation: + "**Display Structure of an Object**\n\nCompactly display the internal structure of an R object.\n\n**Arguments:**\n- `object`: Any R object\n- `...`: Additional arguments", + signature: "str(object, ...)", + }, + ], + [ + "summary", + { + label: "summary", + detail: "summary(object, ...)", + documentation: + "**Object Summary**\n\nGeneric function used to produce result summaries.\n\n**Arguments:**\n- `object`: An object\n- `...`: Additional arguments", + signature: "summary(object, ...)", + }, + ], + [ + "lm", + { + label: "lm", + detail: "lm(formula, data, ...)", + documentation: + "**Fit Linear Model**\n\nFit linear models. Used to fit linear regression.\n\n**Arguments:**\n- `formula`: Model formula\n- `data`: Data frame\n- `...`: Additional arguments", + signature: "lm(formula, data, ...)", + }, + ], + [ + "glm", + { + label: "glm", + detail: "glm(formula, family, data, ...)", + documentation: + "**Fit Generalized Linear Model**\n\nFit generalized linear models.\n\n**Arguments:**\n- `formula`: Model formula\n- `family`: Error distribution\n- `data`: Data frame", + signature: "glm(formula, family, data, ...)", + }, + ], + [ + "read.csv", + { + label: "read.csv", + detail: "read.csv(file, header = TRUE, ...)", + documentation: + "**Read CSV File**\n\nReads a file in table format and creates a data frame.\n\n**Arguments:**\n- `file`: File path\n- `header`: Logical. Does file have header?\n- `...`: Additional arguments", + signature: "read.csv(file, header = TRUE, ...)", + }, + ], + [ + "write.csv", + { + label: "write.csv", + detail: "write.csv(x, file, ...)", + documentation: + "**Write CSV File**\n\nWrites a data frame to a CSV file.\n\n**Arguments:**\n- `x`: Data frame to write\n- `file`: Output file path\n- `...`: Additional arguments", + signature: "write.csv(x, file, ...)", + }, + ], + [ + "paste", + { + label: "paste", + detail: "paste(..., sep = ' ', collapse = NULL)", + documentation: + "**Concatenate Strings**\n\nConcatenate vectors after converting to character.\n\n**Arguments:**\n- `...`: One or more R objects\n- `sep`: Character string to separate terms\n- `collapse`: Optional character string to separate results", + signature: "paste(..., sep = ' ', collapse = NULL)", + }, + ], + [ + "for", + { + label: "for", + detail: "for (var in seq) expr", + documentation: + "**For Loop**\n\nExecutes a loop over a sequence.\n\n**Arguments:**\n- `var`: Loop variable\n- `seq`: Sequence to iterate over\n- `expr`: Expression to execute", + signature: "for (var in seq) expr", + }, + ], + ]); + + constructor(connection: Connection) { + this.connection = connection; + } + + public setSasLspProvider( + provider: (uri: string) => LanguageServiceProvider, + ): void { + this.sasLspProvider = provider; + } + + public async initialize( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: InitializeParams, + ): Promise { + return { + capabilities: { + hoverProvider: true, + completionProvider: { + triggerCharacters: [".", "$"], + }, + }, + }; + } + + public onInitialized(): void { + this.connection.console.log( + "R language provider initialized (static mode)", + ); + } + + public addContentChange(doc: TextDocument): void { + if (!this.sasLspProvider) { + return; + } + + const lsp = this.sasLspProvider(doc.uri); + const rCode = extractRCodes(doc, lsp); + + if (rCode) { + this.rDocuments.set(doc.uri, rCode); + } else { + this.rDocuments.delete(doc.uri); + } + } + + public async onDidOpenTextDocument( + params: DidOpenTextDocumentParams, + ): Promise { + const doc = TextDocument.create( + params.textDocument.uri, + params.textDocument.languageId, + params.textDocument.version, + params.textDocument.text, + ); + this.addContentChange(doc); + } + + public async onDidCloseTextDocument( + params: DidCloseTextDocumentParams, + ): Promise { + this.rDocuments.delete(params.textDocument.uri); + } + + public async onHover( + params: HoverParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + const doc = this.getDocument(params.textDocument.uri); + if (!doc) { + return null; + } + + // Map SAS position to R code position + const rPosition = this.mapSasPositionToR( + params.textDocument.uri, + params.position, + ); + if (!rPosition) { + return null; + } + + // Get word at position in R code + const word = this.getWordAtPosition( + doc, + rPosition.line, + rPosition.character, + ); + if (!word) { + return null; + } + + // Look up function info + const funcInfo = this.rFunctions.get(word); + if (funcInfo) { + const contents: MarkupContent = { + kind: MarkupKind.Markdown, + value: `\`\`\`r\n${funcInfo.detail}\n\`\`\`\n\n${funcInfo.documentation}`, + }; + return { contents }; + } + + return null; + } + + public async onCompletion( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: CompletionParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + const items: CompletionItem[] = Array.from(this.rFunctions.values()).map( + (fn) => ({ + label: fn.label, + kind: CompletionItemKind.Function, + detail: fn.detail, + documentation: fn.documentation, + }), + ); + + return { + isIncomplete: false, + items, + }; + } + + public async onCompletionResolve( + item: CompletionItem, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + return item; + } + + public async onSignatureHelp( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: SignatureHelpParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + return null; + } + + public async onDefinition( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: TextDocumentPositionParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + return null; + } + + public async onShutdown( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: CancellationToken, + ): Promise { + this.rDocuments.clear(); + } + + private getDocument(uri: string): string | undefined { + return this.rDocuments.get(uri); + } + + private mapSasPositionToR( + sasUri: string, + sasPosition: Position, + ): Position | null { + if (!this.sasLspProvider) { + return null; + } + + const lsp = this.sasLspProvider(sasUri); + const codeZoneManager = lsp.getCodeZoneManager(); + const zone = codeZoneManager.getCurrentZone( + sasPosition.line, + sasPosition.character, + ); + + // Only process if we're in an embedded language zone (R code) + if (zone !== CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG) { + return null; + } + + // Find the line offset in the extracted R code + const symbols = lsp.getDocumentSymbols(); + + let rLineOffset = 0; + for (const symbol of symbols) { + if (symbol.name?.toUpperCase() !== "PROC RLANG") { + continue; + } + + // Find the start of the R code within this PROC RLANG + let rCodeStartLine = symbol.range.start.line; + const pos = { line: symbol.range.start.line, character: 0 }; + + while (pos.line <= symbol.range.end.line) { + const currentZone = codeZoneManager.getCurrentZone( + pos.line, + pos.character, + ); + if (currentZone === CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG) { + rCodeStartLine = pos.line; + break; + } + pos.line++; + } + + // If the cursor is in this PROC RLANG block + if ( + sasPosition.line >= rCodeStartLine && + sasPosition.line <= symbol.range.end.line + ) { + const rLine = sasPosition.line - rCodeStartLine + rLineOffset; + return { + line: rLine, + character: sasPosition.character, + }; + } + + // Count lines in this R block for offset calculation + let blockLines = 0; + for (let line = rCodeStartLine; line <= symbol.range.end.line; line++) { + if ( + codeZoneManager.getCurrentZone(line, 0) === + CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG + ) { + blockLines++; + } + } + rLineOffset += blockLines; + } + + return null; + } + + private getWordAtPosition( + doc: string, + line: number, + character: number, + ): string | null { + const lines = doc.split("\n"); + if (line >= lines.length) { + return null; + } + + const lineText = lines[line]; + if (character >= lineText.length) { + return null; + } + + // Find word boundaries + let start = character; + let end = character; + + // Move start back to beginning of word + while (start > 0 && /[a-zA-Z0-9_.]/.test(lineText[start - 1])) { + start--; + } + + // Move end forward to end of word + while (end < lineText.length && /[a-zA-Z0-9_.]/.test(lineText[end])) { + end++; + } + + const word = lineText.substring(start, end); + return word.length > 0 ? word : null; + } +} diff --git a/server/src/r/utils.ts b/server/src/r/utils.ts new file mode 100644 index 000000000..a65b1c1e2 --- /dev/null +++ b/server/src/r/utils.ts @@ -0,0 +1,80 @@ +// Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { DocumentSymbol } from "vscode-languageserver"; +import { TextDocument } from "vscode-languageserver-textdocument"; + +import { CodeZoneManager } from "../sas/CodeZoneManager"; +import { LanguageServiceProvider } from "../sas/LanguageServiceProvider"; +import { isCustomRegionStartComment } from "../sas/utils"; + +/** + * Extracts R code from PROC RLANG blocks in a SAS document. + * This converts the SAS document with embedded R into a pure R document + * that the R language server can analyze. + */ +export const extractRCodes = ( + doc: TextDocument, + languageService: LanguageServiceProvider, +): string => { + const codeZoneManager = languageService.getCodeZoneManager(); + const rDocLines: string[] = []; + const symbols: DocumentSymbol[] = languageService.getDocumentSymbols(); + + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + if (isCustomRegionStartComment(symbol.name)) { + symbols.splice(i + 1, 0, ...(symbol.children ?? [])); + } + if (symbol.name?.toUpperCase() !== "PROC RLANG") { + continue; + } + + let rCodeStart = undefined; + let rCodeEnd = undefined; + const pos = { ...symbol.range.start }; + + while (pos.line <= symbol.range.end.line) { + if ( + !rCodeStart && + codeZoneManager.getCurrentZone(pos.line, pos.character) === + CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG + ) { + rCodeStart = { ...pos }; + } + if ( + rCodeStart && + codeZoneManager.getCurrentZone(pos.line, pos.character) !== + CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG + ) { + rCodeEnd = { ...pos }; + if (rCodeEnd.character === 0) { + rCodeEnd.line--; + rCodeEnd.character = + languageService.model.getColumnCount(rCodeEnd.line) - 1; + if (rCodeEnd.character < 0) { + rCodeEnd.character = 0; + } + } + break; + } + pos.line++; + pos.character = 0; + } + + if (!rCodeStart) { + continue; + } + if (!rCodeEnd) { + rCodeEnd = { ...symbol.range.end }; + } + + const rCode = doc.getText({ + start: rCodeStart, + end: rCodeEnd, + }); + + rDocLines.push(rCode); + } + + return rDocLines.join("\n"); +}; diff --git a/server/src/sas/CodeZoneManager.ts b/server/src/sas/CodeZoneManager.ts index 08ed4f0e5..a55717882 100644 --- a/server/src/sas/CodeZoneManager.ts +++ b/server/src/sas/CodeZoneManager.ts @@ -1132,7 +1132,7 @@ export class CodeZoneManager { this._getFullStmtName(context, this._procName, stmt); const zone = this._stmtEx(context, stmt); type = zone.type; - if (["PYTHON", "LUA"].includes(this._procName)) { + if (["PYTHON", "LUA", "RLANG"].includes(this._procName)) { if (!this._embeddedCodeStarted) { if (["SUBMIT", "INTERACTIVE", "I"].includes(stmt.text)) { this._embeddedCodeStarted = true; diff --git a/server/src/sas/Lexer.ts b/server/src/sas/Lexer.ts index a3f7ed182..f8cfd09ec 100644 --- a/server/src/sas/Lexer.ts +++ b/server/src/sas/Lexer.ts @@ -86,6 +86,9 @@ enum EmbeddedLangState { PROC_LUA_DEF, PROC_LUA_SUBMIT_OR_INTERACTIVE, PROC_LUA_CODE, + PROC_RLANG_DEF, + PROC_RLANG_SUBMIT_OR_INTERACTIVE, + PROC_RLANG_CODE, } export class Lexer { start = { line: 0, column: 0 }; @@ -290,6 +293,8 @@ export class Lexer { ) { if (token.type === "text" && token.text === "PYTHON") { this.context.embeddedLangState = EmbeddedLangState.PROC_PYTHON_DEF; + } else if (token.type === "text" && token.text === "RLANG") { + this.context.embeddedLangState = EmbeddedLangState.PROC_RLANG_DEF; } else if (token.type === "text" && token.text === "LUA") { this.context.embeddedLangState = EmbeddedLangState.PROC_LUA_DEF; } @@ -324,6 +329,20 @@ export class Lexer { } break SWITCH; } + case EmbeddedLangState.PROC_RLANG_DEF: { + token = this._readToken(); + if (!token) { + break SWITCH; + } + if ( + token.type === "text" && + ["SUBMIT", "INTERACTIVE", "I"].includes(token.text) + ) { + this.context.embeddedLangState = + EmbeddedLangState.PROC_RLANG_SUBMIT_OR_INTERACTIVE; + } + break SWITCH; + } case EmbeddedLangState.PROC_PYTHON_SUBMIT_OR_INTERACTIVE: { token = this._readToken(); if (!token) { @@ -386,6 +405,55 @@ export class Lexer { token = this._foundEmbeddedCodeToken(this.curr); break SWITCH; } + case EmbeddedLangState.PROC_RLANG_SUBMIT_OR_INTERACTIVE: { + token = this._readToken(); + if (!token) { + break SWITCH; + } + if (token.type === "sep" && token.text === ";") { + this.context.embeddedLangState = EmbeddedLangState.PROC_RLANG_CODE; + } + break SWITCH; + } + case EmbeddedLangState.PROC_RLANG_CODE: { + // R doesn't have multi-line string delimiters like Python's triple quotes + for ( + let line = this.curr.line; + line < this.model.getLineCount(); + line++ + ) { + const lineContent = this._readEmbeddedCodeLine(this.curr, line); + let pos = 0; + let match; + do { + if (match) { + pos += match.index + match[0].length; + } + const stringReg = /("[^"]*?("|$))|('[^']*?('|$))/; + const commentReg = /#.*$/; + const secReg = + /(\b((endsubmit|endinteractive)(\s+|\/\*.*?\*\/)*;|(data|proc|%macro)\b[^'";]*;))/; + match = new RegExp( + `${stringReg.source}|${commentReg.source}|${secReg.source}`, + "m", + ).exec(lineContent.substring(pos)); + if (match) { + const matchedText = match[0]; + if (/^('|"|#)/.test(matchedText)) { + // do nothing to skip string and single line comment + } else { + token = this._foundEmbeddedCodeToken(this.curr, { + line: line, + column: pos + match.index, + }); + break SWITCH; + } + } + } while (match); + } + token = this._foundEmbeddedCodeToken(this.curr); + break SWITCH; + } case EmbeddedLangState.PROC_LUA_SUBMIT_OR_INTERACTIVE: { token = this._readToken(); if (!token) { diff --git a/server/src/sas/LexerEx.ts b/server/src/sas/LexerEx.ts index 8ca905437..848e766fe 100644 --- a/server/src/sas/LexerEx.ts +++ b/server/src/sas/LexerEx.ts @@ -3101,7 +3101,11 @@ export class LexerEx { this.setKeyword_(token, true); generalProcStmt = false; } - } else if (procName === "LUA" || procName === "PYTHON") { + } else if ( + procName === "LUA" || + procName === "PYTHON" || + procName === "RLANG" + ) { if (["SUBMIT", "INTERACTIVE", "I"].includes(word)) { const next = this.prefetch_({ pos: 1 }); if (next && next.text === ";" && next.type === "sep") { diff --git a/server/src/sas/formatter/parser.ts b/server/src/sas/formatter/parser.ts index 07f4e6cba..6a2d35059 100644 --- a/server/src/sas/formatter/parser.ts +++ b/server/src/sas/formatter/parser.ts @@ -124,7 +124,7 @@ const preserveProcs = ( token: Token, model: Model, ) => { - // should not format python/lua, treat it as raw data + // should not format python/R/lua, treat it as raw data const lastStatement = region.children.length >= 2 && region.children[region.children.length - 1].children; @@ -135,7 +135,7 @@ const preserveProcs = ( region.children[0].children.length > 0 && lastStatement.length > 1 && "text" in region.children[0].children[1] && - /^(python|lua)$/i.test(region.children[0].children[1].text) && + /^(python|lua|rlang)$/i.test(region.children[0].children[1].text) && "text" in lastStatement[0] && /^(submit|interactive|i)$/i.test(lastStatement[0].text) ) { @@ -286,7 +286,7 @@ export const getParser = const node = tokens[i]; let parent = parents.length ? parents[parents.length - 1] : root; - //#region --- Preserve Python/Lua + //#region --- Preserve Python/R/Lua if (region && region.block) { preserveProc = preserveProcs(preserveProc, region, node, model); if (preserveProc === 0 && i === tokens.length - 1) { diff --git a/server/src/server.ts b/server/src/server.ts index 3c8ca1963..82a5405c8 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -38,6 +38,7 @@ import { CollectionResult } from "pyright-internal-node/dist/packages/pyright-in import { ParseFileResults } from "pyright-internal-node/dist/packages/pyright-internal/src/parser/parser"; import { PyrightLanguageProvider } from "./python/PyrightLanguageProvider"; +import { RLanguageProvider } from "./r/RLanguageProvider"; import { CodeZoneManager } from "./sas/CodeZoneManager"; import { LanguageServiceProvider, legend } from "./sas/LanguageServiceProvider"; import type { LibCompleteItem } from "./sas/SyntaxDataProvider"; @@ -52,6 +53,7 @@ interface DocumentInfo { export const runServer = ( connection: Connection, _pyrightLanguageProvider: PyrightLanguageProvider, + _rLanguageProvider: RLanguageProvider, ) => { const documentPool: Record = {}; @@ -59,6 +61,7 @@ export const runServer = ( let registeredAdvancedCapabilities = false; _pyrightLanguageProvider.setSasLspProvider(getLanguageService); + _rLanguageProvider.setSasLspProvider(getLanguageService); connection.onInitialize((params) => { if ( @@ -117,7 +120,10 @@ export const runServer = ( return result; }); - connection.onInitialized(() => _pyrightLanguageProvider.onInitialized()); + connection.onInitialized(() => { + _pyrightLanguageProvider.onInitialized(); + _rLanguageProvider.onInitialized(); + }); connection.onRequest(SemanticTokensRequest.type, (params) => { syncIfDocChange(params.textDocument.uri); @@ -136,6 +142,9 @@ export const runServer = ( async python(pyrightLanguageService) { return await pyrightLanguageService.onHover(params, token); }, + async r(rLanguageService) { + return await rLanguageService.onHover(params, token); + }, }); }); @@ -216,6 +225,53 @@ export const runServer = ( } return completionList; }, + async r(rLanguageService) { + const completionList = await rLanguageService.onCompletion( + params, + token, + ); + if (completionList) { + for (const item of completionList.items) { + if (!item.data) { + item.data = {}; + } + item.data._languageService = "r"; + item.data._uri = params.textDocument.uri; + } + + if ( + params.context?.triggerKind === CompletionTriggerKind.Invoked || + params.context?.triggerKind === + CompletionTriggerKind.TriggerForIncompleteCompletions + ) { + const doc = documentPool[params.textDocument.uri].document; + const line = doc.getText({ + start: { + line: params.position.line, + character: 0, + }, + end: params.position, + }); + if (!/\W/.test(line.trimStart())) { + const item = { + kind: CompletionItemKind.Keyword, + data: { + _languageService: "sas", + _uri: params.textDocument.uri, + }, + }; + if ( + completionList.items.findIndex( + (item) => item.label === "endsubmit", + ) === -1 + ) { + completionList.items.push({ ...item, label: "endsubmit" }); + } + } + } + } + return completionList; + }, }); }); @@ -231,6 +287,11 @@ export const runServer = ( completionItem, token, ); + } else if (lang === "r") { + return await _rLanguageProvider.onCompletionResolve( + completionItem, + token, + ); } return completionItem; }); @@ -329,6 +390,14 @@ export const runServer = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any )) as any; }, + async r(rLanguageService) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return (await rLanguageService.onSignatureHelp( + params, + token, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + )) as any; + }, }); }); @@ -350,12 +419,14 @@ export const runServer = ( ); documentPool[doc.uri] = { document: doc, changed: false }; await _pyrightLanguageProvider.onDidOpenTextDocument(params); + await _rLanguageProvider.onDidOpenTextDocument(params); }); connection.onDidCloseTextDocument(async (params) => { const uri = params.textDocument.uri; delete documentPool[uri]; await _pyrightLanguageProvider.onDidCloseTextDocument(params); + await _rLanguageProvider.onDidCloseTextDocument(params); }); connection.onDidChangeTextDocument((params) => { @@ -382,6 +453,9 @@ export const runServer = ( async python(pyrightLanguageService) { return await pyrightLanguageService.onDefinition(params, token); }, + async r(rLanguageService) { + return await rLanguageService.onDefinition(params, token); + }, }); }, ); @@ -562,6 +636,7 @@ export const runServer = ( connection.onShutdown(async (token) => { await _pyrightLanguageProvider.onShutdown(token); + await _rLanguageProvider.onShutdown(token); }); // Listen on the connection @@ -591,9 +666,11 @@ export const runServer = ( python?: ( pyrightLanguageService: PyrightLanguageProvider, ) => Promise; + r?: (rLanguageService: RLanguageProvider) => Promise; default?: (languageServices: { sasLanguageService: LanguageServiceProvider; pythonLanguageService?: PyrightLanguageProvider; + rLanguageService?: RLanguageProvider; }) => Promise; }, ) => { @@ -627,6 +704,24 @@ export const runServer = ( return await callbacks.default({ sasLanguageService: languageService, pythonLanguageService: _pyrightLanguageProvider, + rLanguageService: _rLanguageProvider, + }); + } else { + return undefined; + } + } + if ( + symbol.name?.toUpperCase() === "PROC RLANG" && + codeZoneManager.getCurrentZone(pos.line, pos.character) === + CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG + ) { + if (callbacks.r) { + return await callbacks.r(_rLanguageProvider); + } else if (callbacks.default) { + return await callbacks.default({ + sasLanguageService: languageService, + pythonLanguageService: _pyrightLanguageProvider, + rLanguageService: _rLanguageProvider, }); } else { return undefined; @@ -640,6 +735,7 @@ export const runServer = ( return await callbacks.default({ sasLanguageService: languageService, pythonLanguageService: _pyrightLanguageProvider, + rLanguageService: _rLanguageProvider, }); } { @@ -660,11 +756,16 @@ export const runServer = ( const syncIfDocChange = (uri: string) => { const docInfo = documentPool[uri]; + if (!docInfo) { + // Document not in pool yet - this can happen if hover is triggered before onDidOpenTextDocument + return; + } if (!docInfo.changed) { return; } docInfo.changed = false; _pyrightLanguageProvider.addContentChange(docInfo.document); + _rLanguageProvider.addContentChange(docInfo.document); }; const syncAllChangedDoc = () => { diff --git a/server/test/embedded_lang/embedded_lang.test.ts b/server/test/embedded_lang/embedded_lang.test.ts index b8c1b3ed7..4a0725e59 100644 --- a/server/test/embedded_lang/embedded_lang.test.ts +++ b/server/test/embedded_lang/embedded_lang.test.ts @@ -64,4 +64,19 @@ describe("Test code zone for embedded language", () => { assert.equal(zoneList[4], CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG); assert.equal(zoneList[6], CodeZoneManager.ZONE_TYPE.PROC_STMT); }); + + it("proc rlang", () => { + const doc = openDoc("server/testFixture/embedded_lang/proc_rlang.sas"); + const languageServer = new LanguageServiceProvider(doc); + const codeZoneManager = languageServer.getCodeZoneManager(); + + const zoneList = []; + for (let i = 0; i < doc.lineCount; i++) { + zoneList.push(codeZoneManager.getCurrentZone(i, 1)); + } + assert.equal(zoneList[1], CodeZoneManager.ZONE_TYPE.PROC_STMT); + assert.equal(zoneList[2], CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG); + assert.equal(zoneList[4], CodeZoneManager.ZONE_TYPE.EMBEDDED_LANG); + assert.equal(zoneList[6], CodeZoneManager.ZONE_TYPE.PROC_STMT); + }); }); diff --git a/server/test/python/extract_python_codes.test.ts b/server/test/python/extract_python_codes.test.ts new file mode 100644 index 000000000..6eae8ada7 --- /dev/null +++ b/server/test/python/extract_python_codes.test.ts @@ -0,0 +1,158 @@ +// Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TextDocument } from "vscode-languageserver-textdocument"; + +import { assert } from "chai"; + +import { extractPythonCodes } from "../../src/python/utils"; +import { LanguageServiceProvider } from "../../src/sas/LanguageServiceProvider"; + +describe("Python Code Extraction", () => { + it("extracts simple Python code from PROC PYTHON", () => { + const sasCode = `proc python; +submit; +x = [1, 2, 3] +print(sum(x)) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const pythonCode = extractPythonCodes(doc, languageService); + + assert.include(pythonCode, "x = [1, 2, 3]"); + assert.include(pythonCode, "print(sum(x))"); + assert.include(pythonCode, "import sas2py"); // Python includes helper import + }); + + it("extracts Python code from multiple PROC PYTHON blocks", () => { + const sasCode = `proc python; +submit; +x = list(range(10)) +endsubmit; +run; + +data _null_; + put "SAS code"; +run; + +proc python; +submit; +y = sum(x) +print(y) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const pythonCode = extractPythonCodes(doc, languageService); + + assert.include(pythonCode, "x = list(range(10))"); + assert.include(pythonCode, "y = sum(x)"); + assert.include(pythonCode, "print(y)"); + }); + + it("handles Python code with comments", () => { + const sasCode = `proc python; +submit; +# This is a Python comment +x = [1, 2, 3] +# Calculate sum +print(sum(x)) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const pythonCode = extractPythonCodes(doc, languageService); + + assert.include(pythonCode, "# This is a Python comment"); + assert.include(pythonCode, "# Calculate sum"); + }); + + it("handles Python code with triple-quoted strings", () => { + const sasCode = `proc python; +submit; +text = """Hello +World""" +print(text) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const pythonCode = extractPythonCodes(doc, languageService); + + assert.include(pythonCode, "text ="); + assert.include(pythonCode, "print(text)"); + }); + + it("handles Python code with loops and control structures", () => { + const sasCode = `proc python; +submit; +for i in range(10): + print(i) + if i == 5: + break +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const pythonCode = extractPythonCodes(doc, languageService); + + assert.include(pythonCode, "for i in range(10):"); + assert.include(pythonCode, "print(i)"); + assert.include(pythonCode, "if i == 5:"); + assert.include(pythonCode, "break"); + }); + + it("handles Python code with function definitions", () => { + const sasCode = `proc python; +submit; +def my_func(x, y): + result = x + y + return result +z = my_func(5, 3) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const pythonCode = extractPythonCodes(doc, languageService); + + assert.include(pythonCode, "def my_func(x, y):"); + assert.include(pythonCode, "return result"); + assert.include(pythonCode, "z = my_func(5, 3)"); + }); + + it("returns import statement when no PROC PYTHON blocks exist", () => { + const sasCode = `data _null_; + x = 5; + put x=; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const pythonCode = extractPythonCodes(doc, languageService); + + assert.include(pythonCode, "import sas2py"); + assert.notInclude(pythonCode, "data _null_"); + }); + + it("handles interactive mode", () => { + const sasCode = `proc python; +interactive; +import pandas as pd +df = pd.DataFrame({'x': [1, 2, 3]}) +endinteractive; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const pythonCode = extractPythonCodes(doc, languageService); + + assert.include(pythonCode, "import pandas as pd"); + assert.include(pythonCode, "df = pd.DataFrame"); + }); +}); diff --git a/server/test/r/extract_r_codes.test.ts b/server/test/r/extract_r_codes.test.ts new file mode 100644 index 000000000..1d3b3ea46 --- /dev/null +++ b/server/test/r/extract_r_codes.test.ts @@ -0,0 +1,206 @@ +// Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { TextDocument } from "vscode-languageserver-textdocument"; + +import { assert } from "chai"; + +import { extractRCodes } from "../../src/r/utils"; +import { LanguageServiceProvider } from "../../src/sas/LanguageServiceProvider"; + +describe("R Code Extraction", () => { + it("extracts simple R code from PROC RLANG", () => { + const sasCode = `proc rlang; +submit; +x <- c(1, 2, 3) +mean(x) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.include(rCode, "x <- c(1, 2, 3)"); + assert.include(rCode, "mean(x)"); + assert.notInclude(rCode, "proc rlang"); + assert.notInclude(rCode, "endsubmit"); + }); + + it("extracts R code from multiple PROC RLANG blocks", () => { + const sasCode = `proc rlang; +submit; +x <- 1:10 +endsubmit; +run; + +data _null_; + put "SAS code"; +run; + +proc rlang; +submit; +y <- mean(x) +print(y) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.include(rCode, "x <- 1:10"); + assert.include(rCode, "y <- mean(x)"); + assert.include(rCode, "print(y)"); + assert.notInclude(rCode, 'put "SAS code"'); + }); + + it("handles R code with comments", () => { + const sasCode = `proc rlang; +submit; +# This is an R comment +x <- c(1, 2, 3) +# Calculate mean +mean(x) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.include(rCode, "# This is an R comment"); + assert.include(rCode, "# Calculate mean"); + }); + + it("handles R code with multiline strings", () => { + const sasCode = `proc rlang; +submit; +text <- "Hello +World" +cat(text) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.include(rCode, "text <-"); + assert.include(rCode, "cat(text)"); + }); + + it("handles R code with loops and control structures", () => { + const sasCode = `proc rlang; +submit; +for (i in 1:10) { + print(i) + if (i == 5) { + break + } +} +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.include(rCode, "for (i in 1:10)"); + assert.include(rCode, "print(i)"); + assert.include(rCode, "if (i == 5)"); + assert.include(rCode, "break"); + }); + + it("handles R code with function definitions", () => { + const sasCode = `proc rlang; +submit; +my_func <- function(x, y) { + result <- x + y + return(result) +} +z <- my_func(5, 3) +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.include(rCode, "my_func <- function(x, y)"); + assert.include(rCode, "return(result)"); + assert.include(rCode, "z <- my_func(5, 3)"); + }); + + it("returns empty string when no PROC RLANG blocks exist", () => { + const sasCode = `data _null_; + x = 5; + put x=; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.equal(rCode, ""); + }); + + it("handles empty PROC RLANG blocks", () => { + const sasCode = `proc rlang; +submit; +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.equal(rCode, ""); + }); + + it("handles mixed SAS and R code", () => { + const sasCode = `/* SAS comment */ +data test; + x = 1; +run; + +proc rlang; +submit; +r_data <- data.frame(x = 1:5, y = 6:10) +summary(r_data) +endsubmit; +run; + +proc print data=test; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + assert.include(rCode, "r_data <- data.frame"); + assert.include(rCode, "summary(r_data)"); + assert.notInclude(rCode, "data test"); + assert.notInclude(rCode, "proc print"); + }); + + it("preserves R code whitespace and indentation", () => { + const sasCode = `proc rlang; +submit; +if (TRUE) { + x <- 1 + if (x > 0) { + print("positive") + } +} +endsubmit; +run;`; + + const doc = TextDocument.create("test.sas", "sas", 1, sasCode); + const languageService = new LanguageServiceProvider(doc); + const rCode = extractRCodes(doc, languageService); + + // Check that indentation is preserved + assert.include(rCode, " x <- 1"); + assert.include(rCode, ' print("positive")'); + }); +}); diff --git a/server/testFixture/embedded_lang/proc_rlang.sas b/server/testFixture/embedded_lang/proc_rlang.sas new file mode 100644 index 000000000..e608f2c28 --- /dev/null +++ b/server/testFixture/embedded_lang/proc_rlang.sas @@ -0,0 +1,9 @@ +proc rlang; +submit; + for (x in 1:6) { + print(x) + mean(x) + } + paste("first statement after for loop") +endsubmit; +run; diff --git a/syntaxes/sas.tmLanguage.json b/syntaxes/sas.tmLanguage.json index 05616611b..eada48c42 100644 --- a/syntaxes/sas.tmLanguage.json +++ b/syntaxes/sas.tmLanguage.json @@ -76,6 +76,31 @@ } ] }, + { + "begin": "(?i)proc(\\s|/\\*.*?\\*/)*rlang", + "end": "(?i)(?=(run|quit)(\\s|/\\*.*?\\*/)*;|(data|proc|%macro)\\b[^;]*;)", + "name": "sas.proc.rlang", + "patterns": [ + { + "include": "#strings-or-comments" + }, + { + "begin": "(?i)(?<=\\bsubmit|\\binteractive|\\bi)(\\s|/\\*.*?\\*/)*;", + "end": "(?i)(endsubmit|endinteractive)(\\s|/\\*.*?\\*/)*;", + "name": "source.r", + "beginCaptures": { + "0": { + "name": "strange_bug" + } + }, + "patterns": [ + { + "include": "source.r" + } + ] + } + ] + }, { "begin": "(?i)data\\b.*?;", "end": "(?i)(?=(run|quit)(\\s|/\\*.*?\\*/)*;|(data|proc|%macro)\\b[^;]*;)", diff --git a/website/docs/Features/sasNotebook.md b/website/docs/Features/sasNotebook.md index c2e7afb8d..69e7334da 100644 --- a/website/docs/Features/sasNotebook.md +++ b/website/docs/Features/sasNotebook.md @@ -19,13 +19,30 @@ Starting with Visual Studio Code version 1.93, [the language for SQL files has b ::: +## Language Support for Embedded Code + +SAS Notebook supports multiple languages including Python, R, SQL, and Lua. When working with these languages in SAS notebooks, enhanced language features such as code completion, hover information, and signature help are available. + +### Python Language Features + +Python code cells benefit from integrated IntelliSense powered by Pyright, which is included with the extension. No additional setup is required for Python language features. + +### R Language Features + +R code cells include basic IntelliSense features (code completion and hover documentation) for common R functions: + +- **In VS Code Desktop**: Basic code completion and hover information for common R functions is provided automatically - no installation required +- **In Browser (VS Code for Web)**: Enhanced R language features are automatically available via WebR - no installation required! WebR provides basic code completion and hover information for common R functions. + +Note: R code execution via PROC RLANG requires a SAS connection, but the language features work independently. + ## Export To export your SAS Notebook to other formats, click the **More Actions** (`...`) button on the notebook toolbar at top, and select `Export`. The following formats are supported. ### SAS -PYTHON and SQL code cells will be wrapped with PROC PYTHON/SQL respectively to be executed on SAS. Markdown cells will be converted to block comments. +PYTHON, R, and SQL code cells will be wrapped with PROC PYTHON/RLANG/SQL respectively to be executed on SAS. Markdown cells will be converted to block comments. ### HTML diff --git a/website/docs/README.md b/website/docs/README.md index e54c39d6e..8955ff2ef 100644 --- a/website/docs/README.md +++ b/website/docs/README.md @@ -16,4 +16,4 @@ The SAS extension includes the following features: - Access to SAS Content and libraries -- Ability to create notebooks for SAS, SQL, Python, and other languages +- Ability to create notebooks for SAS, SQL, Python, R, and other languages diff --git a/website/docs/faq.md b/website/docs/faq.md index 890425d00..e91bca559 100644 --- a/website/docs/faq.md +++ b/website/docs/faq.md @@ -107,3 +107,36 @@ If the options on the Problems panel toolbar are not visible, you can display th ### Can I control whether errors and warnings from my SAS log are displayed in the Problems panel? Yes. The `SAS.problems.log` setting controls whether problems from the SAS log are displayed in the Problems panel. This option is enabled by default. To access this option, select `File > Preferences > Settings`, and search for "sas problems". + +## Language Server questions + +### Why don't I see code completion or hover information in my PROC RLANG code? + +**In VS Code Desktop**: Basic R language features (code completion and hover) are provided automatically for common R functions - no installation required. If you're not seeing them: + +1. Make sure you're inside a `PROC RLANG` submit block +2. Check that your cursor is positioned on R code (not SAS code) +3. Try restarting VS Code if features don't appear + +**In Browser (VS Code for Web)**: R language features are automatically available via WebR. If you don't see completions: + +```r +1. WebR may still be initializing - wait a few moments after opening the file +2. Check the console log (`Help > Toggle Developer Tools`) for WebR initialization messages +3. Refresh the page if features don\'t appear after waiting + +### How can I verify that R language features are working? + +**In VS Code Desktop**: +1. Open a SAS file with a PROC RLANG submit block +2. Type common R functions like `mean`, `sum`, `print`, or `paste` inside the submit block +3. You should see autocomplete suggestions as you type +4. Hover over these function names - you should see documentation + +If features don\'t work, check the VS Code console log (`Help > Toggle Developer Tools`) for errors. + +**In Browser (VS Code for Web)**: +1. Open a SAS file with a PROC RLANG block +2. Check the console log (`Help > Toggle Developer Tools`) for messages like "WebR initialized successfully" +3. Try typing common R functions like `mean`, `sum`, or `plot` - you should see autocomplete suggestions +```