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..ba6de89fd 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ 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 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..56837979e 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,10 @@ ${code}`; wrapped = this.wrapPython(wrapped); } + if (this.parameters.languageId === "r") { + 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/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/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/testFixture/embedded_lang/proc_rlang.sas b/server/testFixture/embedded_lang/proc_rlang.sas new file mode 100644 index 000000000..da286d1e4 --- /dev/null +++ b/server/testFixture/embedded_lang/proc_rlang.sas @@ -0,0 +1,8 @@ +proc rlang; +submit; + for (x in 1:6) { + print(x) + } + print("first statement after for loop") +endsubmit; +run; \ No newline at end of file diff --git a/website/docs/Features/sasNotebook.md b/website/docs/Features/sasNotebook.md index c2e7afb8d..01066361d 100644 --- a/website/docs/Features/sasNotebook.md +++ b/website/docs/Features/sasNotebook.md @@ -25,7 +25,7 @@ To export your SAS Notebook to other formats, click the **More Actions** (`...`) ### 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