├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── dependabot.yml └── workflows │ ├── deploy-doc.yml │ ├── package.yml │ ├── pr.yml │ └── translations.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ContributorAgreement.txt ├── LICENSE ├── README.md ├── SUPPORT.md ├── client ├── package-lock.json ├── package.json ├── src │ ├── browser │ │ └── extension.ts │ ├── commands │ │ ├── authorize.ts │ │ ├── closeSession.ts │ │ ├── new.ts │ │ ├── profile.ts │ │ └── run.ts │ ├── components │ │ ├── APIProvider.ts │ │ ├── AuthProvider.ts │ │ ├── CAHelper.ts │ │ ├── ContentNavigator │ │ │ ├── ContentAdapterFactory.ts │ │ │ ├── ContentDataProvider.ts │ │ │ ├── ContentModel.ts │ │ │ ├── const.ts │ │ │ ├── convert.ts │ │ │ ├── index.ts │ │ │ ├── mime-types.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── ExtensionContext.ts │ │ ├── LibraryNavigator │ │ │ ├── LibraryAdapterFactory.ts │ │ │ ├── LibraryDataProvider.ts │ │ │ ├── LibraryModel.ts │ │ │ ├── PaginatedResultSet.ts │ │ │ ├── const.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── ResultPanel │ │ │ ├── ResultPanel.ts │ │ │ ├── ResultPanelSubscriptionProvider.ts │ │ │ └── index.ts │ │ ├── StatusBarItem.ts │ │ ├── SubscriptionProvider.ts │ │ ├── logViewer │ │ │ ├── DiagnosticCodeActionProvider.ts │ │ │ ├── ProblemProcessor.ts │ │ │ ├── index.ts │ │ │ ├── logParser.ts │ │ │ └── sasDiagnostics.ts │ │ ├── notebook │ │ │ ├── Controller.ts │ │ │ ├── Serializer.ts │ │ │ ├── exporters │ │ │ │ ├── index.ts │ │ │ │ ├── templates │ │ │ │ │ ├── dark.css │ │ │ │ │ ├── default.html │ │ │ │ │ └── light.css │ │ │ │ ├── toHTML.ts │ │ │ │ └── toSAS.ts │ │ │ └── renderers │ │ │ │ ├── HTMLRenderer.ts │ │ │ │ └── LogRenderer.ts │ │ ├── profile.ts │ │ ├── tasks │ │ │ ├── SasTaskProvider.ts │ │ │ └── SasTasks.ts │ │ └── utils │ │ │ ├── SASCodeDocument.ts │ │ │ ├── SASCodeDocumentHelper.ts │ │ │ ├── deferred.ts │ │ │ ├── settings.ts │ │ │ └── throttle.ts │ ├── connection │ │ ├── index.ts │ │ ├── itc │ │ │ ├── CodeRunner.ts │ │ │ ├── ITCSASServerAdapter.ts │ │ │ ├── ItcLibraryAdapter.ts │ │ │ ├── LineParser.ts │ │ │ ├── const.ts │ │ │ ├── index.ts │ │ │ ├── script.ts │ │ │ ├── types.ts │ │ │ └── util.ts │ │ ├── rest │ │ │ ├── RestLibraryAdapter.ts │ │ │ ├── RestSASServerAdapter.ts │ │ │ ├── SASContentAdapter.ts │ │ │ ├── api │ │ │ │ ├── common.ts │ │ │ │ ├── compute.ts │ │ │ │ ├── configuration.ts │ │ │ │ ├── files.ts │ │ │ │ └── folders.ts │ │ │ ├── auth.ts │ │ │ ├── common.ts │ │ │ ├── context.ts │ │ │ ├── identities.ts │ │ │ ├── index.ts │ │ │ ├── job.ts │ │ │ ├── server.ts │ │ │ ├── session.ts │ │ │ └── util.ts │ │ ├── session.ts │ │ ├── ssh │ │ │ ├── auth.ts │ │ │ ├── const.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── studio │ │ │ └── index.ts │ │ └── util.ts │ ├── node │ │ └── extension.ts │ ├── panels │ │ ├── DataViewer.ts │ │ └── WebviewManager.ts │ ├── store │ │ ├── index.ts │ │ ├── log │ │ │ ├── actions.ts │ │ │ ├── initialState.ts │ │ │ ├── selectors.ts │ │ │ └── store.ts │ │ ├── middleware.ts │ │ ├── run │ │ │ ├── actions.ts │ │ │ ├── initialState.ts │ │ │ ├── selectors.ts │ │ │ └── store.ts │ │ └── selectors.ts │ └── webview │ │ ├── DataViewer.css │ │ ├── DataViewer.tsx │ │ ├── columnHeaderTemplate.ts │ │ ├── index.ts │ │ └── useDataViewer.ts ├── test │ ├── components │ │ ├── ContentNavigator │ │ │ ├── ContentDataProvider.test.ts │ │ │ ├── convert.test.ts │ │ │ └── utils.test.ts │ │ ├── LibraryNavigator │ │ │ ├── LibraryDataProvider.test.ts │ │ │ └── PaginatedResultSet.test.ts │ │ ├── logViewer │ │ │ ├── log.ts │ │ │ └── logParser.test.ts │ │ ├── notebook │ │ │ ├── exporter.test.ts │ │ │ └── serializer.test.ts │ │ ├── profile │ │ │ └── profile.test.ts │ │ └── util │ │ │ └── SASCodeDocument.test.ts │ ├── connection │ │ ├── itc │ │ │ ├── Coderunner.test.ts │ │ │ ├── ItcLibraryAdapter.test.ts │ │ │ ├── index.test.ts │ │ │ └── util.test.ts │ │ └── ssh │ │ │ ├── auth.test.ts │ │ │ └── index.test.ts │ ├── extension.test.ts │ ├── index.ts │ ├── languageServer │ │ └── formatter.test.ts │ ├── runTest.ts │ ├── store │ │ ├── log │ │ │ └── actions.test.ts │ │ └── run │ │ │ └── actions.test.ts │ └── utils.ts ├── testFixture │ ├── SampleCode.sas │ ├── SampleCode2.sas │ ├── TestFolder │ │ ├── SampleCode1.sas │ │ └── TestSubFolder │ │ │ └── SampleCode2.sas │ ├── formatter │ │ ├── expected.sas │ │ └── unformatted.sas │ ├── keyContent.txt │ ├── sasnb_export.sas │ ├── sasnb_export.sasnb │ ├── test.ipynb │ ├── test_ipynb.flw │ ├── test_multi.sasnb │ ├── test_multi_node.flw │ ├── test_multi_swimlane.flw │ ├── test_single.sasnb │ ├── test_single_node.flw │ └── test_single_swimlane.flw └── tsconfig.json ├── doc ├── profileExamples │ └── viya4.json └── scripts │ └── itc-connection-test.ps1 ├── eslint.config.mjs ├── icons ├── dark │ ├── DateDark.svg │ ├── characterTypeDark.svg │ ├── connectorNodeIndicator.svg │ ├── currencyDark.svg │ ├── deleteDark.svg │ ├── favoritesFolderDark.svg │ ├── flowStepDark.svg │ ├── folderDark.svg │ ├── importOtherDark.svg │ ├── jobTemplateDark.svg │ ├── libraryDark.svg │ ├── numericTypeDark.svg │ ├── pdfModelDark.svg │ ├── readOnlyLibraryDark.svg │ ├── sasDataSetDark.svg │ ├── sasFoldersDark.svg │ ├── sasIndicatorDark.svg │ ├── sasProgramFileDark.svg │ ├── serverDark.svg │ ├── submitSASCode.svg │ ├── tableHeaderCharacterTypeDark.svg │ ├── tableHeaderCurrencyDark.svg │ ├── tableHeaderDateDark.svg │ ├── tableHeaderNumericTypeDark.svg │ ├── taskContainerDark.svg │ ├── taskDark.svg │ ├── userWorkspaceDark.svg │ ├── vennDiagramAndDark.svg │ └── workLibraryDark.svg ├── light │ ├── DateLight.svg │ ├── characterTypeLight.svg │ ├── connectorNodeIndicator.svg │ ├── currencyLight.svg │ ├── deleteLight.svg │ ├── favoritesFolderLight.svg │ ├── flowStepLight.svg │ ├── folderLight.svg │ ├── importOtherLight.svg │ ├── jobTemplateLight.svg │ ├── libraryLight.svg │ ├── numericTypeLight.svg │ ├── pdfModelLight.svg │ ├── readOnlyLibraryLight.svg │ ├── sasDataSetLight.svg │ ├── sasFoldersLight.svg │ ├── sasIndicatorLight.svg │ ├── sasProgramFileLight.svg │ ├── serverLight.svg │ ├── submitSASCode.svg │ ├── tableHeaderCharacterTypeLight.svg │ ├── tableHeaderCurrencyLight.svg │ ├── tableHeaderDateLight.svg │ ├── tableHeaderNumericTypeLight.svg │ ├── taskContainerLight.svg │ ├── taskLight.svg │ ├── userWorkspaceLight.svg │ ├── vennDiagramAndLight.svg │ └── workLibraryLight.svg └── sas.png ├── l10n ├── bundle.l10n.de.json ├── bundle.l10n.es.json ├── bundle.l10n.fr.json ├── bundle.l10n.it.json ├── bundle.l10n.ja.json ├── bundle.l10n.ko.json ├── bundle.l10n.pl.json ├── bundle.l10n.pt-br.json ├── bundle.l10n.zh-cn.json └── bundle.l10n.zh-tw.json ├── language-configuration.json ├── package-lock.json ├── package.json ├── package.nls.de.json ├── package.nls.es.json ├── package.nls.fr.json ├── package.nls.it.json ├── package.nls.ja.json ├── package.nls.json ├── package.nls.ko.json ├── package.nls.pl.json ├── package.nls.pt-br.json ├── package.nls.zh-cn.json ├── package.nls.zh-tw.json ├── prettier.config.js ├── pull_request_template.md ├── server ├── data │ ├── DS2ContextPrompt.json │ ├── DS2Functions.json │ ├── DS2Keywords.json │ ├── HashPackageMethods.json │ ├── MacroDefinitionOptions.json │ ├── ODS_Tagsets.json │ ├── Procedures │ │ ├── DATA.json │ │ ├── DEFINE_EVENT.json │ │ ├── DEFINE_TAGSET.json │ │ ├── MACRO.json │ │ ├── ODS.json │ │ └── STATGRAPH.json │ ├── SASARMMacros.json │ ├── SASAutoVariables.json │ ├── SASAutocallMacros.json │ ├── SASCallRoutines.json │ ├── SASColorValues.json │ ├── SASContextPrompt.json │ ├── SASDataSetOptions.json │ ├── SASDataStepOptions.json │ ├── SASDataStepOptions2.json │ ├── SASDataStepStatements.json │ ├── SASFormats.json │ ├── SASFunctions.json │ ├── SASGlobalProcedureStatements.json │ ├── SASGlobalStatements.json │ ├── SASInformats.json │ ├── SASMacroFunctions.json │ ├── SASMacroStatements.json │ ├── SASProcedures.json │ ├── SQLKeywords.json │ ├── Statements │ │ ├── ABORT.json │ │ ├── ARRAY.json │ │ ├── ATTRIB.json │ │ ├── AXIS.json │ │ ├── CAS.json │ │ ├── CASLIB.json │ │ ├── ENDRSUBMIT.json │ │ ├── FILE.json │ │ ├── FILENAME.json │ │ ├── FOOTNOTE.json │ │ ├── FORMAT.json │ │ ├── GOPTIONS.json │ │ ├── INFILE.json │ │ ├── INFORMAT.json │ │ ├── KILLTASK.json │ │ ├── LEGEND.json │ │ ├── LIBNAME.json │ │ ├── LISTTASK.json │ │ ├── LOCK.json │ │ ├── NOTE.json │ │ ├── ODS.json │ │ ├── OPTIONS.json │ │ ├── PATTERN.json │ │ ├── RDISPLAY.json │ │ ├── RGET.json │ │ ├── RSUBMIT.json │ │ ├── RUN.json │ │ ├── SIGNOFF.json │ │ ├── SIGNON.json │ │ ├── SYMBOL.json │ │ ├── SYSTASK.json │ │ ├── TITLE.json │ │ ├── WAITFOR.json │ │ └── WHERE.json │ ├── StatisticsKeywords.json │ ├── StyleAttributes.json │ ├── StyleElements.json │ ├── StyleLocations.json │ └── WHERE.json ├── messagebundle.properties ├── messagebundle_ar.properties ├── messagebundle_cs.properties ├── messagebundle_da.properties ├── messagebundle_de.properties ├── messagebundle_el.properties ├── messagebundle_es.properties ├── messagebundle_fi.properties ├── messagebundle_fr.properties ├── messagebundle_he.properties ├── messagebundle_hr.properties ├── messagebundle_hu.properties ├── messagebundle_it.properties ├── messagebundle_iw.properties ├── messagebundle_ja.properties ├── messagebundle_ko.properties ├── messagebundle_nb.properties ├── messagebundle_nl.properties ├── messagebundle_no.properties ├── messagebundle_pl.properties ├── messagebundle_pt.properties ├── messagebundle_pt_BR.properties ├── messagebundle_ru.properties ├── messagebundle_sh.properties ├── messagebundle_sk.properties ├── messagebundle_sl.properties ├── messagebundle_sr.properties ├── messagebundle_sv.properties ├── messagebundle_th.properties ├── messagebundle_tr.properties ├── messagebundle_uk.properties ├── messagebundle_zh_CN.properties ├── messagebundle_zh_TW.properties ├── package-lock.json ├── package.json ├── pubsdata │ ├── Functions │ │ └── en │ │ │ ├── base.json │ │ │ └── macro.json │ ├── Procedures │ │ └── en │ │ │ ├── ACCELERATOR.json │ │ │ ├── ACECLUS.json │ │ │ ├── ADAPTIVEREG.json │ │ │ ├── ANOM.json │ │ │ ├── ANOVA.json │ │ │ ├── APPEND.json │ │ │ ├── ARIMA.json │ │ │ ├── ASSESS.json │ │ │ ├── ASTORE.json │ │ │ ├── AUTOREG.json │ │ │ ├── BCHOICE.json │ │ │ ├── BINNING.json │ │ │ ├── BNET.json │ │ │ ├── BOM.json │ │ │ ├── BOOLRULE.json │ │ │ ├── BOXPLOT.json │ │ │ ├── CALIS.json │ │ │ ├── CALLRFC.json │ │ │ ├── CANCORR.json │ │ │ ├── CANDISC.json │ │ │ ├── CAPABILITY.json │ │ │ ├── CARDINALITY.json │ │ │ ├── CAS.json │ │ │ ├── CASUTIL.json │ │ │ ├── CATALOG.json │ │ │ ├── CATMOD.json │ │ │ ├── CAUSALDISCOVERY.json │ │ │ ├── CAUSALMED.json │ │ │ ├── CCDM.json │ │ │ ├── CCOPULA.json │ │ │ ├── CIMPORT.json │ │ │ ├── CLP.json │ │ │ ├── CLUSTER.json │ │ │ ├── CNTSELECT.json │ │ │ ├── COMPARE.json │ │ │ ├── COMPILE.json │ │ │ ├── COMPUTAB.json │ │ │ ├── CONTENTS.json │ │ │ ├── COPY.json │ │ │ ├── CORR.json │ │ │ ├── CORRELATION.json │ │ │ ├── CORRESP.json │ │ │ ├── COUNTREG.json │ │ │ ├── CPANEL.json │ │ │ ├── CPM.json │ │ │ ├── CPORT.json │ │ │ ├── CQLIM.json │ │ │ ├── CSPADE.json │ │ │ ├── CSPATIALREG.json │ │ │ ├── CUSUM.json │ │ │ ├── DATA.json │ │ │ ├── DATAMETRICS.json │ │ │ ├── DATASETS.json │ │ │ ├── DATASOURCE.json │ │ │ ├── DATEKEYS.json │ │ │ ├── DBCSTAB.json │ │ │ ├── DELETE.json │ │ │ ├── DISCRIM.json │ │ │ ├── DISTANCE.json │ │ │ ├── DLMZEXPORT.json │ │ │ ├── DLMZSCORE.json │ │ │ ├── DLMZTRAIN.json │ │ │ ├── DMSRVADM.json │ │ │ ├── DMSRVDATASVC.json │ │ │ ├── DMSRVPROCESSSVC.json │ │ │ ├── DOCUMENT.json │ │ │ ├── DOWNLOAD.json │ │ │ ├── DQLOCLST.json │ │ │ ├── DQMATCH.json │ │ │ ├── DQSCHEME.json │ │ │ ├── DS2.json │ │ │ ├── DSTODS2.json │ │ │ ├── DTREE.json │ │ │ ├── EEL.json │ │ │ ├── ENTROPY.json │ │ │ ├── ESM.json │ │ │ ├── EXPAND.json │ │ │ ├── EXPORT.json │ │ │ ├── FACTEX.json │ │ │ ├── FACTMAC.json │ │ │ ├── FACTOR.json │ │ │ ├── FASTCLUS.json │ │ │ ├── FASTKNN.json │ │ │ ├── FCMP.json │ │ │ ├── FEDSQL.json │ │ │ ├── FISM.json │ │ │ ├── FITTEDQNET.json │ │ │ ├── FMM.json │ │ │ ├── FMTC2ITM.json │ │ │ ├── FONTREG.json │ │ │ ├── FOREST.json │ │ │ ├── FORMAT.json │ │ │ ├── FREQ.json │ │ │ ├── FREQTAB.json │ │ │ ├── G3D.json │ │ │ ├── G3GRID.json │ │ │ ├── GA.json │ │ │ ├── GAM.json │ │ │ ├── GAMMOD.json │ │ │ ├── GAMPL.json │ │ │ ├── GANNO.json │ │ │ ├── GANTT.json │ │ │ ├── GATEWAY.json │ │ │ ├── GBARLINE.json │ │ │ ├── GCHART.json │ │ │ ├── GCONTOUR.json │ │ │ ├── GDEVICE.json │ │ │ ├── GEE.json │ │ │ ├── GENMOD.json │ │ │ ├── GENSELECT.json │ │ │ ├── GEOCODE.json │ │ │ ├── GFONT.json │ │ │ ├── GINSIDE.json │ │ │ ├── GKPI.json │ │ │ ├── GLIMMIX.json │ │ │ ├── GLM.json │ │ │ ├── GLMMOD.json │ │ │ ├── GLMPOWER.json │ │ │ ├── GLMSELECT.json │ │ │ ├── GMAP.json │ │ │ ├── GMM.json │ │ │ ├── GOPTIONS.json │ │ │ ├── GPLOT.json │ │ │ ├── GPROJECT.json │ │ │ ├── GRADAR.json │ │ │ ├── GRADBOOST.json │ │ │ ├── GREDUCE.json │ │ │ ├── GREMOVE.json │ │ │ ├── GREPLAY.json │ │ │ ├── GROOVY.json │ │ │ ├── GSLIDE.json │ │ │ ├── GTILE.json │ │ │ ├── GVARCLUS.json │ │ │ ├── HADOOP.json │ │ │ ├── HMM.json │ │ │ ├── HP4SCORE.json │ │ │ ├── HPBIN.json │ │ │ ├── HPCANDISC.json │ │ │ ├── HPCDM.json │ │ │ ├── HPCLUS.json │ │ │ ├── HPCORR.json │ │ │ ├── HPCOUNTREG.json │ │ │ ├── HPDECIDE.json │ │ │ ├── HPDMDB.json │ │ │ ├── HPDS2.json │ │ │ ├── HPEXPORT.json │ │ │ ├── HPF.json │ │ │ ├── HPFARIMASPEC.json │ │ │ ├── HPFDIAGNOSE.json │ │ │ ├── HPFENGINE.json │ │ │ ├── HPFESMSPEC.json │ │ │ ├── HPFEVENTS.json │ │ │ ├── HPFEXMSPEC.json │ │ │ ├── HPFIDMSPEC.json │ │ │ ├── HPFMM.json │ │ │ ├── HPFOREST.json │ │ │ ├── HPFSELECT.json │ │ │ ├── HPFUCMSPEC.json │ │ │ ├── HPGENSELECT.json │ │ │ ├── HPIMPUTE.json │ │ │ ├── HPLMIXED.json │ │ │ ├── HPLOGISTIC.json │ │ │ ├── HPMIXED.json │ │ │ ├── HPNEURAL.json │ │ │ ├── HPNLMOD.json │ │ │ ├── HPPLS.json │ │ │ ├── HPPRINCOMP.json │ │ │ ├── HPQLIM.json │ │ │ ├── HPQUANTSELECT.json │ │ │ ├── HPREDUCE.json │ │ │ ├── HPREG.json │ │ │ ├── HPSAMPLE.json │ │ │ ├── HPSEVERITY.json │ │ │ ├── HPSPLIT.json │ │ │ ├── HPSUMMARY.json │ │ │ ├── HTTP.json │ │ │ ├── ICA.json │ │ │ ├── ICLIFETEST.json │ │ │ ├── ICPHREG.json │ │ │ ├── IML.json │ │ │ ├── IMPORT.json │ │ │ ├── INBREED.json │ │ │ ├── IRT.json │ │ │ ├── JAVAINFO.json │ │ │ ├── JSON.json │ │ │ ├── KCLUS.json │ │ │ ├── KDE.json │ │ │ ├── KRIGE2D.json │ │ │ ├── LATTICE.json │ │ │ ├── LIFEREG.json │ │ │ ├── LIFETEST.json │ │ │ ├── LMIXED.json │ │ │ ├── LOAN.json │ │ │ ├── LOCALEDATA.json │ │ │ ├── LOESS.json │ │ │ ├── LOGISTIC.json │ │ │ ├── LOGSELECT.json │ │ │ ├── LUA.json │ │ │ ├── MACONTROL.json │ │ │ ├── MAPIMPORT.json │ │ │ ├── MBANALYSIS.json │ │ │ ├── MBC.json │ │ │ ├── MCMC.json │ │ │ ├── MDC.json │ │ │ ├── MDS.json │ │ │ ├── MDSUMMARY.json │ │ │ ├── MEANS.json │ │ │ ├── MI.json │ │ │ ├── MIANALYZE.json │ │ │ ├── MIGRATE.json │ │ │ ├── MIXED.json │ │ │ ├── MODECLUS.json │ │ │ ├── MODEL.json │ │ │ ├── MODELMATRIX.json │ │ │ ├── MTLEARN.json │ │ │ ├── MULTTEST.json │ │ │ ├── MWPCA.json │ │ │ ├── NESTED.json │ │ │ ├── NETDRAW.json │ │ │ ├── NETWORK.json │ │ │ ├── NLIN.json │ │ │ ├── NLMIXED.json │ │ │ ├── NLMOD.json │ │ │ ├── NNET.json │ │ │ ├── NPAR1WAY.json │ │ │ ├── ODSLIST.json │ │ │ ├── ODSTABLE.json │ │ │ ├── ODSTEXT.json │ │ │ ├── OPTEX.json │ │ │ ├── OPTGRAPH.json │ │ │ ├── OPTIONS.json │ │ │ ├── OPTLOAD.json │ │ │ ├── OPTLP.json │ │ │ ├── OPTMILP.json │ │ │ ├── OPTMODEL.json │ │ │ ├── OPTNET.json │ │ │ ├── OPTNETWORK.json │ │ │ ├── OPTQP.json │ │ │ ├── OPTSAVE.json │ │ │ ├── ORTHOREG.json │ │ │ ├── PANEL.json │ │ │ ├── PARETO.json │ │ │ ├── PARTITION.json │ │ │ ├── PATHING.json │ │ │ ├── PCA.json │ │ │ ├── PDLREG.json │ │ │ ├── PHREG.json │ │ │ ├── PHSELECT.json │ │ │ ├── PLAN.json │ │ │ ├── PLM.json │ │ │ ├── PLS.json │ │ │ ├── PLSMOD.json │ │ │ ├── PM.json │ │ │ ├── POWER.json │ │ │ ├── PRINCOMP.json │ │ │ ├── PRINQUAL.json │ │ │ ├── PRINT.json │ │ │ ├── PRINTTO.json │ │ │ ├── PROBIT.json │ │ │ ├── PROTO.json │ │ │ ├── PRTDEF.json │ │ │ ├── PRTEXP.json │ │ │ ├── PWENCODE.json │ │ │ ├── PYTHON.json │ │ │ ├── QDEVICE.json │ │ │ ├── QKB.json │ │ │ ├── QLIM.json │ │ │ ├── QTRSELECT.json │ │ │ ├── QUANTLIFE.json │ │ │ ├── QUANTREG.json │ │ │ ├── QUANTSELECT.json │ │ │ ├── RANK.json │ │ │ ├── REG.json │ │ │ ├── REGISTERMODEL.json │ │ │ ├── REGISTRY.json │ │ │ ├── REGSELECT.json │ │ │ ├── RELIABILITY.json │ │ │ ├── REPORT.json │ │ │ ├── RISK.json │ │ │ ├── ROBUSTREG.json │ │ │ ├── RPCA.json │ │ │ ├── RSREG.json │ │ │ ├── S3.json │ │ │ ├── SCAPROC.json │ │ │ ├── SCORE.json │ │ │ ├── SCOREACCEL.json │ │ │ ├── SEMISUPLEARN.json │ │ │ ├── SEQDESIGN.json │ │ │ ├── SEQMC.json │ │ │ ├── SEQTEST.json │ │ │ ├── SEVERITY.json │ │ │ ├── SEVSELECT.json │ │ │ ├── SGMAP.json │ │ │ ├── SGPANEL.json │ │ │ ├── SGPIE.json │ │ │ ├── SGPLOT.json │ │ │ ├── SGRENDER.json │ │ │ ├── SGSCATTER.json │ │ │ ├── SIM2D.json │ │ │ ├── SIMILARITY.json │ │ │ ├── SIMLIN.json │ │ │ ├── SIMNORMAL.json │ │ │ ├── SORT.json │ │ │ ├── SPC.json │ │ │ ├── SPDO.json │ │ │ ├── SPECTRA.json │ │ │ ├── SPP.json │ │ │ ├── SQL.json │ │ │ ├── SQOOP.json │ │ │ ├── SSM.json │ │ │ ├── STANDARD.json │ │ │ ├── STATESPACE.json │ │ │ ├── STDIZE.json │ │ │ ├── STDRATE.json │ │ │ ├── STEPDISC.json │ │ │ ├── STREAM.json │ │ │ ├── SUMMARY.json │ │ │ ├── SURVEYFREQ.json │ │ │ ├── SURVEYIMPUTE.json │ │ │ ├── SURVEYLOGISTIC.json │ │ │ ├── SURVEYMEANS.json │ │ │ ├── SURVEYPHREG.json │ │ │ ├── SURVEYREG.json │ │ │ ├── SURVEYSELECT.json │ │ │ ├── SVDD.json │ │ │ ├── SVMACHINE.json │ │ │ ├── SYSLIN.json │ │ │ ├── TABULATE.json │ │ │ ├── TEMPLATE.json │ │ │ ├── TEXTCATEGORY.json │ │ │ ├── TEXTCATSCORE.json │ │ │ ├── TEXTCONCEPT.json │ │ │ ├── TEXTCONCEPTSCORE.json │ │ │ ├── TEXTMINE.json │ │ │ ├── TIMEID.json │ │ │ ├── TIMESERIES.json │ │ │ ├── TMODEL.json │ │ │ ├── TMSCORE.json │ │ │ ├── TPSPLINE.json │ │ │ ├── TRANSPOSE.json │ │ │ ├── TRANSREG.json │ │ │ ├── TRANTAB.json │ │ │ ├── TREE.json │ │ │ ├── TREESPLIT.json │ │ │ ├── TSCSREG.json │ │ │ ├── TSMODEL.json │ │ │ ├── TSNE.json │ │ │ ├── TSRECONCILE.json │ │ │ ├── TTEST.json │ │ │ ├── UCM.json │ │ │ ├── UNIVARIATE.json │ │ │ ├── UPLOAD.json │ │ │ ├── VARCLUS.json │ │ │ ├── VARCOMP.json │ │ │ ├── VARIMPUTE.json │ │ │ ├── VARIOGRAM.json │ │ │ ├── VARMAX.json │ │ │ ├── VARREDUCE.json │ │ │ ├── X11.json │ │ │ ├── X12.json │ │ │ └── XSL.json │ ├── Statements │ │ └── en │ │ │ ├── datastep.json │ │ │ ├── global.json │ │ │ ├── macro.json │ │ │ └── standalone.json │ └── procedures.json ├── src │ ├── browser │ │ ├── ResLoader.ts │ │ └── server.ts │ ├── node │ │ ├── ResLoader.ts │ │ └── server.ts │ ├── python │ │ ├── PyrightLanguageProvider.ts │ │ ├── browser │ │ │ ├── PyrightLanguageProviderBrowser.ts │ │ │ ├── fakeFileSystem.ts │ │ │ ├── typeShed.ts │ │ │ └── typeshed-loader │ │ │ │ └── index.js │ │ ├── node │ │ │ └── PyrightLanguageProviderNode.ts │ │ ├── sas │ │ │ └── sas2py.pyi │ │ └── utils.ts │ ├── sas │ │ ├── CodeZoneManager.ts │ │ ├── CompletionProvider.ts │ │ ├── FormatOnTypeProvider.ts │ │ ├── LanguageServiceProvider.ts │ │ ├── Lexer.ts │ │ ├── LexerEx.ts │ │ ├── Model.ts │ │ ├── SyntaxDataProvider.ts │ │ ├── SyntaxProvider.ts │ │ ├── formatter │ │ │ ├── index.ts │ │ │ ├── parser.ts │ │ │ └── printer.ts │ │ └── utils.ts │ └── server.ts ├── test │ └── embedded_lang │ │ └── embedded_lang.test.ts ├── testFixture │ └── embedded_lang │ │ ├── proc_lua.sas │ │ ├── proc_python.sas │ │ └── proc_sql.sas └── tsconfig.json ├── snippets └── proc-snippets.json ├── syntaxes ├── sas.tmLanguage.json └── sassql.tmLanguage.json ├── themes ├── dark_plus.json ├── dark_vs.json ├── hc_black.json ├── light_plus.json ├── light_vs.json ├── sas-dark-color-theme.json ├── sas-highcontrast-color-theme.json └── sas-light-color-theme.json ├── tools ├── build.mjs ├── check-copyright.mjs ├── locale.mjs └── preparePubsdata.js ├── tsconfig.json ├── webpack.config.js └── website ├── .gitignore ├── README.md ├── babel.config.js ├── docs ├── Configurations │ ├── Profiles │ │ ├── _category_.json │ │ ├── additional.md │ │ ├── index.md │ │ ├── sas9iom.md │ │ ├── sas9local.md │ │ ├── sas9ssh.md │ │ └── viya.md │ ├── _category_.json │ ├── index.md │ └── sasLog.md ├── Features │ ├── _category_.json │ ├── accessContent.md │ ├── accessLibraries.md │ ├── accessServer.md │ ├── errorsWarnings.md │ ├── index.md │ ├── running.md │ ├── runningTask.md │ ├── sasCodeEditing.md │ └── sasNotebook.md ├── README.md ├── faq.md ├── installation.md └── matrix.md ├── docusaurus.config.ts ├── package-lock.json ├── package.json ├── src └── css │ └── custom.css ├── static ├── .nojekyll └── images │ ├── NoActiveProfilesStatusBar.png │ ├── StatusBarProfileItem.png │ ├── featuresGlance.png │ ├── formatter.gif │ ├── libraries.png │ ├── quickFix.png │ ├── runCode2.png │ ├── sas.png │ ├── sasContent.png │ ├── sasNotebook.png │ ├── signatureHelp.gif │ ├── vsCodeChangeTheme2.gif │ ├── vsCodeFoldAndOutline.gif │ ├── vsCodeRegionFunction.gif │ ├── vsCodeSnippets.gif │ ├── vsCodeSyntaxAssist2.gif │ └── vsCodeTypeAhead.gif └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | client/src/session/rest/api/* linguist-generated=true 2 | server/data/* linguist-generated=true 3 | server/pubsdata/* linguist-generated=true 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug 3 | type: Bug 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Describe the bug 8 | placeholder: A clear and concise description of what the bug is 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: Steps to reproduce 14 | description: Detailed steps for us to consistently reproduce what you're seeing 15 | value: | 16 | 1. 17 | 2. 18 | - type: textarea 19 | attributes: 20 | label: SAS code (if applicable) 21 | description: If applicable, add the minimal SAS code that will reproduce the bug 22 | render: SAS 23 | - type: textarea 24 | attributes: 25 | label: Expected behavior 26 | placeholder: A clear and concise description of what you expected to happen 27 | - type: textarea 28 | attributes: 29 | label: Screenshots 30 | placeholder: If applicable, add screenshots to help explain your problem 31 | - type: input 32 | attributes: 33 | label: Client OS 34 | description: Where is your VS Code running on? 35 | placeholder: e.g. Windows 11 36 | - type: input 37 | attributes: 38 | label: Extension version 39 | placeholder: e.g. v1.13.0 40 | validations: 41 | required: true 42 | - type: input 43 | attributes: 44 | label: SAS version 45 | placeholder: e.g. Viya 2024.09 46 | - type: textarea 47 | attributes: 48 | label: Additional context 49 | placeholder: Add any other context about the problem here 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | type: Feature 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Is your feature request related to a problem? Please describe. 8 | placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when ... 9 | - type: textarea 10 | attributes: 11 | label: Describe the solution you'd like 12 | placeholder: A clear and concise description of what you want to happen. 13 | - type: textarea 14 | attributes: 15 | label: Describe alternatives you've considered 16 | placeholder: A clear and concise description of any alternative solutions or features you've considered. 17 | - type: textarea 18 | attributes: 19 | label: Additional context 20 | placeholder: Add any other context or screenshots about the feature request here. 21 | - type: input 22 | attributes: 23 | label: SAS version 24 | placeholder: e.g. Viya 4 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for npm 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | groups: 14 | dev: 15 | patterns: 16 | - "eslint*" 17 | - "@types*" 18 | - "ts-*" 19 | - package-ecosystem: "npm" 20 | directory: "/client" 21 | schedule: 22 | interval: "weekly" 23 | groups: 24 | aggrid: 25 | patterns: 26 | - "ag-grid*" 27 | react: 28 | patterns: 29 | - "react*" 30 | - "@types/react*" 31 | ignore: 32 | # @types/vscode should match the engine version we're using 33 | # should not be upgraded on its own 34 | - dependency-name: "@types/vscode" 35 | - package-ecosystem: "npm" 36 | directory: "/server" 37 | schedule: 38 | interval: "weekly" 39 | - package-ecosystem: "npm" 40 | directory: "/website" 41 | schedule: 42 | interval: "weekly" 43 | groups: 44 | docusaurus: 45 | patterns: 46 | - "@docusaurus/*" 47 | react: 48 | patterns: 49 | - "react*" 50 | - package-ecosystem: "github-actions" 51 | # Don't need to specify `/.github/workflows` for `directory`. Github knows it. 52 | directory: "/" 53 | schedule: 54 | interval: "weekly" 55 | -------------------------------------------------------------------------------- /.github/workflows/deploy-doc.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Doc Website to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: write 14 | 15 | defaults: 16 | run: 17 | shell: bash 18 | working-directory: ./website 19 | 20 | jobs: 21 | deploy: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version-file: ".nvmrc" 28 | cache: "npm" 29 | - run: npm ci 30 | 31 | - name: Build website 32 | run: npm run build 33 | 34 | # Popular action to deploy to GitHub Pages: 35 | # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus 36 | - name: Deploy to GitHub Pages 37 | uses: peaceiris/actions-gh-pages@v4 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | # Build output to publish to the `gh-pages` branch: 41 | publish_dir: ./website/build 42 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: Package and Publish VSIX 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | package: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version-file: ".nvmrc" 17 | cache: "npm" 18 | - run: npm ci 19 | - run: npx @vscode/vsce package 20 | - uses: actions/upload-artifact@v4 21 | with: 22 | path: ./*.vsix 23 | retention-days: 5 24 | 25 | - uses: johnnybenson/package-json-versioned-action@v1.0.9 26 | id: package-json 27 | with: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | - run: echo "vsixPath=sas-lsp-${{ steps.package-json.outputs.version }}.vsix" >> "$GITHUB_OUTPUT" 30 | id: vsixPath 31 | if: steps.package-json.outputs.has-updated == 'true' 32 | - run: npx @vscode/vsce publish -i ${{ steps.vsixPath.outputs.vsixPath }} 33 | if: steps.package-json.outputs.has-updated == 'true' 34 | env: 35 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 36 | - run: npx ovsx publish ${{ steps.vsixPath.outputs.vsixPath }} -p ${{ secrets.OVSX_PAT }} 37 | if: steps.package-json.outputs.has-updated == 'true' 38 | - run: | 39 | git tag -f v${{ steps.package-json.outputs.version }} 40 | git push -f origin v${{ steps.package-json.outputs.version }} 41 | if: steps.package-json.outputs.has-updated == 'true' 42 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | # This workflow perform basic checks for a pull request 2 | 3 | name: Pull Request Check 4 | 5 | on: pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version-file: ".nvmrc" 15 | cache: "npm" 16 | - run: npm ci 17 | - run: npm run format:check 18 | - run: npm run copyright:check 19 | - run: xvfb-run -a npm run test 20 | if: runner.os == 'Linux' 21 | - run: npx @vscode/vsce package 22 | - uses: actions/upload-artifact@v4 23 | with: 24 | path: ./*.vsix 25 | retention-days: 5 26 | -------------------------------------------------------------------------------- /.github/workflows/translations.yml: -------------------------------------------------------------------------------- 1 | name: Translation Check 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | check-translations: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version-file: ".nvmrc" 17 | cache: "npm" 18 | - run: npm ci 19 | - run: npm run locale --update-locales 20 | - run: | 21 | CHANGES=$(git diff --name-only) 22 | if [ -z "$CHANGES" ] 23 | then 24 | echo "No translations needed" 25 | else 26 | echo "The following files need translations:" 27 | echo $CHANGES 28 | exit 1 29 | fi 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test* 5 | *.vsix 6 | *.node 7 | *.qps-ploc.json 8 | l10n/bundle.l10n.json -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # generated files 2 | package-lock.json 3 | out/ 4 | dist/ 5 | .vscode-test/ 6 | website/build/ 7 | 8 | # git files 9 | .git/ 10 | 11 | # data files 12 | server/**/*.json 13 | 14 | # VSCode history 15 | .history/ 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | // for code formatting 8 | "esbenp.prettier-vscode", 9 | // for linting 10 | "dbaeumer.vscode-eslint", 11 | // for webpack 12 | "amodio.tsl-problem-matcher" 13 | ], 14 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 15 | "unwantedRecommendations": [] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.tabSize": 2, 4 | "editor.insertSpaces": true, 5 | "editor.formatOnSave": true, 6 | "typescript.tsc.autoDetect": "off", 7 | "typescript.preferences.quoteStyle": "single" 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "watch", 7 | "isBackground": true, 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "presentation": { 13 | "panel": "dedicated", 14 | "reveal": "never" 15 | }, 16 | "problemMatcher": { 17 | "source": "esbuild", 18 | "owner": "typescript", 19 | "fileLocation": "relative", 20 | "pattern": { 21 | "regexp": "^(.*)$", 22 | "file": 1 23 | }, 24 | "background": { 25 | "beginsPattern": { 26 | "regexp": "start" 27 | }, 28 | "endsPattern": { 29 | "regexp": "end" 30 | } 31 | } 32 | } 33 | }, 34 | { 35 | "type": "npm", 36 | "script": "compile-browser", 37 | "group": "build", 38 | "presentation": { 39 | "panel": "dedicated", 40 | "reveal": "never" 41 | }, 42 | "problemMatcher": ["$ts-webpack", "$tslint-webpack"] 43 | }, 44 | { 45 | "type": "npm", 46 | "script": "watch-browser", 47 | "isBackground": true, 48 | "group": "build", 49 | "presentation": { 50 | "panel": "dedicated", 51 | "reveal": "never" 52 | }, 53 | "problemMatcher": ["$ts-webpack-watch", "$tslint-webpack-watch"] 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .github/** 3 | **/src/** 4 | **/test/** 5 | **/testFixture/** 6 | **/out/** 7 | **/*.ts 8 | **/*.map 9 | **/node_modules/** 10 | **/website/** 11 | tools 12 | .gitignore 13 | .prettierignore 14 | prettier.config.js 15 | **/tsconfig.json 16 | contributing.md 17 | ContributorAgreement.txt 18 | .travis.yml 19 | .nvmrc 20 | eslint.config.mjs 21 | pull_request_template.md 22 | l10n/bundle.l10n.json -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | ## Support 2 | 3 | We use GitHub for tracking bugs and feature requests. Please submit a GitHub issue or pull request for support. 4 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sas-lsp-client", 3 | "description": "VS Code client for SAS language server", 4 | "author": "SAS Institute Inc.", 5 | "license": "Apache-2.0", 6 | "version": "0.0.1", 7 | "publisher": "SAS", 8 | "engines": { 9 | "vscode": "^1.89.0" 10 | }, 11 | "dependencies": { 12 | "ag-grid-community": "^33.3.2", 13 | "ag-grid-react": "^33.3.2", 14 | "axios": "^1.9.0", 15 | "highlight.js": "^11.11.1", 16 | "marked": "^15.0.12", 17 | "media-typer": "^1.1.0", 18 | "react": "^18.3.1", 19 | "react-dom": "^18.3.1", 20 | "ssh2": "^1.16.0", 21 | "uuid": "^11.1.0", 22 | "vscode-languageclient": "^10.0.0-next.2", 23 | "zustand": "^5.0.5" 24 | }, 25 | "devDependencies": { 26 | "@types/react": "^18.3.3", 27 | "@types/react-dom": "^18.3.0", 28 | "@types/ssh2": "^1.15.5", 29 | "@types/uuid": "^10.0.0", 30 | "@types/vscode": "1.82.0", 31 | "@types/vscode-notebook-renderer": "^1.72.3", 32 | "@vscode/test-electron": "^2.5.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/src/browser/extension.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { ExtensionContext, Uri, commands } from "vscode"; 4 | import { LanguageClientOptions } from "vscode-languageclient"; 5 | import { LanguageClient } from "vscode-languageclient/browser"; 6 | 7 | let client: LanguageClient; 8 | 9 | // this method is called when vs code is activated 10 | export function activate(context: ExtensionContext): void { 11 | commands.executeCommand("setContext", "SAS.hideRunMenuItem", true); 12 | 13 | // Options to control the language client 14 | const clientOptions: LanguageClientOptions = { 15 | // Register the server for sas file 16 | documentSelector: [{ language: "sas" }], 17 | }; 18 | 19 | client = createWorkerLanguageClient(context, clientOptions); 20 | 21 | client.start(); 22 | } 23 | 24 | function createWorkerLanguageClient( 25 | context: ExtensionContext, 26 | clientOptions: LanguageClientOptions, 27 | ) { 28 | // Create a worker. The worker main file implements the language server. 29 | const serverMain = Uri.joinPath( 30 | context.extensionUri, 31 | "server/dist/browser/server.js", 32 | ); 33 | const worker = new Worker(serverMain.toString()); 34 | 35 | // create the language server client to communicate with the server running in the worker 36 | return new LanguageClient( 37 | "sas-lsp", 38 | "SAS Language Server", 39 | worker, 40 | clientOptions, 41 | ); 42 | } 43 | 44 | export function deactivate(): Thenable | undefined { 45 | if (!client) { 46 | return undefined; 47 | } 48 | return client.stop(); 49 | } 50 | -------------------------------------------------------------------------------- /client/src/commands/authorize.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { authentication, commands, window } from "vscode"; 4 | 5 | import { SASAuthProvider } from "../components/AuthProvider"; 6 | import LibraryNavigator from "../components/LibraryNavigator"; 7 | import { ConnectionType } from "../components/profile"; 8 | import { profileConfig, switchProfile } from "./profile"; 9 | 10 | const finishAuthorization = (profileConfig): boolean => { 11 | commands.executeCommand("setContext", "SAS.authorizing", false); 12 | return profileConfig.getActiveProfile() !== ""; 13 | }; 14 | 15 | export const checkProfileAndAuthorize = 16 | (libraryNavigator: LibraryNavigator) => async (): Promise => { 17 | commands.executeCommand("setContext", "SAS.authorizing", true); 18 | if (profileConfig.getActiveProfile() === "") { 19 | await switchProfile(); 20 | } 21 | 22 | if (profileConfig.getActiveProfile() === "") { 23 | return finishAuthorization(profileConfig); 24 | } 25 | 26 | const activeProfile = profileConfig.getProfileByName( 27 | profileConfig.getActiveProfile(), 28 | ); 29 | 30 | switch (activeProfile.connectionType) { 31 | case ConnectionType.Rest: 32 | try { 33 | await authentication.getSession(SASAuthProvider.id, [], { 34 | createIfNone: true, 35 | }); 36 | } catch (error) { 37 | window.showErrorMessage(error.message); 38 | } 39 | 40 | return finishAuthorization(profileConfig); 41 | case ConnectionType.IOM: 42 | case ConnectionType.COM: 43 | commands.executeCommand("setContext", "SAS.librariesDisplayed", true); 44 | commands.executeCommand("setContext", "SAS.serverDisplayed", true); 45 | libraryNavigator.refresh(); 46 | return finishAuthorization(profileConfig); 47 | default: 48 | return finishAuthorization(profileConfig); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /client/src/commands/closeSession.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { window } from "vscode"; 4 | 5 | import { getSession } from "../connection"; 6 | import { Session } from "../connection/session"; 7 | 8 | export async function closeSession(message?: string): Promise { 9 | let session: Session; 10 | try { 11 | session = getSession(); 12 | } catch { 13 | // no session, do nothing 14 | } 15 | await session?.close(); 16 | if (message) { 17 | window.showInformationMessage(message); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/src/commands/new.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { 4 | NotebookCellData, 5 | NotebookCellKind, 6 | NotebookData, 7 | window, 8 | workspace, 9 | } from "vscode"; 10 | 11 | export async function newSASNotebook() { 12 | await window.showNotebookDocument( 13 | await workspace.openNotebookDocument( 14 | "sas-notebook", 15 | new NotebookData([ 16 | new NotebookCellData(NotebookCellKind.Code, "", "sas"), 17 | ]), 18 | ), 19 | ); 20 | } 21 | 22 | export async function newSASFile() { 23 | await window.showTextDocument( 24 | await workspace.openTextDocument({ language: "sas" }), 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /client/src/components/APIProvider.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { authentication } from "vscode"; 4 | 5 | import { profileConfig } from "../commands/profile"; 6 | import { SASAuthProvider } from "./AuthProvider"; 7 | import { ConnectionType } from "./profile"; 8 | 9 | /* only Rest APIs for now */ 10 | 11 | const apis = {}; 12 | 13 | export const registerAPI = (name: string, fn) => { 14 | apis[name] = fn; 15 | }; 16 | 17 | export const getRestAPIs = async (accessToken: string) => { 18 | const activeProfile = profileConfig.getProfileByName( 19 | profileConfig.getActiveProfile(), 20 | ); 21 | if (!activeProfile || activeProfile.connectionType !== ConnectionType.Rest) { 22 | return; 23 | } 24 | const session = await authentication.getSession(SASAuthProvider.id, [], { 25 | silent: true, 26 | }); 27 | if (session.accessToken !== accessToken) { 28 | return; 29 | } 30 | 31 | return apis; 32 | }; 33 | -------------------------------------------------------------------------------- /client/src/components/CAHelper.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { workspace } from "vscode"; 4 | 5 | import * as fs from "fs"; 6 | import * as https from "https"; 7 | import * as tls from "tls"; 8 | 9 | export const installCAs = () => { 10 | const certFiles: string[] = workspace 11 | .getConfiguration("SAS") 12 | .get("userProvidedCertificates"); 13 | if (!certFiles || !certFiles.length) { 14 | return; 15 | } 16 | 17 | const userCertificates = []; 18 | for (const filename of certFiles) { 19 | if (filename && filename.length) { 20 | try { 21 | userCertificates.push(fs.readFileSync(filename)); 22 | } catch (e) { 23 | console.log(`Failed to read user provided certificate`, e); 24 | } 25 | } 26 | } 27 | if (userCertificates.length > 0) { 28 | https.globalAgent.options.ca = 29 | tls.rootCertificates.concat(userCertificates); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /client/src/components/ContentNavigator/ContentAdapterFactory.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import ITCSASServerAdapter from "../../connection/itc/ITCSASServerAdapter"; 4 | import RestSASServerAdapter from "../../connection/rest/RestSASServerAdapter"; 5 | import SASContentAdapter from "../../connection/rest/SASContentAdapter"; 6 | import { ConnectionType } from "../profile"; 7 | import { 8 | ContentAdapter, 9 | ContentNavigatorConfig, 10 | ContentSourceType, 11 | } from "./types"; 12 | 13 | class ContentAdapterFactory { 14 | public create( 15 | connectionType: ConnectionType, 16 | sourceType: ContentNavigatorConfig["sourceType"], 17 | ): ContentAdapter { 18 | const key = `${connectionType}.${sourceType}`; 19 | switch (key) { 20 | case `${ConnectionType.Rest}.${ContentSourceType.SASServer}`: 21 | return new RestSASServerAdapter(); 22 | case `${ConnectionType.IOM}.${ContentSourceType.SASServer}`: 23 | case `${ConnectionType.COM}.${ContentSourceType.SASServer}`: 24 | return new ITCSASServerAdapter(); 25 | case `${ConnectionType.Rest}.${ContentSourceType.SASContent}`: 26 | default: 27 | return new SASContentAdapter(); 28 | } 29 | } 30 | } 31 | 32 | export default ContentAdapterFactory; 33 | -------------------------------------------------------------------------------- /client/src/components/ExtensionContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { ExtensionContext, Uri } from "vscode"; 4 | 5 | let context: ExtensionContext; 6 | 7 | export function setContext(c: ExtensionContext) { 8 | context = c; 9 | } 10 | 11 | /** 12 | * Set an extension context value. 13 | */ 14 | export async function setContextValue( 15 | key: string, 16 | value: string, 17 | ): Promise { 18 | context.workspaceState.update(key, value); 19 | } 20 | 21 | /** 22 | * Get an extension context value. 23 | */ 24 | export async function getContextValue( 25 | key: string, 26 | ): Promise { 27 | return context.workspaceState.get(key); 28 | } 29 | 30 | export function getGlobalStorageUri(): Uri { 31 | return context.globalStorageUri; 32 | } 33 | 34 | export function getSecretStorage(namespace: string) { 35 | const getNamespaceData = async (): Promise | undefined> => { 36 | const storedSessionData = await context.secrets.get(namespace); 37 | if (!storedSessionData) { 38 | return; 39 | } 40 | 41 | return JSON.parse(storedSessionData); 42 | }; 43 | const setNamespaceData = async (data: Record) => { 44 | await context.secrets.store(namespace, JSON.stringify(data)); 45 | }; 46 | 47 | const get = async (key: string): Promise => { 48 | const data = await getNamespaceData(); 49 | if (!data) { 50 | return; 51 | } 52 | 53 | return data[key]; 54 | }; 55 | 56 | const store = async (key: string, value: T) => { 57 | const data = await getNamespaceData(); 58 | const newData = { 59 | ...(data || {}), 60 | [key]: value, 61 | }; 62 | await context.secrets.store(namespace, JSON.stringify(newData)); 63 | }; 64 | 65 | return { setNamespaceData, getNamespaceData, get, store }; 66 | } 67 | -------------------------------------------------------------------------------- /client/src/components/LibraryNavigator/LibraryAdapterFactory.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import ItcLibraryAdapter from "../../connection/itc/ItcLibraryAdapter"; 4 | import RestLibraryAdapter from "../../connection/rest/RestLibraryAdapter"; 5 | import { ConnectionType } from "../profile"; 6 | import { LibraryAdapter } from "./types"; 7 | 8 | class LibraryAdapterFactory { 9 | public create(connectionType: ConnectionType): LibraryAdapter { 10 | switch (connectionType) { 11 | case ConnectionType.IOM: 12 | case ConnectionType.COM: 13 | return new ItcLibraryAdapter(); 14 | case ConnectionType.Rest: 15 | default: 16 | return new RestLibraryAdapter(); 17 | } 18 | } 19 | } 20 | 21 | export default LibraryAdapterFactory; 22 | -------------------------------------------------------------------------------- /client/src/components/LibraryNavigator/PaginatedResultSet.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | class PaginatedResultSet { 5 | private queryForData: (start: number, end: number) => Promise; 6 | 7 | constructor(queryForData: (start: number, end: number) => Promise) { 8 | this.queryForData = queryForData; 9 | } 10 | 11 | public async getData(start: number, end: number): Promise { 12 | return await this.queryForData(start, end); 13 | } 14 | } 15 | 16 | export default PaginatedResultSet; 17 | -------------------------------------------------------------------------------- /client/src/components/LibraryNavigator/const.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { l10n } from "vscode"; 4 | 5 | export const Messages = { 6 | TableDeletionError: l10n.t("Unable to delete table {tableName}."), 7 | ViewTableCommandTitle: l10n.t("View SAS Table"), 8 | }; 9 | 10 | export const Icons = { 11 | DataSet: { 12 | light: "icons/light/sasDataSetLight.svg", 13 | dark: "icons/dark/sasDataSetDark.svg", 14 | }, 15 | ReadOnlyLibrary: { 16 | light: "icons/light/readOnlyLibraryLight.svg", 17 | dark: "icons/dark/readOnlyLibraryDark.svg", 18 | }, 19 | Library: { 20 | light: "icons/light/libraryLight.svg", 21 | dark: "icons/dark/libraryDark.svg", 22 | }, 23 | WorkLibrary: { 24 | light: "icons/light/workLibraryLight.svg", 25 | dark: "icons/dark/workLibraryDark.svg", 26 | }, 27 | }; 28 | 29 | export const DefaultRecordLimit = 100; 30 | export const WorkLibraryId = "WORK"; 31 | -------------------------------------------------------------------------------- /client/src/components/LibraryNavigator/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { ColumnCollection } from "../../connection/rest/api/compute"; 4 | 5 | export const LibraryType = "library"; 6 | export const TableType = "table"; 7 | export type LibraryItemType = "library" | "table"; 8 | export interface LibraryItem { 9 | uid: string; 10 | id: string; 11 | name: string; 12 | type: LibraryItemType; 13 | library?: string; 14 | readOnly: boolean; 15 | } 16 | 17 | export interface TableRow { 18 | cells?: string[]; 19 | columns?: string[]; 20 | } 21 | 22 | export interface TableData { 23 | rows: TableRow[]; 24 | count: number; 25 | } 26 | 27 | export interface LibraryAdapter { 28 | connect(): Promise; 29 | deleteTable(item: LibraryItem): Promise; 30 | getColumns( 31 | item: LibraryItem, 32 | start: number, 33 | limit: number, 34 | ): Promise; 35 | getLibraries( 36 | start: number, 37 | limit: number, 38 | ): Promise<{ 39 | items: LibraryItem[]; 40 | count: number; 41 | }>; 42 | getRows(item: LibraryItem, start: number, limit: number): Promise; 43 | getRowsAsCSV( 44 | item: LibraryItem, 45 | start: number, 46 | limit: number, 47 | ): Promise; 48 | getTableRowCount( 49 | item: LibraryItem, 50 | ): Promise<{ rowCount: number; maxNumberOfRowsToRead: number }>; 51 | getTables( 52 | item: LibraryItem, 53 | start: number, 54 | limit: number, 55 | ): Promise<{ 56 | items: LibraryItem[]; 57 | count: number; 58 | }>; 59 | setup(): Promise; 60 | } 61 | -------------------------------------------------------------------------------- /client/src/components/ResultPanel/ResultPanelSubscriptionProvider.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Disposable, Uri, commands, window, workspace } from "vscode"; 4 | 5 | import { SubscriptionProvider } from "../SubscriptionProvider"; 6 | import { fetchHtmlFor } from "./ResultPanel"; 7 | 8 | interface ResultPanelContext { 9 | webview: string; 10 | panelId: string; 11 | } 12 | export class ResultPanelSubscriptionProvider implements SubscriptionProvider { 13 | getSubscriptions(): Disposable[] { 14 | return [ 15 | commands.registerCommand( 16 | "SAS.saveHTML", 17 | async (context: ResultPanelContext) => { 18 | const panelHtml = await fetchHtmlFor(context.panelId); 19 | 20 | if (panelHtml.length === 0) { 21 | return; 22 | } 23 | 24 | const uri = await window.showSaveDialog({ 25 | defaultUri: Uri.file(`results.html`), 26 | }); 27 | 28 | if (!uri) { 29 | return; 30 | } 31 | 32 | await workspace.fs.writeFile( 33 | uri, 34 | new TextEncoder().encode(panelHtml), 35 | ); 36 | }, 37 | ), 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/src/components/ResultPanel/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | export * from "./ResultPanel"; 4 | export * from "./ResultPanelSubscriptionProvider"; 5 | -------------------------------------------------------------------------------- /client/src/components/StatusBarItem.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { MarkdownString, StatusBarAlignment, Uri, l10n, window } from "vscode"; 4 | 5 | import { profileConfig } from "../commands/profile"; 6 | 7 | const statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); 8 | statusBarItem.command = "SAS.switchProfile"; 9 | 10 | export const getStatusBarItem = () => statusBarItem; 11 | 12 | export async function updateStatusBarItem(connected?: boolean) { 13 | const activeProfileName = profileConfig.getActiveProfile(); 14 | const activeProfile = profileConfig.getProfileByName(activeProfileName); 15 | if (!activeProfile) { 16 | resetStatusBarItem(); 17 | } else { 18 | const targetURL = profileConfig.remoteTarget(activeProfileName); 19 | const closeSessionUri = Uri.parse("command:SAS.close"); 20 | const tooltip = new MarkdownString( 21 | `#### ${l10n.t("SAS Profile")}\n\n${activeProfileName}\n\n${targetURL}${ 22 | connected 23 | ? `\n\n---\n\n[${l10n.t("Close Session")}](${closeSessionUri})` 24 | : "" 25 | }`, 26 | ); 27 | tooltip.isTrusted = true; 28 | 29 | statusBarItem.text = `${ 30 | connected ? "$(vm-active)" : "$(account)" 31 | } ${activeProfileName}`; 32 | statusBarItem.tooltip = tooltip; 33 | statusBarItem.show(); 34 | } 35 | } 36 | 37 | export function resetStatusBarItem(): void { 38 | statusBarItem.text = `$(debug-disconnect) ${l10n.t("No Profile")}`; 39 | statusBarItem.tooltip = l10n.t("No SAS Connection Profile"); 40 | statusBarItem.show(); 41 | } 42 | -------------------------------------------------------------------------------- /client/src/components/SubscriptionProvider.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Disposable } from "vscode"; 4 | 5 | export interface SubscriptionProvider { 6 | getSubscriptions(): Disposable[]; 7 | } 8 | -------------------------------------------------------------------------------- /client/src/components/notebook/exporters/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Uri, window, workspace } from "vscode"; 4 | import type { LanguageClient } from "vscode-languageclient/node"; 5 | 6 | import path from "path"; 7 | 8 | import { exportToHTML } from "./toHTML"; 9 | import { exportToSAS } from "./toSAS"; 10 | 11 | export const exportNotebook = async (client: LanguageClient) => { 12 | const notebook = window.activeNotebookEditor?.notebook; 13 | 14 | if (!notebook) { 15 | return; 16 | } 17 | 18 | const uri = await window.showSaveDialog({ 19 | filters: { SAS: ["sas"], HTML: ["html"] }, 20 | defaultUri: Uri.parse(path.basename(notebook.uri.path, ".sasnb")), 21 | }); 22 | 23 | if (!uri) { 24 | return; 25 | } 26 | 27 | const content = uri.path.endsWith(".html") 28 | ? await exportToHTML(notebook, client) 29 | : exportToSAS(notebook); 30 | 31 | workspace.fs.writeFile(uri, Buffer.from(content)); 32 | }; 33 | -------------------------------------------------------------------------------- /client/src/components/notebook/exporters/templates/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 47 | 48 | 49 | ${content} 50 | 51 | 52 | -------------------------------------------------------------------------------- /client/src/components/notebook/exporters/templates/light.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #3b3b3b; 3 | --border-color: #b0b7bb; 4 | --highlight-background-color: #818b981f; 5 | } 6 | .hljs { 7 | background: #fff; 8 | } 9 | 10 | /* Syntax highlighting */ 11 | .hljs-operator, 12 | .hljs-punctuation { 13 | color: #1b1d22; 14 | } 15 | 16 | .hljs-keyword { 17 | color: #3578c5; 18 | } 19 | 20 | .hljs-comment { 21 | color: #647f29; 22 | } 23 | 24 | .hljs-string { 25 | color: #8f4238; 26 | } 27 | 28 | .hljs-number { 29 | color: #3c8275; 30 | } 31 | 32 | .hljs-title { 33 | color: #224c7c; 34 | font-weight: bold; 35 | } 36 | 37 | .hljs-built_in { 38 | color: #aa0d91; 39 | } 40 | 41 | /* SAS Syntax */ 42 | .sas-syntax-sep { 43 | color: #1b1d22; 44 | } 45 | 46 | .sas-syntax-keyword, 47 | .sas-syntax-macro-keyword { 48 | color: #3578c5; 49 | } 50 | 51 | .sas-syntax-sec-keyword, 52 | .sas-syntax-proc-name { 53 | color: #224c7c; 54 | font-weight: bold; 55 | } 56 | 57 | .sas-syntax-comment, 58 | .sas-syntax-macro-comment { 59 | color: #647f29; 60 | } 61 | 62 | .sas-syntax-macro-ref, 63 | .sas-syntax-macro-sec-keyword { 64 | color: #1b1d22; 65 | font-weight: bold; 66 | } 67 | 68 | .sas-syntax-cards-data { 69 | color: #ad6531; 70 | } 71 | 72 | .sas-syntax-string { 73 | color: #8f4238; 74 | } 75 | 76 | .sas-syntax-date, 77 | .sas-syntax-time, 78 | .sas-syntax-dt, 79 | .sas-syntax-bitmask { 80 | color: #3c8275; 81 | font-weight: bold; 82 | } 83 | 84 | .sas-syntax-namelit { 85 | color: #8f4238; 86 | font-weight: bold; 87 | } 88 | 89 | .sas-syntax-hex, 90 | .sas-syntax-numeric { 91 | color: #3c8275; 92 | font-weight: bold; 93 | } 94 | 95 | .sas-syntax-format { 96 | color: #3c8275; 97 | } 98 | 99 | .log-line.sas-log-error { 100 | color: #ff0000; 101 | } 102 | 103 | .log-line.sas-log-warning { 104 | color: #8f5f00; 105 | } 106 | 107 | .log-line.sas-log-note { 108 | color: #0000ff; 109 | } 110 | -------------------------------------------------------------------------------- /client/src/components/notebook/exporters/toSAS.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { NotebookCell, NotebookDocument } from "vscode"; 4 | 5 | export const exportToSAS = (notebook: NotebookDocument) => 6 | notebook 7 | .getCells() 8 | .map((cell) => exportCell(cell) + "\n") 9 | .join("\n"); 10 | 11 | const exportCell = (cell: NotebookCell) => { 12 | const text = cell.document.getText(); 13 | switch (cell.document.languageId) { 14 | case "sas": 15 | return text; 16 | case "python": 17 | return wrapPython(text); 18 | case "sql": 19 | return wrapSQL(text); 20 | case "markdown": 21 | return `/*\n${text}\n*/`; 22 | } 23 | }; 24 | 25 | const wrapSQL = (code: string) => { 26 | if (!code.trimEnd().endsWith(";")) { 27 | code = `${code};`; 28 | } 29 | return `proc sql; 30 | ${code} 31 | quit;`; 32 | }; 33 | 34 | const wrapPython = (code: string) => `proc python; 35 | submit; 36 | ${code} 37 | endsubmit; 38 | run;`; 39 | -------------------------------------------------------------------------------- /client/src/components/notebook/renderers/HTMLRenderer.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import type { ActivationFunction } from "vscode-notebook-renderer"; 4 | 5 | /** 6 | * Replace the last occurrence of a substring 7 | */ 8 | function replaceLast( 9 | base: string, 10 | searchValue: string, 11 | replaceValue: string, 12 | ): string { 13 | const index = base.lastIndexOf(searchValue); 14 | if (index < 0) { 15 | return base; 16 | } 17 | return ( 18 | base.slice(0, index) + replaceValue + base.slice(index + searchValue.length) 19 | ); 20 | } 21 | 22 | export const activate: ActivationFunction = () => ({ 23 | renderOutputItem(data, element) { 24 | const html = data.text(); 25 | let shadow = element.shadowRoot; 26 | if (!shadow) { 27 | shadow = element.attachShadow({ mode: "open" }); 28 | } 29 | shadow.innerHTML = replaceLast( 30 | // it's not a whole webview, body not allowed 31 | html.replace("", 33 | "", 34 | ); 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /client/src/components/notebook/renderers/LogRenderer.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import type { ActivationFunction } from "vscode-notebook-renderer"; 4 | 5 | import type { LogLine } from "../../../connection"; 6 | 7 | const colorMap = { 8 | error: "var(--vscode-editorError-foreground)", 9 | warning: "var(--vscode-editorWarning-foreground)", 10 | note: "var(--vscode-editorInfo-foreground)", 11 | }; 12 | 13 | export const activate: ActivationFunction = () => ({ 14 | renderOutputItem(data, element) { 15 | const root = document.createElement("div"); 16 | root.style.whiteSpace = "pre"; 17 | root.style.fontFamily = "var(--vscode-editor-font-family)"; 18 | 19 | const logs: LogLine[] = data.json(); 20 | for (const line of logs) { 21 | const color = colorMap[line.type]; 22 | const div = document.createElement("div"); 23 | div.innerText = line.line; 24 | if (color) { 25 | div.style.color = color; 26 | } 27 | root.append(div); 28 | } 29 | element.replaceChildren(root); 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /client/src/components/utils/deferred.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export interface Deferred { 5 | promise: Promise; 6 | resolve: (value: T | PromiseLike) => void; 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | reject: (reason?: any) => void; 9 | } 10 | 11 | export function deferred() { 12 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 13 | const deferred = {} as Deferred; 14 | deferred.promise = new Promise((resolve, reject) => { 15 | deferred.resolve = resolve; 16 | deferred.reject = reject; 17 | }); 18 | return deferred; 19 | } 20 | -------------------------------------------------------------------------------- /client/src/components/utils/settings.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { workspace } from "vscode"; 4 | 5 | export function isOutputHtmlEnabled(): boolean { 6 | return !!workspace.getConfiguration("SAS").get("results.html.enabled"); 7 | } 8 | 9 | export function getHtmlStyle(): string { 10 | return workspace.getConfiguration("SAS").get("results.html.style"); 11 | } 12 | 13 | export function isSideResultEnabled(): string { 14 | return workspace.getConfiguration("SAS").get("results.sideBySide"); 15 | } 16 | 17 | export function isSinglePanelEnabled(): string { 18 | return workspace.getConfiguration("SAS").get("results.singlePanel"); 19 | } 20 | 21 | export function showLogOnExecutionStart(): boolean { 22 | return workspace.getConfiguration("SAS").get("log.showOnExecutionStart"); 23 | } 24 | 25 | export function showLogOnExecutionFinish(): boolean { 26 | return workspace.getConfiguration("SAS").get("log.showOnExecutionFinish"); 27 | } 28 | 29 | export function clearLogOnExecutionStart(): boolean { 30 | return workspace.getConfiguration("SAS").get("log.clearOnExecutionStart"); 31 | } 32 | 33 | export function isShowProblemsFromSASLogEnabled(): boolean { 34 | return workspace.getConfiguration("SAS").get("problems.log.enabled"); 35 | } 36 | 37 | export function includeLogInNotebookExport(): boolean { 38 | return workspace.getConfiguration("SAS").get("notebook.export.includeLog"); 39 | } 40 | -------------------------------------------------------------------------------- /client/src/components/utils/throttle.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Run tasks with limited concurrency 6 | * @param tasks array of tasks to run 7 | * @param limit limit of tasks that can run in parallel 8 | * @returns a promise like `Promise.all` 9 | */ 10 | export function throttle(tasks: Array<() => Promise>, limit: number) { 11 | const total = tasks.length; 12 | const results: T[] = Array(total); 13 | let count = 0; 14 | return new Promise((resolve, reject) => { 15 | function run() { 16 | const index = total - tasks.length; 17 | if (index === total) { 18 | if (count === total) { 19 | resolve(results); 20 | } 21 | return; 22 | } 23 | const task = tasks.shift(); 24 | task().then((result) => { 25 | results[index] = result; 26 | ++count; 27 | run(); 28 | }, reject); 29 | } 30 | Array(limit).fill(0).forEach(run); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /client/src/connection/itc/LineParser.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export class LineParser { 5 | protected processedLines: string[] = []; 6 | protected capturingLine: boolean = false; 7 | 8 | public constructor( 9 | protected startTag: string, 10 | protected endTag: string, 11 | protected returnNonProcessedLines: boolean, 12 | ) {} 13 | 14 | public processLine(line: string): string | undefined { 15 | if (line.includes(this.startTag) || this.capturingLine) { 16 | this.processedLines.push(line); 17 | this.capturingLine = true; 18 | if (line.includes(this.endTag)) { 19 | return this.processedLine(); 20 | } 21 | return; 22 | } 23 | 24 | return this.returnNonProcessedLines ? line : undefined; 25 | } 26 | 27 | protected processedLine(): string { 28 | this.capturingLine = false; 29 | const fullError = this.processedLines 30 | .join("") 31 | .replace(this.startTag, "") 32 | .replace(this.endTag, ""); 33 | this.processedLines = []; 34 | return fullError; 35 | } 36 | 37 | public isCapturingLine(): boolean { 38 | return this.capturingLine; 39 | } 40 | 41 | public reset() { 42 | this.capturingLine = false; 43 | this.processedLines = []; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/src/connection/itc/const.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export const ERROR_START_TAG = ""; 5 | export const ERROR_END_TAG = ""; 6 | 7 | export const WORK_DIR_START_TAG = ""; 8 | export const WORK_DIR_END_TAG = ""; 9 | -------------------------------------------------------------------------------- /client/src/connection/itc/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { BaseConfig } from ".."; 4 | 5 | export enum LineCodes { 6 | ResultsFetchedCode = "--vscode-sas-extension-results-fetched--", 7 | RunCancelledCode = "--vscode-sas-extension-run-cancelled--", 8 | RunEndCode = "--vscode-sas-extension-submit-end--", 9 | SessionCreatedCode = "--vscode-sas-extension-session-created--", 10 | LogLineType = "--vscode-sas-extension-log-line-type--", 11 | } 12 | 13 | export enum ScriptActions { 14 | CreateDirectory = `$runner.CreateDirectory($folderPath, $folderName)`, 15 | CreateFile = `$runner.CreateFile($folderPath, $fileName, $content)`, 16 | DeleteFile = `$runner.DeleteFile($filePath)`, 17 | FetchFileContent = `$runner.FetchFileContent($filePath, $outputFile)`, 18 | GetChildItems = `$runner.GetChildItems($path)`, 19 | RenameFile = `$runner.RenameFile($oldPath,$newPath,$newName)`, 20 | UpdateFile = `$runner.UpdateFile($filePath, $content)`, 21 | } 22 | 23 | export enum ITCProtocol { 24 | COM = 0, 25 | IOMBridge = 2, 26 | } 27 | 28 | /** 29 | * Configuration parameters for this connection provider 30 | */ 31 | export interface Config extends BaseConfig { 32 | host: string; 33 | port: number; 34 | username: string; 35 | protocol: ITCProtocol; 36 | interopLibraryFolderPath?: string; 37 | } 38 | 39 | export type PowershellResponse = { 40 | category: number; 41 | creationTimeStamp?: string; 42 | modifiedTimeStamp: string; 43 | name: string; 44 | parentFolderUri: string; 45 | size: number; 46 | uri: string; 47 | }; 48 | -------------------------------------------------------------------------------- /client/src/connection/itc/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export const decodeEntities = (msg: string): string => { 5 | // Some of our messages from the server contain html encoded 6 | // characters. This converts them back. 7 | const specialCharacters = { 8 | "'": "'", 9 | }; 10 | 11 | Object.entries(specialCharacters).map(([encodedHtml, text]) => { 12 | msg = msg.replace(encodedHtml, text); 13 | }); 14 | 15 | return msg; 16 | }; 17 | 18 | export const escapePowershellString = (unescapedString: string): string => 19 | unescapedString.replace(/(`|"|'|\$|\(|\)|%|{|}|\[|\])/g, "`$1"); 20 | 21 | export const getDirectorySeparator = (path: string): string => 22 | path.lastIndexOf("/") !== -1 ? "/" : "\\"; 23 | -------------------------------------------------------------------------------- /client/src/connection/rest/identities.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import axios from "axios"; 4 | 5 | export interface User { 6 | id: string; 7 | name: string; 8 | } 9 | 10 | // It's used by AuthProvider before a new authentication session returned 11 | // So has to pass access token mannually 12 | export async function getCurrentUser(options: { 13 | endpoint: string; 14 | accessToken: string; 15 | }) { 16 | return ( 17 | await axios 18 | .create({ 19 | baseURL: options.endpoint, 20 | headers: { 21 | Authorization: "Bearer " + options.accessToken, 22 | }, 23 | }) 24 | .get("/identities/users/@currentUser") 25 | ).data; 26 | } 27 | -------------------------------------------------------------------------------- /client/src/connection/session.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { ProgressLocation, l10n, window } from "vscode"; 4 | 5 | import type { OnLogFn, RunResult } from "."; 6 | 7 | export abstract class Session { 8 | protected _rejectRun: (reason?: unknown) => void | undefined; 9 | 10 | protected _onSessionLogFn: OnLogFn | undefined; 11 | public set onSessionLogFn(value: OnLogFn) { 12 | this._onSessionLogFn = value; 13 | } 14 | 15 | protected _onExecutionLogFn: OnLogFn | undefined; 16 | public set onExecutionLogFn(value: OnLogFn) { 17 | this._onExecutionLogFn = value; 18 | } 19 | 20 | async setup(silent?: boolean): Promise { 21 | if (silent) { 22 | return await this.establishConnection(); 23 | } 24 | 25 | await window.withProgress( 26 | { 27 | location: ProgressLocation.Notification, 28 | title: l10n.t("Connecting to SAS session..."), 29 | }, 30 | this.establishConnection, 31 | ); 32 | } 33 | 34 | protected abstract establishConnection(): Promise; 35 | 36 | run(code: string, ...args): Promise { 37 | return new Promise((resolve, reject) => { 38 | this._rejectRun = reject; 39 | this._run(code, ...args) 40 | .then(resolve, reject) 41 | .finally(() => (this._rejectRun = undefined)); 42 | }); 43 | } 44 | protected abstract _run(code: string, ...args): Promise; 45 | 46 | cancel?(): Promise; 47 | 48 | close(): Promise | void { 49 | if (this._rejectRun) { 50 | this._rejectRun({ message: l10n.t("The SAS session has closed.") }); 51 | this._rejectRun = undefined; 52 | } 53 | return this._close(); 54 | } 55 | protected abstract _close(): Promise | void; 56 | 57 | abstract sessionId?(): string | undefined; 58 | } 59 | -------------------------------------------------------------------------------- /client/src/connection/ssh/const.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const SECOND = 1000; 5 | const MINUTE = 60 * SECOND; 6 | export const KEEPALIVE_INTERVAL = 60 * SECOND; //How often (in milliseconds) to send SSH-level keepalive packets to the server. Set to 0 to disable. 7 | export const KEEPALIVE_UNANSWERED_THRESHOLD = 8 | (15 * MINUTE) / KEEPALIVE_INTERVAL; //How many consecutive, unanswered SSH-level keepalive packets that can be sent to the server before disconnection. 9 | export const WORK_DIR_START_TAG = "WORKDIR"; 10 | export const WORK_DIR_END_TAG = "WORKDIREND"; 11 | export const CONNECT_READY_TIMEOUT = 5 * MINUTE; //allow extra time due to possible prompting 12 | -------------------------------------------------------------------------------- /client/src/connection/ssh/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { BaseConfig } from ".."; 4 | 5 | export interface Config extends BaseConfig { 6 | host: string; 7 | username: string; 8 | saspath: string; 9 | port: number; 10 | privateKeyFilePath: string; 11 | } 12 | 13 | export enum LineCodes { 14 | ResultsFetchedCode = "--vscode-sas-extension-results-fetched--", 15 | RunCancelledCode = "--vscode-sas-extension-run-cancelled--", 16 | RunEndCode = "--vscode-sas-extension-submit-end--", 17 | LogLineType = "--vscode-sas-extension-log-line-type--", 18 | } 19 | -------------------------------------------------------------------------------- /client/src/connection/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export function extractOutputHtmlFileName( 5 | line: string, 6 | defaultValue: string, 7 | ): string { 8 | return ( 9 | line.match(/body="(.{8}-.{4}-.{4}-.{4}-.{12}).htm"/)?.[1] ?? defaultValue 10 | ); 11 | } 12 | 13 | export const extractTextBetweenTags = ( 14 | text: string, 15 | startTag: string = "", 16 | endTag: string = "", 17 | ): string => { 18 | return startTag && endTag 19 | ? text 20 | .slice(text.lastIndexOf(startTag), text.lastIndexOf(endTag)) 21 | .replace(startTag, "") 22 | .replace(endTag, "") 23 | .replace(/^\n/, "") 24 | .replace(/\n$/, "") 25 | : text; 26 | }; 27 | -------------------------------------------------------------------------------- /client/src/panels/WebviewManager.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Disposable, Uri, ViewColumn, WebviewPanel, window } from "vscode"; 4 | 5 | export class WebViewManager { 6 | public panels: Record = {}; 7 | 8 | public render(webview: WebView, uid: string) { 9 | if (this.panels[uid]) { 10 | this.panels[uid].display(); 11 | return; 12 | } 13 | 14 | const panel = window.createWebviewPanel("webView", uid, ViewColumn.One, { 15 | enableScripts: true, 16 | }); 17 | 18 | webview.onDispose = () => delete this.panels[uid]; 19 | this.panels[uid] = webview.withPanel(panel).render(); 20 | } 21 | } 22 | 23 | export abstract class WebView { 24 | protected panel: WebviewPanel; 25 | private _disposables: Disposable[] = []; 26 | private _onDispose: () => void; 27 | 28 | set onDispose(disposeCallback: () => void) { 29 | this._onDispose = disposeCallback; 30 | } 31 | 32 | abstract render(): WebView; 33 | abstract processMessage(event: Event): void; 34 | 35 | public withPanel(webviewPanel: WebviewPanel): WebView { 36 | this.panel = webviewPanel; 37 | this.panel.onDidDispose(() => this.dispose(), null, this._disposables); 38 | this.panel.webview.onDidReceiveMessage(this.processMessage.bind(this)); 39 | 40 | return this; 41 | } 42 | 43 | public dispose() { 44 | this.panel.dispose(); 45 | while (this._disposables.length) { 46 | const disposable = this._disposables.pop(); 47 | if (disposable) { 48 | disposable.dispose(); 49 | } 50 | } 51 | this._onDispose && this._onDispose(); 52 | } 53 | 54 | public display() { 55 | this.panel.reveal(ViewColumn.One); 56 | } 57 | 58 | public webviewUri(extensionUri: Uri, name: string): Uri { 59 | return this.panel.webview.asWebviewUri( 60 | Uri.joinPath(extensionUri, "client", "dist", "webview", name), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /client/src/store/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | export { useStore as useLogStore } from "./log/store"; 4 | export { useStore as useRunStore } from "./run/store"; 5 | -------------------------------------------------------------------------------- /client/src/store/log/actions.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { StateCreator } from "zustand/vanilla"; 4 | 5 | import type { Store } from "./store"; 6 | 7 | export interface LogActions { 8 | /** 9 | * Resets producedExecutionOutput to its default value. 10 | */ 11 | setProducedExecutionLogOutput: (boolean) => void; 12 | } 13 | 14 | export const createLogActions: StateCreator = ( 15 | set, 16 | ) => ({ 17 | setProducedExecutionLogOutput: (producedExecutionOutput: boolean) => 18 | set({ producedExecutionOutput }), 19 | }); 20 | -------------------------------------------------------------------------------- /client/src/store/log/initialState.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | //TODO: when session is refactored to flux pattern, types will move over 4 | 5 | export interface LogState { 6 | producedExecutionOutput: boolean; 7 | } 8 | 9 | export const initialState: LogState = { 10 | producedExecutionOutput: false, 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/store/log/selectors.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import type { LogState } from "./initialState"; 4 | 5 | const selectProducedExecutionOutput = (store: LogState) => { 6 | return store.producedExecutionOutput; 7 | }; 8 | 9 | export const logSelectors = { 10 | selectProducedExecutionOutput, 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/store/log/store.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { subscribeWithSelector } from "zustand/middleware"; 4 | import { StateCreator, createStore } from "zustand/vanilla"; 5 | 6 | import { LogActions, createLogActions } from "./actions"; 7 | import { LogState, initialState } from "./initialState"; 8 | 9 | export type Store = LogState & LogActions; 10 | 11 | const createdStore: StateCreator = (...parameters) => ({ 12 | ...initialState, 13 | ...createLogActions(...parameters), 14 | }); 15 | 16 | export const useStore = createStore()( 17 | subscribeWithSelector(createdStore), 18 | ); 19 | -------------------------------------------------------------------------------- /client/src/store/middleware.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Middleware that will log state changes to the console. Useful for debugging purposes. 6 | * @param config 7 | */ 8 | export const logger = (config) => (set, get, api) => { 9 | return config( 10 | (args) => { 11 | const newState = typeof args === "function" ? args(get()) : args; 12 | console.info(`State changed:`, newState); 13 | set(newState); 14 | }, 15 | get, 16 | api, 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /client/src/store/run/actions.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { StateCreator } from "zustand/vanilla"; 4 | 5 | import { type Store } from "./store"; 6 | 7 | export interface RunActions { 8 | setIsExecutingCode: (isExecuting: boolean, isUserExecuting?: boolean) => void; 9 | } 10 | 11 | export const createRunActions: StateCreator = ( 12 | set, 13 | ) => ({ 14 | setIsExecutingCode: (isExecutingCode, isUserExecuting = true) => { 15 | set({ 16 | isExecutingCode, 17 | isUserExecuting, 18 | }); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /client/src/store/run/initialState.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | export interface RunState { 4 | isExecutingCode: boolean; 5 | isUserExecuting: boolean; 6 | } 7 | 8 | export const initialState: RunState = { 9 | isExecutingCode: false, 10 | isUserExecuting: false, 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/store/run/selectors.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { RunState } from "./initialState"; 4 | 5 | const selectIsExecutingCode = (store: RunState) => { 6 | return store.isExecutingCode; 7 | }; 8 | 9 | export const runSelectors = { 10 | selectIsExecutingCode, 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/store/run/store.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { subscribeWithSelector } from "zustand/middleware"; 4 | import { StateCreator, createStore } from "zustand/vanilla"; 5 | 6 | import { RunActions, createRunActions } from "./actions"; 7 | import { RunState, initialState } from "./initialState"; 8 | 9 | export type Store = RunState & RunActions; 10 | 11 | const createdStore: StateCreator = (...parameters) => ({ 12 | ...initialState, 13 | ...createRunActions(...parameters), 14 | }); 15 | 16 | export const useStore = createStore()( 17 | subscribeWithSelector(createdStore), 18 | ); 19 | -------------------------------------------------------------------------------- /client/src/store/selectors.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { logSelectors } from "./log/selectors"; 5 | export { runSelectors } from "./run/selectors"; 6 | -------------------------------------------------------------------------------- /client/src/webview/DataViewer.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | } 4 | 5 | html, 6 | body { 7 | min-height: 100%; 8 | } 9 | 10 | html, 11 | body, 12 | .data-viewer { 13 | height: 100%; 14 | } 15 | 16 | .header-icon.float, 17 | .header-icon.date, 18 | .header-icon.time, 19 | .header-icon.date-time, 20 | .header-icon.currency, 21 | .header-icon.char { 22 | width: 16px; 23 | height: 22px; 24 | margin-right: 0.25rem; 25 | } 26 | 27 | /* DEFAULT ICONS */ 28 | 29 | .header-icon.float { 30 | background: url(../../../icons/light/tableHeaderNumericTypeLight.svg) center 31 | no-repeat; 32 | } 33 | 34 | .header-icon.time, 35 | .header-icon.date-time, 36 | .header-icon.date { 37 | background: url(../../../icons/light/tableHeaderDateLight.svg) center 38 | no-repeat; 39 | } 40 | 41 | .header-icon.currency { 42 | background: url(../../../icons/light/tableHeaderCurrencyLight.svg) center 43 | no-repeat; 44 | } 45 | 46 | .header-icon.char { 47 | background: url(../../../icons/light/tableHeaderCharacterTypeLight.svg) center 48 | no-repeat; 49 | } 50 | 51 | /* DARK MODE ICONS */ 52 | 53 | .vscode-dark .header-icon.float, 54 | .vscode-high-contrast .header-icon.float { 55 | background: url(../../../icons/dark/tableHeaderNumericTypeDark.svg) center 56 | no-repeat; 57 | } 58 | 59 | .header-icon.time, 60 | .header-icon.date-time, 61 | .vscode-dark .header-icon.date, 62 | .vscode-high-contrast .header-icon.date { 63 | background: url(../../../icons/dark/tableHeaderDateDark.svg) center no-repeat; 64 | } 65 | 66 | .vscode-dark .header-icon.currency, 67 | .vscode-high-contrast .header-icon.currency { 68 | background: url(../../../icons/dark/tableHeaderCurrencyDark.svg) center 69 | no-repeat; 70 | } 71 | 72 | .vscode-dark .header-icon.char, 73 | .vscode-high-contrast .header-icon.char { 74 | background: url(../../../icons/dark/tableHeaderCharacterTypeDark.svg) center 75 | no-repeat; 76 | } 77 | -------------------------------------------------------------------------------- /client/src/webview/DataViewer.tsx: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { useMemo } from "react"; 4 | import { createRoot } from "react-dom/client"; 5 | 6 | import { AgGridReact } from "ag-grid-react"; 7 | 8 | import "."; 9 | import useDataViewer from "./useDataViewer"; 10 | 11 | import "./DataViewer.css"; 12 | import "ag-grid-community/styles/ag-grid.css"; 13 | import "ag-grid-community/styles/ag-theme-alpine.css"; 14 | 15 | const gridStyles = { 16 | "--ag-borders": "none", 17 | "--ag-row-border-width": "0px", 18 | height: "100%", 19 | width: "100%", 20 | }; 21 | 22 | const DataViewer = () => { 23 | const { columns, onGridReady } = useDataViewer(); 24 | const theme = useMemo(() => { 25 | const themeKind = document 26 | .querySelector("[data-vscode-theme-kind]") 27 | .getAttribute("data-vscode-theme-kind"); 28 | 29 | switch (themeKind) { 30 | case "vscode-high-contrast-light": 31 | case "vscode-light": 32 | return "ag-theme-alpine"; 33 | case "vscode-high-contrast": 34 | case "vscode-dark": 35 | return "ag-theme-alpine-dark"; 36 | } 37 | }, []); 38 | 39 | if (columns.length === 0) { 40 | return null; 41 | } 42 | 43 | return ( 44 |
45 | 57 |
58 | ); 59 | }; 60 | 61 | const root = createRoot(document.querySelector(".data-viewer")); 62 | root.render(); 63 | -------------------------------------------------------------------------------- /client/src/webview/columnHeaderTemplate.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const getIconForColumnType = (type: string) => { 5 | switch (type.toLocaleLowerCase()) { 6 | case "float": 7 | case "num": 8 | return "float"; 9 | case "date": 10 | return "date"; 11 | case "time": 12 | return "time"; 13 | case "datetime": 14 | return "date-time"; 15 | case "currency": 16 | return "currency"; 17 | case "char": 18 | return "char"; 19 | default: 20 | return ""; 21 | } 22 | }; 23 | 24 | // Taken from https://www.ag-grid.com/react-data-grid/column-headers/#provided-component 25 | const columnHeaderTemplate = (columnType: string) => ` 26 | 38 | `; 39 | 40 | export default columnHeaderTemplate; 41 | -------------------------------------------------------------------------------- /client/src/webview/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // This declares a global type for DedicatedWorkerGlobalScope which 5 | // doesn't exist in the DOM library. This is necessary because there are conflicts 6 | // when including both DOM & WebWorker. See https://github.com/microsoft/TypeScript/issues/20595 7 | // for more information. 8 | declare global { 9 | type DedicatedWorkerGlobalScope = Worker; 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /client/test/components/ContentNavigator/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { getFileStatement } from "../../../src/components/ContentNavigator/utils"; 4 | 5 | describe("utils", async function () { 6 | it("getFileStatement - returns extensionless name + numeric suffix with no content", () => { 7 | expect(getFileStatement("testcsv.csv", "", "/path").value).to.equal( 8 | `filename \${1:fileref} filesrvc folderpath='/path' filename='testcsv.csv';\n`, 9 | ); 10 | }); 11 | 12 | it("getFileStatement - returns uppercase name + suffix with uppercase content", () => { 13 | expect( 14 | getFileStatement("testcsv.csv", "UPPER CASE CONTENT", "/path").value, 15 | ).to.equal( 16 | `FILENAME \${1:FILEREF} FILESRVC FOLDERPATH='/path' FILENAME='testcsv.csv';\n`, 17 | ); 18 | }); 19 | 20 | it("getFileStatement - returns encoded filename when filename contains quotes", () => { 21 | expect( 22 | getFileStatement("testcsv-'withquotes'.csv", "", "/path").value, 23 | ).to.equal( 24 | `filename \${1:fileref} filesrvc folderpath='/path' filename='testcsv-''withquotes''.csv';\n`, 25 | ); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /client/test/components/LibraryNavigator/PaginatedResultSet.test.ts: -------------------------------------------------------------------------------- 1 | import { AxiosHeaders, AxiosResponse } from "axios"; 2 | import { expect } from "chai"; 3 | 4 | import PaginatedResultSet from "../../../src/components/LibraryNavigator/PaginatedResultSet"; 5 | 6 | const axiosResponseDefaults = { 7 | status: 200, 8 | statusText: "OK", 9 | headers: {}, 10 | config: { 11 | headers: new AxiosHeaders(), 12 | }, 13 | }; 14 | 15 | describe("PaginatedResultSet", async function () { 16 | it("returns a basic response", async () => { 17 | const mockAxiosResponse: AxiosResponse = { 18 | ...axiosResponseDefaults, 19 | data: { 20 | limit: 0, 21 | count: 100, 22 | test: "yes", 23 | }, 24 | }; 25 | 26 | const paginatedResultSet = new PaginatedResultSet( 27 | async () => mockAxiosResponse, 28 | ); 29 | 30 | expect(await paginatedResultSet.getData(0, 100)).to.deep.equal( 31 | mockAxiosResponse, 32 | ); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /client/test/components/notebook/exporter.test.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import * as assert from "assert"; 4 | import * as sinon from "sinon"; 5 | 6 | import { getTestFixtureContent, getUri, openNotebookDoc } from "../../utils"; 7 | 8 | describe("export notebook", () => { 9 | const writeFileFn = sinon.spy(); 10 | const uri = vscode.Uri.file("/a"); 11 | before(async () => { 12 | await openNotebookDoc(getUri("sasnb_export.sasnb")); 13 | sinon.stub(vscode.window, "showSaveDialog").resolves(uri); 14 | sinon.stub(vscode.workspace, "fs").get(() => ({ 15 | writeFile: writeFileFn, 16 | })); 17 | }); 18 | 19 | after(() => { 20 | sinon.restore(); 21 | }); 22 | 23 | it("exports the sasnb to sas file correctly", async () => { 24 | await vscode.commands.executeCommand("SAS.notebook.export"); 25 | assert.strictEqual( 26 | writeFileFn.calledOnceWithExactly( 27 | uri, 28 | getTestFixtureContent("sasnb_export.sas"), 29 | ), 30 | true, 31 | ); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /client/test/components/notebook/serializer.test.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import * as assert from "assert"; 4 | 5 | import { NotebookSerializer } from "../../../src/components/notebook/Serializer"; 6 | 7 | const testCell = new vscode.NotebookCellData( 8 | vscode.NotebookCellKind.Code, 9 | "test", 10 | "sas", 11 | ); 12 | testCell.outputs = [ 13 | new vscode.NotebookCellOutput([ 14 | vscode.NotebookCellOutputItem.text("test", "application/test"), 15 | vscode.NotebookCellOutputItem.text("test1", "application/test1"), 16 | ]), 17 | ]; 18 | const testData = new vscode.NotebookData([ 19 | new vscode.NotebookCellData( 20 | vscode.NotebookCellKind.Markup, 21 | "test", 22 | "markdown", 23 | ), 24 | testCell, 25 | ]); 26 | const decoder = new TextDecoder(); 27 | 28 | describe("notebook serializer", () => { 29 | it("serialize/deserialize the data correctly", async () => { 30 | const serializer = new NotebookSerializer(); 31 | const serializedData = await serializer.serializeNotebook(testData); 32 | const newData = await serializer.serializeNotebook( 33 | await serializer.deserializeNotebook(serializedData), 34 | ); 35 | assert.equal( 36 | decoder.decode(newData), 37 | decoder.decode(serializedData), 38 | "The data don't match after serialize/deserialize", 39 | ); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /client/test/connection/itc/util.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { escapePowershellString } from "../../../src/connection/itc/util"; 4 | 5 | describe("ITC util test", () => { 6 | it("escapePowershellString - escapes powershell special characters", () => { 7 | const input = "P@$${}[]()\"'%{}rd"; 8 | const expectedOutput = "P@`$`$`{`}`[`]`(`)`\"`'`%`{`}rd"; 9 | 10 | expect(escapePowershellString(input)).to.equal(expectedOutput); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /client/test/index.ts: -------------------------------------------------------------------------------- 1 | import glob from "glob"; 2 | import Mocha from "mocha"; 3 | import path from "path"; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: "bdd", 9 | color: true, 10 | }); 11 | mocha.timeout(100000); 12 | 13 | const testsRoot = __dirname; 14 | const testFile = process.env.testFile; 15 | const pattern = testFile 16 | ? path.join(...testFile.replace(/\.ts$/, ".js").split(path.sep).slice(2)) 17 | : "**/**.test.js"; 18 | 19 | return new Promise((resolve, reject) => { 20 | glob(pattern, { cwd: testsRoot }, (err, files) => { 21 | if (err) { 22 | return reject(err); 23 | } 24 | 25 | // Add files to the test suite 26 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 27 | 28 | try { 29 | // Run the mocha test 30 | mocha.run((failures) => { 31 | if (failures > 0) { 32 | reject(new Error(`${failures} tests failed.`)); 33 | } else { 34 | resolve(); 35 | } 36 | }); 37 | } catch (err) { 38 | console.error(err); 39 | reject(err); 40 | } 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /client/test/languageServer/formatter.test.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import * as assert from "assert"; 4 | 5 | import { getTestFixtureContent, getUri, openDoc } from "../utils"; 6 | 7 | const expected = getTestFixtureContent("formatter/expected.sas").toString(); 8 | 9 | it("formats sas code well", async () => { 10 | const docUri = getUri("formatter/unformatted.sas"); 11 | await openDoc(docUri); 12 | // Executing the command `vscode.executeFormatDocumentProvider` to simulate triggering format 13 | const edits: vscode.TextEdit[] = await vscode.commands.executeCommand( 14 | "vscode.executeFormatDocumentProvider", 15 | docUri, 16 | {}, 17 | ); 18 | const edit = new vscode.WorkspaceEdit(); 19 | edit.set(docUri, edits); 20 | await vscode.workspace.applyEdit(edit); 21 | assert.strictEqual( 22 | vscode.window.activeTextEditor.document.getText().replace(/\r\n/g, "\n"), 23 | expected, 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /client/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import { runTests } from "@vscode/test-electron"; 2 | 3 | import * as path from "path"; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, "../../../"); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, "./index"); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ 17 | extensionDevelopmentPath, 18 | extensionTestsPath, 19 | launchArgs: ["--disable-extensions", "--locale en-US"], 20 | }); 21 | } catch (err) { 22 | console.error("Failed to run tests"); 23 | process.exit(1); 24 | } 25 | } 26 | 27 | main(); 28 | -------------------------------------------------------------------------------- /client/test/store/log/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { useLogStore } from "../../../src/store"; 4 | import { initialState } from "../../../src/store/log/initialState"; 5 | 6 | describe("log actions", () => { 7 | beforeEach(() => { 8 | useLogStore.setState(initialState); 9 | }); 10 | 11 | it("unsetProducedExecutionOutput", () => { 12 | useLogStore.getState().setProducedExecutionLogOutput(false); 13 | 14 | expect(useLogStore.getState().producedExecutionOutput).to.be.false; 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /client/test/store/run/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { useRunStore } from "../../../src/store"; 4 | import { RunState, initialState } from "../../../src/store/run/initialState"; 5 | 6 | describe("run actions", () => { 7 | beforeEach(() => { 8 | useRunStore.setState(initialState); 9 | }); 10 | 11 | it("setIsExecutingCode", () => { 12 | const { setIsExecutingCode } = useRunStore.getState(); 13 | const expectedState: RunState = { 14 | isExecutingCode: true, 15 | isUserExecuting: true, 16 | }; 17 | 18 | setIsExecutingCode(true); 19 | 20 | expect(useRunStore.getState()).to.deep.include(expectedState); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /client/test/utils.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import { assert } from "chai"; 4 | import { readFileSync } from "fs"; 5 | import * as path from "path"; 6 | 7 | export function getUri(name: string): vscode.Uri { 8 | return vscode.Uri.file(path.resolve(__dirname, "../../testFixture", name)); 9 | } 10 | 11 | export async function openDoc(docUri: vscode.Uri): Promise { 12 | const doc = await vscode.workspace.openTextDocument(docUri); 13 | await vscode.window.showTextDocument(doc); 14 | await sleep(5000); // Wait for server activation 15 | } 16 | 17 | export async function openNotebookDoc(docUri: vscode.Uri): Promise { 18 | const doc = await vscode.workspace.openNotebookDocument(docUri); 19 | await vscode.window.showNotebookDocument(doc); 20 | } 21 | 22 | async function sleep(ms: number) { 23 | return new Promise((resolve) => setTimeout(resolve, ms)); 24 | } 25 | 26 | export function getTestFixtureContent(name: string): Buffer { 27 | return readFileSync(path.resolve(__dirname, "../../testFixture", name)); 28 | } 29 | 30 | export const assertThrowsAsync = async (fn, expectedMsg?: string) => { 31 | try { 32 | await fn(); 33 | } catch (err) { 34 | if (expectedMsg) { 35 | const typedError: Error = err; 36 | assert.include( 37 | typedError.message, 38 | expectedMsg, 39 | "Expected Message not found in returned error message", 40 | ); 41 | } 42 | return; 43 | } 44 | assert.fail("function was expected to throw, but did not"); 45 | }; 46 | -------------------------------------------------------------------------------- /client/testFixture/SampleCode2.sas: -------------------------------------------------------------------------------- 1 | proc iml; 2 | FF = FINV(0.05/32,2,29); 3 | print FF; 4 | quit; 5 | -------------------------------------------------------------------------------- /client/testFixture/TestFolder/SampleCode1.sas: -------------------------------------------------------------------------------- 1 | proc print data=sashelp.air; 2 | -------------------------------------------------------------------------------- /client/testFixture/TestFolder/TestSubFolder/SampleCode2.sas: -------------------------------------------------------------------------------- 1 | proc print data=sashelp.aarfm; 2 | -------------------------------------------------------------------------------- /client/testFixture/keyContent.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEICC/c7CdkJ77uAbVTbySFZoQAE06MMn/DnAMwn47IEj0oAoGCCqGSM49 3 | AwEHoUQDQgAEMZhIWuZ1G0yl6ZBYjlhPz2KBV0QG/W4rtMiTimfflFh6v4QOmFqi 4 | 3V1bpb+aMETqJzZDNfuH66549cLzRw1rzw== 5 | -----END EC PRIVATE KEY----- -------------------------------------------------------------------------------- /client/testFixture/sasnb_export.sas: -------------------------------------------------------------------------------- 1 | /* 2 | # Notebook to SAS Test 3 | */ 4 | 5 | /* 6 | ## Python Code 7 | 8 | This is some Python code 9 | */ 10 | 11 | /* 12 | This is a separate note in **Markdown** format. 13 | */ 14 | 15 | proc python; 16 | submit; 17 | a, b = 4, 2 18 | print("Result: ", a*10 + b) 19 | endsubmit; 20 | run; 21 | 22 | /* 23 | ## SAS Code 24 | */ 25 | 26 | data work.prdsale; 27 | set sashelp.PRDSALE; 28 | run; 29 | 30 | proc means data=work.prdsale; 31 | run; 32 | 33 | /* 34 | ## SQL Code 35 | */ 36 | 37 | proc sql; 38 | CREATE TABLE WORK.QUERY_PRDSALE AS 39 | SELECT 40 | (t1.COUNTRY) LABEL='Country' FORMAT=$CHAR10., 41 | (SUM(t1.ACTUAL)) FORMAT=DOLLAR12.2 LENGTH=8 AS SUM_ACTUAL 42 | FROM 43 | WORK.PRDSALE t1 44 | GROUP BY 45 | t1.COUNTRY; 46 | quit; 47 | 48 | /* 49 | A last comment in Markdown at the end of the document 50 | */ 51 | 52 | /* 53 | 54 | */ 55 | -------------------------------------------------------------------------------- /client/testFixture/test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "26fe0d41-b4da-4902-845a-98e5c0c8e72a", 6 | "metadata": {}, 7 | "source": [ 8 | "# Python test" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "78855cba-1ec7-4e26-be74-23665ca2f4ee", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import pandas as pd\n", 19 | "\n", 20 | "df = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})\n", 21 | "df.head()" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "id": "7906e55d-829b-41da-8536-5dba90d2dddd", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "483eda5c-6fb4-4193-99c1-7ea7ab9ad338", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "df.info()" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "id": "3f652f5e-c531-4e9d-9349-9ae1f52773ad", 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [] 49 | } 50 | ], 51 | "metadata": { 52 | "kernelspec": { 53 | "display_name": "Python 3 (ipykernel)", 54 | "language": "python", 55 | "name": "python3" 56 | }, 57 | "language_info": { 58 | "codemirror_mode": { 59 | "name": "ipython", 60 | "version": 3 61 | }, 62 | "file_extension": ".py", 63 | "mimetype": "text/x-python", 64 | "name": "python", 65 | "nbconvert_exporter": "python", 66 | "pygments_lexer": "ipython3", 67 | "version": "3.9.7" 68 | } 69 | }, 70 | "nbformat": 4, 71 | "nbformat_minor": 5 72 | } 73 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "jsx": "react-jsx", 5 | "lib": ["ES2019", "DOM"], 6 | "module": "commonjs", 7 | "outDir": "out", 8 | "rootDir": ".", 9 | "sourceMap": true, 10 | "target": "es2019" 11 | }, 12 | "include": ["src", "test"], 13 | "exclude": ["node_modules", ".vscode-test"] 14 | } 15 | -------------------------------------------------------------------------------- /doc/profileExamples/viya4.json: -------------------------------------------------------------------------------- 1 | { 2 | "SAS.log.showOnExecutionFinish": true, 3 | "SAS.log.showOnExecutionStart": false, 4 | "SAS.connectionProfiles": { 5 | "activeProfile": "viyaServer", 6 | "profiles": { 7 | "viya4": { 8 | "endpoint": "https://example-endpoint.com", 9 | "connectionType": "rest", 10 | "sasOptions": ["NONEWS", "ECHOAUTO"], 11 | "autoExec": [ 12 | { 13 | "type": "line", 14 | "line": "ods graphics / imagemap;" 15 | }, 16 | { 17 | "type": "file", 18 | "filePath": "/my/local/autoexec.sas" 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /icons/dark/DateDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/dark/characterTypeDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/dark/connectorNodeIndicator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/dark/currencyDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /icons/dark/deleteDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/dark/favoritesFolderDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/dark/flowStepDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/dark/folderDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /icons/dark/importOtherDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /icons/dark/jobTemplateDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /icons/dark/libraryDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/dark/numericTypeDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /icons/dark/pdfModelDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/dark/readOnlyLibraryDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /icons/dark/sasDataSetDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/dark/sasFoldersDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/dark/sasIndicatorDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /icons/dark/sasProgramFileDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /icons/dark/serverDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/dark/submitSASCode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /icons/dark/tableHeaderCharacterTypeDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /icons/dark/tableHeaderCurrencyDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /icons/dark/tableHeaderDateDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/dark/tableHeaderNumericTypeDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /icons/dark/taskContainerDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/dark/taskDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /icons/dark/userWorkspaceDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /icons/dark/vennDiagramAndDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /icons/dark/workLibraryDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/light/DateLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/light/characterTypeLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/light/connectorNodeIndicator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/light/currencyLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /icons/light/deleteLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/light/favoritesFolderLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/light/flowStepLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/light/folderLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /icons/light/importOtherLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /icons/light/jobTemplateLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /icons/light/libraryLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/light/numericTypeLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/light/pdfModelLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/light/readOnlyLibraryLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /icons/light/sasDataSetLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/light/sasFoldersLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/light/sasIndicatorLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /icons/light/sasProgramFileLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /icons/light/serverLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/light/submitSASCode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /icons/light/tableHeaderCharacterTypeLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /icons/light/tableHeaderCurrencyLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /icons/light/tableHeaderDateLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /icons/light/tableHeaderNumericTypeLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /icons/light/taskContainerLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/light/taskLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /icons/light/userWorkspaceLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /icons/light/vennDiagramAndLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /icons/light/workLibraryLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /icons/sas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/icons/sas.png -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "blockComment": ["/*", "*/"] 4 | }, 5 | "brackets": [ 6 | ["{", "}"], 7 | ["[", "]"], 8 | ["(", ")"] 9 | ], 10 | "autoClosingPairs": [ 11 | { "open": "{", "close": "}" }, 12 | { "open": "[", "close": "]" }, 13 | { "open": "(", "close": ")" }, 14 | { "open": "'", "close": "'", "notIn": ["string", "comment"] }, 15 | { "open": "\"", "close": "\"", "notIn": ["string"] }, 16 | { "open": "/**", "close": " */", "notIn": ["string"] } 17 | ], 18 | "autoCloseBefore": ";:.,=}])> \n\t", 19 | "surroundingPairs": [ 20 | ["{", "}"], 21 | ["[", "]"], 22 | ["(", ")"], 23 | ["'", "'"], 24 | ["\"", "\""] 25 | ], 26 | "indentationRules": { 27 | "increaseIndentPattern": { 28 | "pattern": "(data|proc|%macro)\\b[^;]*;(\\s|/\\*.*\\*/|\\*[^;]*;)*$", 29 | "flags": "i" 30 | }, 31 | "decreaseIndentPattern": { 32 | "pattern": "(;|^\\s*)(\\s|/\\*.*\\*/|\\*[^;]*;)*(run|quit|%mend)(\\s|/\\*.*\\*/|\\*[^;]*;)*;$", 33 | "flags": "i" 34 | } 35 | }, 36 | "wordPattern": "(-?\\d*\\.\\d\\w*)|(\\%?[^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)" 37 | } 38 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["@trivago/prettier-plugin-sort-imports"], 3 | importOrder: [ 4 | "^vscode(.*)$", 5 | "^react(.*)$", 6 | "^@(.*)$", 7 | "", 8 | "^[./].*(?length] \n \nSpecifies the length of variables in variable-list."}},{"Name":"TRANSCODE=","Type":"V","Values":{"@Value1":"YES","@Value2":"NO"},"ToolTips":{"@ToolTip1":"Specifies that character variables can be transcoded.","@ToolTip2":"Specifies that character variables are not transcoded."},"Help":{"#cdata":"Specifies whether character variables can be transcoded."}}]}} -------------------------------------------------------------------------------- /server/data/Statements/ENDRSUBMIT.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":{"Name":"CANCEL","Type":"S","Help":{"#cdata":"Terminates the block of statements without executing the statements. This option \nis useful in an interactive line mode session if you see an error in a previously \nentered statement, and you want to cancel the step."}}}} -------------------------------------------------------------------------------- /server/data/Statements/FORMAT.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":{"Name":"DEFAULT=","Type":"V","Help":{"#cdata":"Syntax: DEFAULT=default-format] \n \nSpecifies a temporary default format for displaying the values of variables that are \nnot listed in the FORMAT statement. These default formats apply only to the current DATA \nstep; they are not permanently associated with variables in the output data set.\n\nA DEFAULT= format specification applies to\n\n o variables that are not named in a FORMAT or ATTRIB statement \n o variables that are not permanently associated with a format within a SAS data set \n o variables that are not written with the explicit use of a format. \n\nDefault: If you omit DEFAULT=, SAS uses BESTw. as the default numeric format and $w. \nas the default character format. \n\nRestriction: Use this option only in a DATA step. \n\nTip: A DEFAULT= specification can occur anywhere in a FORMAT statement. It can specify \neither a numeric default, a character default, or both."}}}} -------------------------------------------------------------------------------- /server/data/Statements/INFORMAT.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":{"Name":"DEFAULT=","Type":"V","Help":{"#cdata":"Syntax: DEFAULT= default-informat] \n \nSpecifies a temporary default informat for reading values of the variables that \nare listed in the INFORMAT statement. If no variable is specified, then the DEFAULT= \ninformat specification applies a temporary default informat for reading values of all \nthe variables of that type included in the DATA step. Numeric informats are applied \nto numeric variables, and character informats are applied to character variables. \nThese default informats apply only to the current DATA step. \n\nA DEFAULT= informat specification applies to\n\n o variables that are not named in an INFORMAT or ATTRIB statement \n o variables that are not permanently associated with an informat within a SAS data set \n o variables that are not read with an explicit informat in the current DATA step.\n\nDefault: If you omit DEFAULT=, SAS uses w.d as the default numeric informat and $w. \nas the default character informat. \n\nRestriction: Use this argument only in a DATA step. \n\nTip: A DEFAULT= specification can occur anywhere in an INFORMAT statement. It can \nspecify either a numeric default, a character default, or both."}}}} -------------------------------------------------------------------------------- /server/data/Statements/KILLTASK.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":{"Name":"_ALL_","Type":"S","Help":{"#cdata":"Terminates all active asynchronous tasks."}}}} -------------------------------------------------------------------------------- /server/data/Statements/LISTTASK.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":{"Name":"_ALL_","Type":"S","Help":{"#cdata":"Provides status information about all current tasks."}}}} -------------------------------------------------------------------------------- /server/data/Statements/LOCK.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":[{"Name":"LIST|QUERY|SHOW","Type":"S","Help":{"#cdata":"Writes to the SAS log whether you have an exclusive lock on the specified SAS file.\n\nTip:\nThis option provides more information in a client/server environment. To use this option in \na client/server environment, see the LOCK statement in the SAS/SHARE User's Guide."}},{"Name":"CLEAR","Type":"S","Help":{"#cdata":"Releases a lock on the specified SAS file that was acquired using the LOCK statement in your SAS session."}},{"Name":"NOMSG","Type":"S","Help":{"#cdata":"Specifies that warning and error messages are not written to the SAS log. NOMSG does not suppress notes \n that tell you that a lock is successful or that a lock is cleared. \n \nInteractions:\nTo suppress warnings and errors, you must specify NOMSG for each execution of the LOCK statement."}}]}} -------------------------------------------------------------------------------- /server/data/Statements/RDISPLAY.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":{"Name":"CONNECTREMOTE=|CREMOTE=|PROCESS=|REMOTE=","Type":"S","Help":{"#cdata":"[Syntax: CONNECTREMOTE=server-ID] \n \nCreates a Log window to display the lines from the log and an Output window to list \nthe output generated from the execution of the statements within an asynchronous \nRSUBMIT block."}}}} -------------------------------------------------------------------------------- /server/data/Statements/RGET.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":{"Name":"CONNECTREMOTE=|CREMOTE=|PROCESS=|REMOTE=","Type":"S","Help":{"#cdata":"[Syntax: CONNECTREMOTE=server-ID] \n \nSpecifies the name of the server session that generated the spooled log and output \nto be retrieved. If only one session is active, server-ID can be omitted. If multiple \nserver sessions are active and the option is omitted, the spooled log and output \nstatements from the most recently accessed server session are retrieved and merged \ninto the client Log and Output windows. You can find out which server session is the \ncurrent session by examining the value that is assigned to the CONNECTREMOTE system \noption."}}}} -------------------------------------------------------------------------------- /server/data/Statements/RUN.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":{"Name":"CANCEL","Type":"S","Help":{"#cdata":"Terminates the current step without executing it. SAS prints a message that indicates that \nthe step was not executed. \n\nCAUTION:\nThe CANCEL option does not prevent execution of a DATA step that contains a DATALINES or DATALINES4 statement. \n\nCAUTION:\nThe CANCEL option has no effect when you use the KILL option with PROC DATASETS."}}}} -------------------------------------------------------------------------------- /server/data/Statements/SIGNOFF.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":[{"Name":"_ALL_","Type":"S","Help":{"#cdata":"Ends all client/server connections sequentially, starting with the first server session that you\nsigned on to."}},{"Name":"CMACVAR=|MACVAR=","Type":"V","Values":{"@Value1":"0","@Value2":"1","@Value3":"2"},"ToolTips":{"@ToolTip1":"Indicates that the sign-off is successful.","@ToolTip2":"Indicates that the sign-off failed","@ToolTip3":"Indicates that the sign-off was unnecessary."},"Help":{"#cdata":"Specifies the macro variable to associate with the sign-off."}},{"Name":"CONNECTREMOTE=|CREMOTE=|PROCESS=|REMOTE=","Type":"S|V","Help":{"#cdata":"[Syntax: CONNECTREMOTE=server-ID] \n \nSpecifies the name of the server session that you want to sign off from.\nIf only one session is active, server-ID can be omitted."}},{"Name":"CSCRIPT=|SCRIPT=","Type":"V","Help":{"#cdata":"[Syntax: CSCRIPT=file-specification] \n \nSpecifies the script file to be used during sign-off."}},{"Name":"NOCSCRIPT|NOSCRIPT","Type":"S","Help":{"#cdata":"Specifies that no SAS/CONNECT script should be used for sign-off. NOCSCRIPT is useful \nif you have defined the RLINK fileref but do not want to use it during sign-off. \n\nNOCSCRIPT accelerates sign-off and saves memory resources."}}]}} -------------------------------------------------------------------------------- /server/data/Statements/WAITFOR.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":[{"Name":"_ANY_","Type":"S","Help":{"#cdata":"Suspends execution of the current SAS session until one of the specified tasks \nfinishes executing. The default setting is _ANY_, which means that as soon as \none of the specified task(s) completes executing, the WAITFOR statement will \nfinish executing."}},{"Name":"_ALL_","Type":"S","Help":{"#cdata":"Suspends execution of the current SAS session until all of the specified tasks \nfinishes executing."}},{"Name":"TIMEOUT=","Type":"V","Help":{"#cdata":"[Syntax: TIMEOUT=seconds] \n \nSpecifies the maximum number of seconds that WAITFOR should suspend the current \nSAS session. If you do not specify the TIMEOUT option, WAITFOR will suspend \nexecution of the SAS session indefinitely."}}]}} -------------------------------------------------------------------------------- /server/data/Statements/WHERE.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":[{"Name":"AND","Type":"V","Help":{"#cdata":"[logical operator between where-expressions]--Logical AND"}},{"Name":"NOT","Type":"V","Help":{"#cdata":"[logical operator between where-expressions]--Logical NOT"}},{"Name":"OR","Type":"V","Help":{"#cdata":"[logical operator between where-expressions]--Logical OR"}},{"Name":"CONTAINS","Type":"V","Help":{"#cdata":"[mnemonic]--For a character string.]"}},{"Name":"IS NULL","Type":"V","Help":{"#cdata":"[mnemonic]--For missing values.]"}},{"Name":"IS MISSING","Type":"V","Help":{"#cdata":"[mnemonic]--For missing values.]"}},{"Name":"LIKE","Type":"V","Help":{"#cdata":"[mnemonic]--For match patterns.]"}},{"Name":"BETWEEN-AND","Type":"V","Help":{"#cdata":"[mnemonic]--For an inclusive range."}},{"Name":"SAME-AND","Type":"V","Help":{"#cdata":"[mnemonic]\n \nAdds clauses to an existing WHERE statement without retyping original one."}}]}} -------------------------------------------------------------------------------- /server/data/WHERE.json: -------------------------------------------------------------------------------- 1 | {"Keywords":{"Keyword":[{"Name":"AND","Type":"V","Help":{"#cdata":"[logical operator between where-expressions]--Logical AND"}},{"Name":"NOT","Type":"V","Help":{"#cdata":"[logical operator between where-expressions]--Logical NOT"}},{"Name":"OR","Type":"V","Help":{"#cdata":"[logical operator between where-expressions]--Logical OR"}},{"Name":"CONTAINS","Type":"V","Help":{"#cdata":"[mnemonic]--For a character string.]"}},{"Name":"IS NULL","Type":"V","Help":{"#cdata":"[mnemonic]--For missing values.]"}},{"Name":"IS MISSING","Type":"V","Help":{"#cdata":"[mnemonic]--For missing values.]"}},{"Name":"LIKE","Type":"V","Help":{"#cdata":"[mnemonic]--For match patterns.]"}},{"Name":"BETWEEN-AND","Type":"V","Help":{"#cdata":"[mnemonic]--For an inclusive range."}},{"Name":"SAME-AND","Type":"V","Help":{"#cdata":"[mnemonic]\n \nAdds clauses to an existing WHERE statement without retyping original one."}}]}} -------------------------------------------------------------------------------- /server/messagebundle_zh_CN.properties: -------------------------------------------------------------------------------- 1 | ce_ac_keyword_txt=关键字: 2 | ce_ac_alias_txt=别名: 3 | ce_ac_context_txt=上下文: 4 | ce_ac_search_txt=搜索: 5 | ce_ac_product_documentation_txt=产品文档 6 | ce_ac_samples_and_sas_notes_txt=示例和 SAS 注释 7 | ce_ac_papers_txt=文章 8 | ce_ac_statement.fmt={0} 语句 9 | ce_ac_option.fmt={0} 选项 10 | ce_ac_proc.fmt=PROC {0} 11 | ce_ac_macro_def_name_txt=宏定义名称 12 | ce_ac_user_macro_var_txt=用户定义的宏变量 13 | ce_ac_colors_txt=颜色 14 | ce_ac_formats_txt=输出格式 15 | ce_ac_informats_txt=输入格式 16 | ce_ac_macro_functions_txt=宏函数 17 | ce_ac_sas_functions_txt=SAS 函数 18 | ce_ac_statistics_keywords_txt=统计量关键字 19 | ce_ac_style_elements_txt=样式元素 20 | ce_ac_style_attributes_txt=样式特性 21 | ce_ac_style_locations_txt=样式位置 22 | ce_ac_style_option_txt=样式选项 23 | ce_ac_global_statement_txt=全局语句 24 | ce_ac_global_statements_txt=全局语句 25 | ce_ac_option_values_txt=选项值 26 | ce_ac_statement_options_txt=语句选项 27 | ce_ac_procedures_txt=过程 28 | ce_ac_procedure_options_txt=过程选项 29 | ce_ac_procedure_statements_txt=过程语句 30 | ce_ac_procedure_definition_txt=过程定义 31 | ce_ac_data_step_statements_txt=DATA 步语句 32 | ce_ac_data_step_txt=DATA 步 33 | ce_ac_data_step_option_value_txt=DATA 步选项值 34 | ce_ac_definition_options_txt=定义选项 35 | ce_ac_options_txt=选项 36 | ce_ac_data_set_option_txt=数据集选项 37 | ce_ac_data_set_options_txt=数据集选项 38 | ce_ac_data_set_option_value_txt=数据集选项值 39 | ce_ac_data_set_option_values_txt=数据集选项值 40 | ce_ac_data_step_definition_options_txt=DATA 步定义选项 41 | ce_ac_password_options_txt=密码选项 42 | ce_ac_password_option_values_txt=密码选项值 43 | ce_ac_macro_definition_txt=宏定义 44 | ce_ac_macro_definition_option_txt=宏定义选项 45 | ce_ac_macro_definition_options_txt=宏定义选项 46 | ce_ac_macro_statement_txt=宏语句 47 | ce_ac_macro_statement_option_txt=宏语句选项 48 | ce_ac_macro_statements_txt=宏语句 49 | ce_ac_call_routine_txt=CALL 例程 50 | ce_ac_call_routines_txt=CALL 例程 51 | ce_ac_tagset_names_txt=标记集名称 52 | ce_ac_ods_txt=ODS 53 | ce_ac_ods_statements_txt=ODS 语句 54 | ce_ac_ods_markup_txt=ODS 标记 55 | ce_ac_macro_variables_txt=宏变量 56 | ce_ac_snippet_abbr_txt=代码段缩写 57 | ce_ac_sas_function_doc_txt=文档: 58 | -------------------------------------------------------------------------------- /server/messagebundle_zh_TW.properties: -------------------------------------------------------------------------------- 1 | ce_ac_keyword_txt=關鍵字: 2 | ce_ac_alias_txt=別名: 3 | ce_ac_context_txt=內容: 4 | ce_ac_search_txt=搜尋: 5 | ce_ac_product_documentation_txt=產品文件 6 | ce_ac_samples_and_sas_notes_txt=範例和 SAS 附註 7 | ce_ac_papers_txt=論文 8 | ce_ac_statement.fmt={0} 陳述式 9 | ce_ac_option.fmt={0} 選項 10 | ce_ac_proc.fmt=PROC {0} 11 | ce_ac_macro_def_name_txt=巨集定義名稱 12 | ce_ac_user_macro_var_txt=使用者定義巨集變數 13 | ce_ac_colors_txt=顏色 14 | ce_ac_formats_txt=輸出格式 15 | ce_ac_informats_txt=輸入格式 16 | ce_ac_macro_functions_txt=巨集函數 17 | ce_ac_sas_functions_txt=SAS 函數 18 | ce_ac_statistics_keywords_txt=統計關鍵字 19 | ce_ac_style_elements_txt=樣式元素 20 | ce_ac_style_attributes_txt=樣式特性 21 | ce_ac_style_locations_txt=樣式位置 22 | ce_ac_style_option_txt=樣式選項 23 | ce_ac_global_statement_txt=全域陳述式 24 | ce_ac_global_statements_txt=全域陳述式 25 | ce_ac_option_values_txt=選項值 26 | ce_ac_statement_options_txt=陳述式選項 27 | ce_ac_procedures_txt=程序 28 | ce_ac_procedure_options_txt=程序選項 29 | ce_ac_procedure_statements_txt=程序陳述式 30 | ce_ac_procedure_definition_txt=程序定義 31 | ce_ac_data_step_statements_txt=DATA Step 陳述式 32 | ce_ac_data_step_txt=DATA Step 33 | ce_ac_data_step_option_value_txt=DATA Step 選項值 34 | ce_ac_definition_options_txt=定義選項 35 | ce_ac_options_txt=選項 36 | ce_ac_data_set_option_txt=資料集選項 37 | ce_ac_data_set_options_txt=資料集選項 38 | ce_ac_data_set_option_value_txt=資料集選項值 39 | ce_ac_data_set_option_values_txt=資料集選項值 40 | ce_ac_data_step_definition_options_txt=Data Step 定義選項 41 | ce_ac_password_options_txt=密碼選項 42 | ce_ac_password_option_values_txt=密碼選項值 43 | ce_ac_macro_definition_txt=巨集定義 44 | ce_ac_macro_definition_option_txt=巨集定義選項 45 | ce_ac_macro_definition_options_txt=巨集定義選項 46 | ce_ac_macro_statement_txt=巨集陳述式 47 | ce_ac_macro_statement_option_txt=巨集陳述式選項 48 | ce_ac_macro_statements_txt=巨集陳述式 49 | ce_ac_call_routine_txt=CALL 常式 50 | ce_ac_call_routines_txt=CALL 常式 51 | ce_ac_tagset_names_txt=標記集名稱 52 | ce_ac_ods_txt=ODS 53 | ce_ac_ods_statements_txt=ODS 陳述式 54 | ce_ac_ods_markup_txt=ODS 標記 55 | ce_ac_macro_variables_txt=巨集變數 56 | ce_ac_snippet_abbr_txt=片段縮寫 57 | ce_ac_sas_function_doc_txt=文件: 58 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sas-lsp-server", 3 | "description": "SAS language server", 4 | "version": "0.0.1", 5 | "author": "SAS Institute Inc.", 6 | "license": "Apache-2.0", 7 | "engines": { 8 | "node": "*" 9 | }, 10 | "dependencies": { 11 | "pyright-internal-node": "^1.1.367", 12 | "pyright-internal-browser": "^1.1.367", 13 | "vscode-languageserver": "^10.0.0-next.2", 14 | "vscode-languageserver-textdocument": "1.0.11" 15 | } 16 | } -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/DMSRVADM.json: -------------------------------------------------------------------------------- 1 | {"name":"DMSRVADM","statements":[{"name":"PROC DMSRVADM","description":"The DMSRVADM procedure creates a status information data set.","help":"PROC DMSRVADM \n\t< HOST=host-name>\n\t\t\n\t\t\n\t\t\n\t\t; ","arguments":[{"name":"HOST=","optional":true,"description":"identifies the host of the DataFlux Data Management Server. The host name can be a variable or a literal string. The string or variable value is the URL of the server.","help":"HOST=*host-name*","type":"value","supportSiteTargetFragment":"n0xhcnu76z1erdn1tsizntx24sf0"},{"name":"OUT=","optional":true,"description":"specifies the storage location of the job status data set.","help":"OUT=*output-data-set*","type":"dataSet","supportSiteTargetFragment":"p0z4losuqhmgzin13lwd3uxg3j5k"},{"name":"PASSWORD=","optional":true,"description":"authenticates the user according to the registry in the DataFlux Data Management Server. The password can be plain text or encoded in SAS.","help":"PASSWORD=*password*","type":"value","supportSiteTargetFragment":"p0eaathi43g0ifn1fm18n00lzc81"},{"name":"PORT=","optional":true,"description":"identifies the port through which the host communicates with the DataFlux Data Management Server.","help":"PORT=*port-number*","type":"value","supportSiteTargetFragment":"n0p186jmx0hllin1upb8qcnpxrzq"},{"name":"USERID=","optional":true,"description":"authenticates the user according to the registry in the DataFlux Data Management Server.","help":"USERID=*userid*","type":"value","supportSiteTargetFragment":"p12n1bwy7bt1i5n148h5u9t9u07x"}],"supportSiteTargetFile":"p0mt69jlik8hirn1hlwn9neyitiw.htm"}],"supportSiteInformation":{"docsetId":"dqclref","docsetVersion":"v_007","docsetTargetFile":"n15nsfihbjr1h6n1d76y8l1ua7n7.htm"}} -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/DQLOCLST.json: -------------------------------------------------------------------------------- 1 | {"name":"DQLOCLST","statements":[{"name":"PROC DQLOCLST","description":"The DQLOCLST procedure creates a data set that includes the list of locales in the Quality Knowledge Base that is named by the system option DQSETUPLOC=.","help":"PROC DQLOCLST <OUT=*output-data-set*>;run;","arguments":[{"name":"OUT=","optional":true,"description":"identifies the name of the output data set. The procedure follows standard SAS data set naming conventions.","help":"OUT=*output-data-set*","type":"dataSet","supportSiteTargetFragment":"p1c5wplqzdqgi4n1n2vwqd1t61iv"}],"supportSiteTargetFile":"p1n4qtlmblekw3n16wia2mvyh7zm.htm"}],"supportSiteInformation":{"docsetId":"dqclref","docsetVersion":"v_007","docsetTargetFile":"n0n3ecvxq8pkfan1vpadv4kfx6cy.htm"}} -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/DSTODS2.json: -------------------------------------------------------------------------------- 1 | {"name":"DSTODS2","statements":[{"name":"PROC DSTODS2","description":"Translates DATA step code into DS2 code.","help":"PROC DSTODS2 IN=*datastep-program-filename*OUT=*ds2-program-filename*<OUTDIR=\"*output-directory-name*\">RUN ;<QUIT ;>","arguments":[{"name":"IN=","description":"specifies the name of the DATA step file or a SAS fileref.","help":"IN=*datastep-program-filename*","type":"value","supportSiteTargetFragment":"p09wfv9ahgn3i1n1tm2qakw8oqw5"},{"name":"OUT=","description":"specifies the name of the DS2 file that is created.","help":"OUT=*ds2-program-filename*","type":"value","supportSiteTargetFragment":"n1gv1aj93j0rlhn1hjl4oambdxzo"},{"name":"OUTDIR=","optional":true,"description":"specifies the output directory name for the file.","help":"OUTDIR=\"*output-directory-name*\"","type":"value","supportSiteTargetFragment":"p0ezsgwk9qb54on1g636vbcbihy2"}],"supportSiteTargetFile":"p0ydariamjl8ken1ahfrvdxldyrx.htm"}],"supportSiteInformation":{"docsetId":"proc","docsetVersion":"v_002","docsetTargetFile":"p05qb7pgqun5urn18bahx1vt640s.htm"}} -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/JAVAINFO.json: -------------------------------------------------------------------------------- 1 | {"name":"JAVAINFO","statements":[{"name":"PROC JAVAINFO","description":"Displays diagnostic information about the SAS Java environment.","help":"PROC JAVAINFO <*options*>;","arguments":[{"name":"ALL","optional":true,"description":"lists current information about the SAS Java environment.","type":"standalone","supportSiteTargetFragment":"n1sbcdqv9tk27tn1sowbz4swsfs2"},{"name":"CLASSPATHS","optional":true,"description":"lists information about the classpaths that Java is using.","type":"standalone","supportSiteTargetFragment":"p1skd39l0l25mpn0zdwoyzeww7tb"},{"name":"HELP","optional":true,"description":"provides usage assistance in using the JAVAINFO procedure.","type":"standalone","supportSiteTargetFragment":"p1w4xztcpoo4syn1c84hfge8gawn"},{"name":"JREOPTIONS","optional":true,"description":"lists the Java properties that are set when the JREOPTIONS configuration option is specified.\n• When used in PROC JAVAINFO, JREOPTIONS specifies the JREOPTIONS Java properties that are set when Java is started.\n• When used in PROC OPTIONS, JREOPTIONS specifies the Java options that are in the configuration file when SAS is started.","type":"standalone","supportSiteTargetFragment":"p0gz2hj285mox4n1s8fb8s1ap63j"},{"name":"OS","optional":true,"description":"lists information about the operating system that SAS is running under.","type":"standalone","supportSiteTargetFragment":"n1852mbxmv3a9cn1h3p7och9ufkj"},{"name":"version","optional":true,"description":"lists the Java Runtime Environment (JRE) that SAS is using.","type":"standalone","supportSiteTargetFragment":"p0emra25xen731n141ujj33in2yp"}],"supportSiteTargetFile":"n0ip2xay8coflrn1g6iy7u376k9w.htm"}],"supportSiteInformation":{"docsetId":"proc","docsetVersion":"v_002","docsetTargetFile":"p0vct8p2qp94nfn15kpp662lbg0t.htm"}} -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/LUA.json: -------------------------------------------------------------------------------- 1 | {"name":"LUA","statements":[{"name":"PROC LUA","description":"Runs Lua statements within a SAS session.","help":"PROC LUA ;\n\t;>\n\t\t ... Lua statements ...\n\trun;","arguments":[{"name":"INFILE=","optional":true,"description":"identifies a source file that contains Lua statements to run within a SAS session.","help":"INFILE= '*filename*'","type":"value","supportSiteTargetFragment":"n1525gzwkwpd9dn1mzq38nvtefju"},{"name":"RESTART","optional":true,"description":"resets the state of Lua code submissions for a SAS session.","type":"standalone","supportSiteTargetFragment":"p1fi3sf9umb3hgn11lpwb7rdyqww"},{"name":"TERMINATE","optional":true,"description":"stops maintaining the Lua code state in memory and terminates the Lua state when the LUA procedure completes. Subsequent calls to the LUA procedure begin a new instance of the Lua code state.","type":"standalone","supportSiteTargetFragment":"p0fmipwzucy4u8n13z2ax1npqbhc"}],"supportSiteTargetFile":"p0lqta2cbq9b44n12h28nil7a093.htm"},{"name":"SUBMIT","description":"Identifies the beginning of a block of Lua code. Enter Lua statements between the SUBMIT and ENDSUBMIT statements.","help":"SUBMIT <'*assignments;*'>;","arguments":[{"name":"assignments;","optional":true,"placeholder":true,"description":"identifies one or more macro variable assignments that are passed to the block of Lua statements. If only one assignment is listed, then the semicolon (;) within the quotation marks is not required.","type":"value","supportSiteTargetFragment":"p15nqsskrf4m3fn19x5uxbrgaxqb"}],"supportSiteTargetFile":"n1e6uqw3idxgzon10b61cg120h9c.htm"},{"name":"ENDSUBMIT","description":"Identifies the end of a block of Lua statements. Do not enter any other statement on the same line as the ENDSUBMIT statement.","help":"ENDSUBMIT;","supportSiteTargetFile":"n1wbamfdkaxscen1tjvyhmb30u6g.htm"}],"supportSiteInformation":{"docsetId":"proc","docsetVersion":"v_002","docsetTargetFile":"n1w8nl91tml15dn1mw9p5l8oj6hy.htm"}} -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/OPTLOAD.json: -------------------------------------------------------------------------------- 1 | {"name":"OPTLOAD","statements":[{"name":"PROC OPTLOAD","description":"Loads saved setting of SAS system options that are stored in the SAS registry or in a SAS data set.","help":"PROC OPTLOAD <*options*>;","arguments":[{"name":"DATA=","optional":true,"description":"Load SAS system option settings from an existing data set.","help":"DATA=*libref.dataset*","type":"dataSet","supportSiteTargetFragment":"p1ezqfdev5foton1pw08vzxcyl8d"},{"name":"KEY=","optional":true,"description":"Load SAS system option settings from an existing registry key.","help":"KEY=\"*SAS registry key*\"","type":"value","supportSiteTargetFragment":"n183pm4wr9gii7n1pzcfcu9ljkmo"}],"supportSiteTargetFile":"p18y99r0z9n3d3n1schuouoyv4tr.htm"}],"supportSiteInformation":{"docsetId":"lesysoptsref","docsetVersion":"v_001","docsetTargetFile":"n0g8uu2ht7cuqwn1cyxwxed66gin.htm"}} -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/OPTSAVE.json: -------------------------------------------------------------------------------- 1 | {"name":"OPTSAVE","statements":[{"name":"PROC OPTSAVE","description":"Saves the current SAS system option settings in the SAS registry or in a SAS data set.","help":"PROC OPTSAVE <*options*>;","arguments":[{"name":"KEY=","optional":true,"description":"Save SAS system option settings to a registry key.","help":"KEY=\"*SAS registry key*\"","type":"value","supportSiteTargetFragment":"p1eb2imd8fvg2jn1gygeamhszvcv"},{"name":"OUT=","optional":true,"description":"Save SAS system option settings to a SAS data set.","help":"OUT=*libref.dataset*","type":"dataSet","supportSiteTargetFragment":"n1jzruyimjcqzen1ki6vs1zbmh5d"}],"supportSiteTargetFile":"n0qx6so0ctrt1vn16jmiaxznhx7z.htm"}],"supportSiteInformation":{"docsetId":"lesysoptsref","docsetVersion":"v_001","docsetTargetFile":"n0rmbvyiga95zcn1mrmt10xkrobu.htm"}} -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/PRTEXP.json: -------------------------------------------------------------------------------- 1 | {"name":"PRTEXP","statements":[{"name":"PROC PRTEXP","description":"Replicates, modifies, and creates printer definitions.","help":"PROC PRTEXP ; \n\tSELECT printer(s);\n\tEXCLUDE printer(s)","arguments":[{"name":"USESASHELP","optional":true,"description":"specifies that SAS search only the SASHELP portion of the registry for printer definitions.","type":"standalone","supportSiteTargetFragment":"n1dl2hy0rkskx3n1qtu2rt3pw3d1"},{"name":"OUT=","optional":true,"description":"specifies the SAS data set that contains the printer definitions.","help":"OUT=*SAS-data-set*","type":"dataSet","supportSiteTargetFragment":"p1r4c3hx3seotyn186pdymlkjzce"}],"supportSiteTargetFile":"n1apklluhvpjthn10pkrmsg7e085.htm"},{"name":"EXCLUDE","description":"Names the printers whose information does not appear in output.","help":"EXCLUDE *printer(s)*;","arguments":[{"name":"printer","placeholder":true,"description":"specifies one or more printers that you do not want the output to contain information about.","help":"*printer(s)* ","type":"value","supportSiteTargetFragment":"n1u7qp957m86g0n11wr5629yonhq"}],"supportSiteTargetFile":"p0kqciwgisfnd8n1e2i5goiehp3i.htm"},{"name":"SELECT","description":"Names the printers whose information is contained in the output.","help":"SELECT *printer(s)*;","arguments":[{"name":"printer","placeholder":true,"description":"specifies one or more printers that you would like the output to contain information about.","help":"*printer(s)*","type":"value","supportSiteTargetFragment":"n0k1xigt44rvh1n17nbfyxsou96w"}],"supportSiteTargetFile":"n18n845qvanh0tn1d23r357k67ai.htm"}],"supportSiteInformation":{"docsetId":"proc","docsetVersion":"v_002","docsetTargetFile":"n1xehxf82gh019n1w77kd1sifrpb.htm"}} -------------------------------------------------------------------------------- /server/pubsdata/Procedures/en/QKB.json: -------------------------------------------------------------------------------- 1 | {"name":"QKB","statements":[{"name":"PROC QKB","description":"The QKB procedure lists each QKB that is stored in the QKB repository.","help":"PROC QKB \n\t ;\n\tLIST ;","arguments":[{"name":"NOPRINT","optional":true,"description":"stops the output from being displayed in the console.","type":"standalone","supportSiteTargetFragment":"p123e1f6crixk6n1lhqxuqugnce6"},{"name":"OUT=","optional":true,"description":"specifies the storage location of the QKB data set.","help":"OUT=*output-data-set*","type":"dataSet","supportSiteTargetFragment":"p0f4dzl6vo9fwpn12m2likudr1iv"}],"supportSiteTargetFile":"p0droqm19z2mo9n12e37toil7xzc.htm"}],"supportSiteInformation":{"docsetId":"dqclref","docsetVersion":"v_007","docsetTargetFile":"p0un31h958xzbjn1wavwiyq88j70.htm"}} -------------------------------------------------------------------------------- /server/src/browser/ResLoader.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export const ResLoader = { 5 | // eslint-disable-next-line 6 | get: function (url: string, cb: (arg0: any) => void, async?: boolean) { 7 | // have to explicitly write path for webpack to bundle 8 | const index = url.indexOf("/data/"); 9 | if (index > 0) { 10 | // eslint-disable-next-line @typescript-eslint/no-require-imports 11 | cb(require(`../../data/${url.slice(index + 6)}`)); 12 | } else { 13 | const index = url.indexOf("/pubsdata/"); 14 | if (index > 0) { 15 | // eslint-disable-next-line @typescript-eslint/no-require-imports 16 | cb(require(`../../pubsdata/${url.slice(index + 10)}`)); 17 | } 18 | } 19 | }, 20 | getBundle: function (locale: string): string { 21 | // eslint-disable-next-line @typescript-eslint/no-require-imports 22 | return require(`../../messagebundle_${locale}.properties`); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /server/src/browser/server.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Connection } from "vscode-languageserver"; 4 | import { 5 | BrowserMessageReader, 6 | BrowserMessageWriter, 7 | createConnection, 8 | } from "vscode-languageserver/browser"; 9 | 10 | import { PyrightLanguageProviderBrowser } from "../python/browser/PyrightLanguageProviderBrowser"; 11 | import { runServer } from "../server"; 12 | 13 | /* browser specific setup code */ 14 | const messageReader = new BrowserMessageReader(self); 15 | const messageWriter = new BrowserMessageWriter(self); 16 | 17 | const connection: Connection = createConnection(messageReader, messageWriter); 18 | 19 | runServer(connection, new PyrightLanguageProviderBrowser(connection, 1)); 20 | -------------------------------------------------------------------------------- /server/src/node/ResLoader.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { readFileSync } from "fs"; 4 | import * as path from "path"; 5 | 6 | export const ResLoader = { 7 | // eslint-disable-next-line 8 | get: function (url: string, cb: (arg0: any) => void, async?: boolean) { 9 | // eslint-disable-next-line @typescript-eslint/no-require-imports 10 | cb(require(url)); 11 | }, 12 | getBundle: function (locale: string): string { 13 | return readFileSync( 14 | path.resolve(__dirname, `../../messagebundle_${locale}.properties`), 15 | ).toString(); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/src/node/server.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Connection } from "vscode-languageserver"; 4 | import { ProposedFeatures, createConnection } from "vscode-languageserver/node"; 5 | 6 | import { PyrightLanguageProviderNode } from "../python/node/PyrightLanguageProviderNode"; 7 | import { runServer } from "../server"; 8 | 9 | const connection: Connection = createConnection(ProposedFeatures.all); 10 | 11 | runServer(connection, new PyrightLanguageProviderNode(connection, 1)); 12 | -------------------------------------------------------------------------------- /server/src/python/PyrightLanguageProvider.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import type { PyrightLanguageProviderBrowser } from "./browser/PyrightLanguageProviderBrowser"; 4 | import type { PyrightLanguageProviderNode } from "./node/PyrightLanguageProviderNode"; 5 | 6 | export type PyrightLanguageProvider = 7 | | PyrightLanguageProviderNode 8 | | PyrightLanguageProviderBrowser; 9 | -------------------------------------------------------------------------------- /server/src/python/browser/typeShed.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // typeshed-loader will fill in real content when bundling 5 | export const typeShed = [ 6 | { 7 | filePath: "", 8 | content: "", 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /server/src/python/browser/typeshed-loader/index.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /* eslint-disable @typescript-eslint/no-var-requires */ 5 | 6 | /** 7 | * A webpack loader gathering typeshed for intellisense features 8 | */ 9 | const fs = require("fs/promises"); 10 | const path = require("path"); 11 | 12 | const dirs = ["stdlib", "stubs/sas"]; 13 | const result = []; 14 | 15 | async function* walk(dir) { 16 | for await (const d of await fs.opendir(dir)) { 17 | const entry = path.join(dir, d.name); 18 | if (d.isDirectory()) { 19 | yield* walk(entry); 20 | } else if (d.isFile()) { 21 | yield entry; 22 | } 23 | } 24 | } 25 | 26 | async function loader() { 27 | const callback = this.async(); 28 | 29 | for (const dir of dirs) { 30 | const entry = path.resolve( 31 | __dirname, 32 | "../../../../dist/node/typeshed-fallback", 33 | dir, 34 | ); 35 | const prefixLength = entry.indexOf("typeshed-fallback") - 1; 36 | for await (const filename of walk(entry)) { 37 | if (filename.endsWith(".pyi")) { 38 | const content = await fs.readFile(filename); 39 | result.push({ 40 | content: content.toString(), 41 | filePath: filename.slice(prefixLength).replace(/\\/g, "/"), 42 | }); 43 | } 44 | } 45 | } 46 | 47 | callback(null, `export const typeShed = ${JSON.stringify(result)}`); 48 | } 49 | 50 | module.exports = loader; 51 | -------------------------------------------------------------------------------- /server/src/sas/Model.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2022, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { TextDocument } from "vscode-languageserver-textdocument"; 4 | 5 | import { TextRange } from "./utils"; 6 | 7 | export class Model { 8 | constructor(private doc: TextDocument) {} 9 | 10 | getLine(line: number): string { 11 | return this.doc.getText({ 12 | start: { line, character: 0 }, 13 | end: { line: line + 1, character: 0 }, 14 | }); 15 | } 16 | 17 | getLineCount(): number { 18 | return this.doc.lineCount; 19 | } 20 | 21 | getText(range: TextRange): string { 22 | return this.doc.getText({ 23 | start: { 24 | line: range.start.line, 25 | character: range.start.column, 26 | }, 27 | end: { 28 | line: range.end.line, 29 | character: range.end.column, 30 | }, 31 | }); 32 | } 33 | 34 | getColumnCount(line: number): number { 35 | return ( 36 | this.doc.offsetAt({ line: line + 1, character: 0 }) - 37 | this.doc.offsetAt({ line, character: 0 }) 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/src/sas/formatter/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | import type { TextEdit } from "vscode-languageserver-protocol"; 4 | 5 | import type { Options, Plugin } from "prettier"; 6 | import { format } from "prettier/standalone"; 7 | 8 | import type { Token } from "../Lexer"; 9 | import type { Model } from "../Model"; 10 | import type { SyntaxProvider } from "../SyntaxProvider"; 11 | import { SASAST, getParser } from "./parser"; 12 | import { print } from "./printer"; 13 | 14 | const getSasPlugin = ( 15 | model: Model, 16 | tokens: Token[], 17 | syntaxProvider: SyntaxProvider, 18 | ): Plugin => ({ 19 | languages: [ 20 | { 21 | name: "sas", 22 | parsers: ["sas"], 23 | extensions: [".sas"], 24 | vscodeLanguageIds: ["sas"], 25 | }, 26 | ], 27 | parsers: { 28 | sas: { 29 | parse: getParser(model, tokens, syntaxProvider), 30 | astFormat: "sas-ast", 31 | locStart: () => 0, 32 | locEnd: () => 0, 33 | }, 34 | }, 35 | printers: { 36 | "sas-ast": { 37 | print, 38 | }, 39 | }, 40 | }); 41 | 42 | export class Formatter { 43 | private tokens: Token[] = []; 44 | 45 | constructor( 46 | private model: Model, 47 | private syntaxProvider: SyntaxProvider, 48 | ) { 49 | this.syntaxProvider.setTokenCallback(this.tokens.push.bind(this.tokens)); 50 | } 51 | 52 | async format(options: Options): Promise { 53 | const formattedText = await format("text", { 54 | parser: "sas", 55 | plugins: [getSasPlugin(this.model, this.tokens, this.syntaxProvider)], 56 | ...options, 57 | }); 58 | return [ 59 | { 60 | range: { 61 | start: { line: 0, character: 0 }, 62 | end: { line: this.model.getLineCount(), character: 0 }, 63 | }, 64 | newText: formattedText, 65 | }, 66 | ]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /server/testFixture/embedded_lang/proc_lua.sas: -------------------------------------------------------------------------------- 1 | proc lua; 2 | submit; 3 | local code = [[ 4 | data sample; 5 | run; 6 | ]] 7 | endsubmit; 8 | run; 9 | -------------------------------------------------------------------------------- /server/testFixture/embedded_lang/proc_python.sas: -------------------------------------------------------------------------------- 1 | * 2 | ''' 3 | my proc python 4 | ''' 5 | ; 6 | proc python; 7 | interactive; 8 | text = """ 9 | abc 10 | """ 11 | print('first statement after for loop'); 12 | endinteractive; 13 | run; 14 | -------------------------------------------------------------------------------- /server/testFixture/embedded_lang/proc_sql.sas: -------------------------------------------------------------------------------- 1 | /* comment */ 2 | proc sql outobs=2; 3 | title 'Densities of Countries'; 4 | select name format=$20. from sql.newcountries; 5 | 6 | /* comment */ 7 | proc sql outobs=2; 8 | title 'Densities of Countries'; 9 | select name format=$20. from sql.newcountries; 10 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "target": "es2019", 5 | "lib": ["ES2019", "WebWorker"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "strict": true, 10 | "outDir": "out", 11 | "rootDir": "." 12 | }, 13 | "include": ["src", "test"], 14 | "exclude": ["node_modules", ".vscode-test"] 15 | } 16 | -------------------------------------------------------------------------------- /snippets/proc-snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "proc print": { 3 | "prefix": "print", 4 | "body": ["proc print data=$1;", "run;\n"], 5 | "description": "PROC PRINT template" 6 | }, 7 | "proc import": { 8 | "prefix": "import", 9 | "body": ["proc import datafile=$1 out=$2 dbms=$3;", "run;\n"], 10 | "description": "PROC IMPORT template" 11 | }, 12 | "proc export": { 13 | "prefix": "export", 14 | "body": ["proc export data=$1 outfile=$2 dbms=$3;", "run;\n"], 15 | "description": "PROC EXPORT template" 16 | }, 17 | "proc ds2": { 18 | "prefix": "ds2", 19 | "body": [ 20 | "proc ds2;", 21 | "data;\n", 22 | "\tmethod init();", 23 | "\t\t$1", 24 | "\tend;\n", 25 | "enddata;", 26 | "run;", 27 | "quit;\n" 28 | ], 29 | "description": "PROC DS2 template" 30 | }, 31 | "proc sql": { 32 | "prefix": "sql", 33 | "body": ["proc sql;", "\tselect $1", "\tfrom $2", "\twhere $3;", "quit;\n"], 34 | "description": "PROC SQL template" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /themes/sas-highcontrast-color-theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SAS High Contrast", 3 | "include": "./hc_black.json", 4 | "semanticHighlighting": true, 5 | "semanticTokenColors": { 6 | "sep": { "foreground": "#fff" }, 7 | "keyword": { "foreground": "#c68aff" }, 8 | "sec-keyword": { "foreground": "#61a8fa", "bold": true }, 9 | "proc-name": { "foreground": "#61a8fa", "bold": true }, 10 | "comment": { "foreground": "#87b532" }, 11 | "macro-keyword": { "foreground": "#c68aff" }, 12 | "macro-comment": { "foreground": "#87b532" }, 13 | "macro-ref": { "foreground": "#fff", "bold": true }, 14 | "macro-sec-keyword": { "foreground": "#fff", "bold": true }, 15 | "cards-data": { "foreground": "#f98e39" }, 16 | "string": { "foreground": "#ff7973" }, 17 | "date": { "foreground": "#1fbba9", "bold": true }, 18 | "time": { "foreground": "#1fbba9", "bold": true }, 19 | "dt": { "foreground": "#1fbba9", "bold": true }, 20 | "bitmask": { "foreground": "#1fbba9", "bold": true }, 21 | "namelit": { "foreground": "#ff7973", "bold": true }, 22 | "hex": { "foreground": "#1fbba9", "bold": true }, 23 | "numeric": { "foreground": "#1fbba9", "bold": true }, 24 | "format": { "foreground": "#1fbba9" }, 25 | "error": { "foreground": "#f5a3a3" }, 26 | "warning": { "foreground": "#f2d08c" }, 27 | "note": { "foreground": "#8cc8f2" } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /themes/sas-light-color-theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SAS Light", 3 | "include": "./light_plus.json", 4 | "semanticHighlighting": true, 5 | "semanticTokenColors": { 6 | "sep": { "foreground": "#1b1d22" }, 7 | "keyword": { "foreground": "#3578c5" }, 8 | "sec-keyword": { "foreground": "#224c7c", "bold": true }, 9 | "proc-name": { "foreground": "#224c7c", "bold": true }, 10 | "comment": { "foreground": "#647f29" }, 11 | "macro-keyword": { "foreground": "#3578c5" }, 12 | "macro-comment": { "foreground": "#647f29" }, 13 | "macro-ref": { "foreground": "#1b1d22", "bold": true }, 14 | "macro-sec-keyword": { "foreground": "#1b1d22", "bold": true }, 15 | "cards-data": { "foreground": "#ad6531" }, 16 | "string": { "foreground": "#8f4238" }, 17 | "date": { "foreground": "#3c8275", "bold": true }, 18 | "time": { "foreground": "#3c8275", "bold": true }, 19 | "dt": { "foreground": "#3c8275", "bold": true }, 20 | "bitmask": { "foreground": "#3c8275", "bold": true }, 21 | "namelit": { "foreground": "#8f4238", "bold": true }, 22 | "hex": { "foreground": "#3c8275", "bold": true }, 23 | "numeric": { "foreground": "#3c8275", "bold": true }, 24 | "format": { "foreground": "#3c8275" }, 25 | "error": { "foreground": "#ff0000" }, 26 | "warning": { "foreground": "#8f5f00" }, 27 | "note": { "foreground": "#0000ff" } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tools/check-copyright.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | import glob from "glob"; 3 | 4 | // These files will not be checked for copyright information 5 | const filesToIgnore = [ 6 | "**/dist/**", 7 | "**/node_modules/**", 8 | "**/out/**", 9 | "**/test/**", 10 | "*.config.*js", 11 | "*.test.tsx?", 12 | "tools/**", 13 | "website/**", 14 | ]; 15 | 16 | const COPYRIGHT_REGEX = /^\/\/ Copyright © ([0-9-\s]+), SAS Institute/; 17 | const COPYRIGHT_TEMPLATE = `// Copyright © ${new Date().getFullYear()}, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. 18 | // SPDX-License-Identifier: Apache-2.0 19 | 20 | `; 21 | 22 | const processChanges = (filesToCheck, fix = false) => { 23 | let invalidFiles = []; 24 | filesToCheck.map((file) => { 25 | const fileContents = readFileSync(file); 26 | if (!COPYRIGHT_REGEX.test(fileContents.toString())) { 27 | invalidFiles.push(file); 28 | if (fix) { 29 | writeFileSync(file, `${COPYRIGHT_TEMPLATE}${fileContents}`); 30 | } 31 | } 32 | }); 33 | 34 | if (invalidFiles.length > 0) { 35 | console.log( 36 | fix 37 | ? "The following files have been updated with copyright information" 38 | : "The following files are missing copyright information", 39 | ); 40 | console.log(invalidFiles.map((file) => `- ${file}`).join("\n")); 41 | process.exit(1); 42 | } 43 | }; 44 | 45 | await processChanges( 46 | glob.sync("**/*.{mjs,js,ts,tsx,jsx}", { 47 | ignore: filesToIgnore, 48 | }), 49 | process.env.npm_config_fix || false, 50 | ); 51 | -------------------------------------------------------------------------------- /tools/preparePubsdata.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | const dirs = [ 6 | "../server/pubsdata/Procedures/en", 7 | "../server/pubsdata/Statements/en", 8 | "../server/pubsdata/Functions/en", 9 | ]; 10 | 11 | function convert(str) { 12 | if (str.indexOf("\n") !== -1) { 13 | // multi-lines, no style can be applied 14 | return str 15 | .replace(/|<\/i>/g, "") 16 | .replace(/</g, "<") 17 | .replace(/>/g, ">"); 18 | } 19 | return str.replace(/\s*|\s*<\/i>/g, "*"); 20 | } 21 | 22 | function gothrough(syntax) { 23 | for (const data of syntax) { 24 | if (data.arguments) gothrough(data.arguments); 25 | if (data.syntax && data.syntax.arguments) gothrough(data.syntax.arguments); 26 | if (data.help) data.help = convert(data.help); 27 | if (data.syntax && data.syntax.help) 28 | data.syntax.help = convert(data.syntax.help); 29 | } 30 | } 31 | 32 | for (const dir of dirs) { 33 | fs.readdir(dir, (err, files) => { 34 | for (const file of files) { 35 | fs.readFile(path.join(dir, file), (err, data) => { 36 | const syntax = JSON.parse(data); 37 | gothrough(syntax.statements ? syntax.statements : syntax); 38 | fs.writeFileSync(path.join(dir, file), JSON.stringify(syntax)); 39 | }); 40 | } 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019"], 6 | "outDir": "out", 7 | "rootDir": "src", 8 | "sourceMap": true, 9 | "jsx": "react" 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules", ".vscode-test"], 13 | "references": [{ "path": "./client" }, { "path": "./server" }] 14 | } 15 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve("@docusaurus/core/lib/babel/preset")], 3 | }; 4 | -------------------------------------------------------------------------------- /website/docs/Configurations/Profiles/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapsed": false 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/Configurations/Profiles/sas9local.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # SAS 9.4 (local) Connection Profile 6 | 7 | To use a SAS 9.4 (local) connection type, you need to have SAS Integration Technologies Client for Windows (ITCLIENT) installed on the client machine (the same machine VS Code is installed on). 8 | 9 | You can check the SASHOME location on your client machine to see if you already have ITCLIENT installed. For example, ITCLIENT is normally installed in the default path "C:\Program Files\SASHome\x86\Integration Technologies". If that path exists on your machine, you have ITCLIENT. ITCLIENT is automatically installed with some SAS software, such as SAS Enterprise Guide and SAS Add-in for Microsoft Office, so if you have one of those on your machine, you likely already have ITCLIENT as well. 10 | 11 | If you do not already have ITCLIENT installed on the client machine, follow the [steps](./sas9iom.md#steps-to-install-itclient). 12 | 13 | ## Profile Anatomy 14 | 15 | A local SAS 9.4 connection profile includes the following parameters: 16 | 17 | `"connectionType": "com"` 18 | 19 | | Name | Description | Additional Notes | 20 | | -------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------ | 21 | | `host` | Indicates SAS 9.4 local instance | Defaults to "localhost" for com | 22 | | `interopLibraryFolderPath` | COM interop library path | A custom path to a folder containing SAS interop libraries (`SASInterop.dll` and `SASOManInterop.dll`) | 23 | -------------------------------------------------------------------------------- /website/docs/Configurations/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapsed": false 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/Configurations/index.md: -------------------------------------------------------------------------------- 1 | # Configuring the SAS Extension 2 | 3 | Before running SAS code, you must configure the SAS extension to access a SAS 9.4 (remote or local) server or a SAS Viya server. You must license SAS 9.4 or SAS Viya to run SAS code. 4 | 5 | To configure the SAS extension: 6 | 7 | 1. Open a SAS program file. 8 | 9 | 2. Click "No Profile" in the status bar on the bottom left of your VS Code window. 10 | 11 | You can also open the command palette (`F1`, or `Ctrl+Shift+P` on Windows or Linux, or `Shift+CMD+P` on OSX) and locate `SAS: Add New Connection Profile` command. 12 | 13 | ![No Active Profiles Found](/images/NoActiveProfilesStatusBar.png) 14 | 15 | 3. Follow the instructions in the [Add New Connection Profile](./Profiles/index.md#add-new-connection-profile) section to add a profile. 16 | 17 | 4. After you have created a profile, the Status Bar Item changes from "No Profile" to the name of the new profile. 18 | 19 | ![Status Bar Profile](/images/StatusBarProfileItem.png) 20 | -------------------------------------------------------------------------------- /website/docs/Configurations/sasLog.md: -------------------------------------------------------------------------------- 1 | # SAS Log 2 | 3 | You can customize when the SAS log is displayed in the bottom panel by using the following extension settings. These settings apply to all connection profiles: 4 | 5 | | Name | Description | Additional Notes | 6 | | ------------------------------- | ---------------------------------- | ---------------- | 7 | | `SAS.log.showOnExecutionStart` | Show SAS log on start of execution | default: `true` | 8 | | `SAS.log.showOnExecutionFinish` | Show SAS log on end of execution | default: `true` | 9 | 10 | To access the SAS settings, select `File > Preferences > Settings`. Search for "sas" and then click SAS in the search results to view the SAS extension settings. You can edit the settings directly in the `settings.json` file by clicking `Edit in settings.json`. 11 | 12 | Example 13 | 14 | ```json title="settings.json" 15 | { 16 | "SAS.log.showOnExecutionFinish": true, 17 | "SAS.log.showOnExecutionStart": false, 18 | "SAS.connectionProfiles": { 19 | "activeProfile": "viyaServer", 20 | "profiles": { 21 | "viya4": { 22 | "endpoint": "https://example-endpoint.com", 23 | "connectionType": "rest", 24 | "sasOptions": ["NONEWS", "ECHOAUTO"], 25 | "autoExec": [ 26 | { 27 | "type": "line", 28 | "line": "ods graphics / imagemap;" 29 | }, 30 | { 31 | "type": "file", 32 | "filePath": "/my/local/autoexec.sas" 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | :::tip 42 | 43 | To view the SAS log as a text file, click the `...` icon on the top right of the OUTPUT panel, and select `Open Output in Editor`. 44 | 45 | ::: 46 | -------------------------------------------------------------------------------- /website/docs/Features/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapsed": false 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/Features/accessContent.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Accessing SAS Content 6 | 7 | After you configure the SAS extension for a SAS Viya environment, you can access SAS Content. 8 | 9 | To access SAS Content: 10 | 11 | 1. Click the SAS icon in the VS Code activity bar. 12 | 2. Click `Sign In`. 13 | 3. Your SAS Content should be displayed after you sign in. You can create, edit, delete, upload, download, and run files stored on a SAS server. 14 | 15 | :::info note 16 | 17 | SAS Content requires a profile with a connection to a SAS Viya server. 18 | 19 | ::: 20 | 21 | ![SAS Content](/images/sasContent.png) 22 | 23 | ## Drag and Drop 24 | 25 | - You can drag and drop files and folders between the SAS Content pane and File Explorer. 26 | - You can drag and drop a file from SAS Content into your SAS code. SAS generates a `FILENAME` statement for you. 27 | -------------------------------------------------------------------------------- /website/docs/Features/accessLibraries.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Accessing Libraries and Tables 6 | 7 | After you configure the SAS extension for a SAS Viya, SAS 9.4 (local), or SAS 9.4 (remote-IOM) environment, you can access your connected libraries. 8 | 9 | You can use the Libraries pane to delete a table, drag and drop tables into your SAS program code, or view the table data. 10 | 11 | ![Libraries](/images/libraries.png) 12 | -------------------------------------------------------------------------------- /website/docs/Features/accessServer.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Accessing SAS Server 6 | 7 | After you configure the SAS extension for a SAS Viya environment, you can access SAS Server. 8 | 9 | To access SAS Server: 10 | 11 | 1. Click the SAS icon in the VS Code activity bar. 12 | 2. Click `Sign In`. 13 | 3. Your SAS Server files should be displayed after you sign in. You can create, edit, delete, upload, download, and run files stored on a SAS server. 14 | 15 | :::info note 16 | 17 | SAS Server requires a profile with one of the following connection types: SAS Viya, SAS 9.4 (remote - IOM), SAS 9.4 (local) 18 | 19 | ::: 20 | 21 | ## Drag and Drop 22 | 23 | - You can drag and drop files and folders between the SAS Server pane and File Explorer. 24 | -------------------------------------------------------------------------------- /website/docs/Features/errorsWarnings.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Fixing Errors and Warnings 6 | 7 | The Problems panel contains error and warning messages that are generated by the SAS log when you run a program. Errors and warnings in the Problems panel are not cleared until you rerun the code. The Quick Fix option enables you to remove items from the Problems panel without rerunning the code. 8 | 9 | To use the Quick Fix options: 10 | 11 | 1. Open the Quick Fix menu in one of these ways: 12 | 13 | - Click a message in the Problems panel and then click the corresponding `Show Code Actions` icon in the code editor. 14 | 15 | - Click the `Show fixes` button for the appropriate message in the Problems panel. 16 | 17 | ![Quick Fix](/images/quickFix.png) 18 | 19 | 2. Select one of the following options: 20 | 21 | - `Ignore: current position` - clears the currently selected problem from the Problems panel and the code editor. 22 | 23 | - `Ignore: warnings` - clears all warnings from the Problems panel and the code editor. 24 | 25 | - `Ignore: error` - clears all errors from the Problems panel and the code editor. 26 | 27 | - `Ignore: all` - clears all problems from the Problems panel and the code editor. 28 | 29 | :::tip 30 | 31 | You can use the Problems panel as a to-do list when you are debugging your code. When you correct an error in your code, open the Quick Fix options for that error and select `Ignore: current position` to remove the error message from the list. 32 | 33 | ::: 34 | -------------------------------------------------------------------------------- /website/docs/Features/index.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | The SAS extension includes many features to help you access your data, write and run code, and create SAS notebooks. 4 | 5 | - You can use the SAS extension to access your libraries and tables. If you are connected to a SAS Viya server, you can also access SAS Content. 6 | - You can edit code using many of the same features that are available in the SAS code editor. 7 | - You can run SAS code and create a custom task to run a specific SAS file. 8 | - You can create an interactive SAS notebook. 9 | -------------------------------------------------------------------------------- /website/docs/Features/sasNotebook.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 8 3 | --- 4 | 5 | # SAS Notebook 6 | 7 | SAS Notebook is an interactive notebook file that includes markdown code, executable code snippets, and corresponding rich output cells. 8 | 9 | - To create a SAS notebook, select `File > New File > SAS Notebook`. 10 | - To change a code language, click the `Select Cell Language Mode` button in the lower right corner of a code cell. 11 | - To toggle log or ODS output display, click the **More Actions** (`...`) button in the upper left corner of the output and select `Change Presentation`. 12 | - You can use the `File` menu to save your SAS Notebook to a `.sasnb` file, share the notebook with others, and open the notebook in another VS Code window. 13 | 14 | ![SAS Notebook](/images/sasNotebook.png) 15 | 16 | :::note 17 | 18 | Starting with Visual Studio Code version 1.93, [the language for SQL files has been renamed](https://code.visualstudio.com/updates/v1_93#_renamed-sql-to-ms-sql) from 'SQL' to 'MS SQL'. As a result, the SQL cell in SAS Notebook is now labeled 'MS SQL'. The SAS Extension for Visual Studio Code does not control the label of the SQL cell in SAS Notebook. The functionality of the SQL cell has not changed, however. Valid PROC SQL code continues to work in the MS SQL cell. 19 | 20 | ::: 21 | 22 | ## Export 23 | 24 | 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. 25 | 26 | ### SAS 27 | 28 | 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. 29 | 30 | ### HTML 31 | 32 | The exported HTML will be in Light or Dark theme depending on your VS Code theme kind. 33 | 34 | By default it doesn't include SAS log into the exported HTML file. To include log, check the `SAS.notebook.export.includeLog` setting. 35 | -------------------------------------------------------------------------------- /website/docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # SAS Extension for Visual Studio Code 6 | 7 | Welcome to the SAS Extension for Visual Studio Code! The SAS extension is lightweight, runs anywhere, and allows you to integrate SAS with other languages. 8 | 9 | The SAS extension includes the following features: 10 | 11 | - SAS syntax highlighting and help, code completion, and code snippets 12 | 13 | - Profile configuration for connecting to SAS and running code 14 | 15 | - Support for SAS Viya and SAS 9 connections 16 | 17 | - Access to SAS Content and libraries 18 | 19 | - Ability to create notebooks for SAS, SQL, Python, and other languages 20 | -------------------------------------------------------------------------------- /website/docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Installation 6 | 7 | Install the latest version of Visual Studio Code (version 1.89 or later). 8 | 9 | To install the SAS extension: 10 | 11 | 1. Open the Extensions view by clicking the Extensions icon in the Activity Bar on the left side of the Visual Studio Code window. 12 | 13 | 2. Search for the 'Official' SAS extension, and click the Install button. Once the installation is complete, the Install button changes to the Manage button. 14 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/preset-classic": "3.8.0", 19 | "@docusaurus/theme-mermaid": "3.8.0", 20 | "@easyops-cn/docusaurus-search-local": "0.50.0", 21 | "prism-react-renderer": "^2.4.1", 22 | "react": "^19.1.0", 23 | "react-dom": "^19.1.0" 24 | }, 25 | "devDependencies": { 26 | "@docusaurus/module-type-aliases": "3.8.0", 27 | "@docusaurus/tsconfig": "3.8.0", 28 | "typescript": "~5.8.3" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 3 chrome version", 38 | "last 3 firefox version", 39 | "last 5 safari version" 40 | ] 41 | }, 42 | "engines": { 43 | "node": ">=18.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | .footer--dark { 2 | --ifm-footer-background-color: #0664d0; 3 | --ifm-footer-link-hover-color: var(--ifm-color-white); 4 | } 5 | 6 | .header-github-link::before { 7 | content: ""; 8 | width: 24px; 9 | height: 24px; 10 | display: flex; 11 | background-color: var(--ifm-navbar-link-color); 12 | mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E"); 13 | transition: background-color var(--ifm-transition-fast) 14 | var(--ifm-transition-timing-default); 15 | } 16 | .header-github-link:hover::before { 17 | background-color: var(--ifm-navbar-link-hover-color); 18 | } 19 | 20 | .navbar__search-input { 21 | transition: box-shadow var(--ifm-transition-fast) 22 | var(--ifm-transition-timing-default); 23 | } 24 | .navbar__search-input:hover { 25 | box-shadow: 0 0 0 2px var(--ifm-navbar-link-hover-color); 26 | } 27 | 28 | [data-theme="light"] img[src$="#gh-dark-mode-only"], 29 | [data-theme="dark"] img[src$="#gh-light-mode-only"] { 30 | display: none; 31 | } 32 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/images/NoActiveProfilesStatusBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/NoActiveProfilesStatusBar.png -------------------------------------------------------------------------------- /website/static/images/StatusBarProfileItem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/StatusBarProfileItem.png -------------------------------------------------------------------------------- /website/static/images/featuresGlance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/featuresGlance.png -------------------------------------------------------------------------------- /website/static/images/formatter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/formatter.gif -------------------------------------------------------------------------------- /website/static/images/libraries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/libraries.png -------------------------------------------------------------------------------- /website/static/images/quickFix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/quickFix.png -------------------------------------------------------------------------------- /website/static/images/runCode2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/runCode2.png -------------------------------------------------------------------------------- /website/static/images/sas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/sas.png -------------------------------------------------------------------------------- /website/static/images/sasContent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/sasContent.png -------------------------------------------------------------------------------- /website/static/images/sasNotebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/sasNotebook.png -------------------------------------------------------------------------------- /website/static/images/signatureHelp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/signatureHelp.gif -------------------------------------------------------------------------------- /website/static/images/vsCodeChangeTheme2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/vsCodeChangeTheme2.gif -------------------------------------------------------------------------------- /website/static/images/vsCodeFoldAndOutline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/vsCodeFoldAndOutline.gif -------------------------------------------------------------------------------- /website/static/images/vsCodeRegionFunction.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/vsCodeRegionFunction.gif -------------------------------------------------------------------------------- /website/static/images/vsCodeSnippets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/vsCodeSnippets.gif -------------------------------------------------------------------------------- /website/static/images/vsCodeSyntaxAssist2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/vsCodeSyntaxAssist2.gif -------------------------------------------------------------------------------- /website/static/images/vsCodeTypeAhead.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/vscode-sas-extension/726bcf2d6accef7036272d9b565e679a2d826391/website/static/images/vsCodeTypeAhead.gif -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | --------------------------------------------------------------------------------