├── .artifactignore ├── .editorconfig ├── .github ├── CODEOWNERS ├── pull_request_template.md └── workflows │ ├── codeql-analysis.yml │ ├── main.yml │ └── prerelease.yml ├── .gitignore ├── .jshintrc ├── .npmrc ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── GOVERNANCE.md ├── LICENSE ├── README.md ├── _config.yml ├── eslint.config.mjs ├── images ├── fileIcon.svg └── logo.png ├── language-configuration-class.jsonc ├── language-configuration.json ├── package-lock.json ├── package.json ├── package.nls.json ├── snippets ├── objectscript-class.json ├── objectscript-int.json └── objectscript.json ├── src ├── api │ ├── atelier.d.ts │ └── index.ts ├── commands │ ├── addServerNamespaceToWorkspace.ts │ ├── compile.ts │ ├── connectFolderToServerNamespace.ts │ ├── delete.ts │ ├── documaticPreviewPanel.ts │ ├── export.ts │ ├── jumpToTagAndOffset.ts │ ├── newFile.ts │ ├── project.ts │ ├── restDebugPanel.ts │ ├── serverActions.ts │ ├── showPlanPanel.ts │ ├── studio.ts │ ├── studioMigration.ts │ ├── subclass.ts │ ├── superclass.ts │ ├── unitTest.ts │ ├── viewOthers.ts │ ├── webSocketTerminal.ts │ └── xmlToUdl.ts ├── debug │ ├── dbgp.ts │ ├── debugAdapter.ts │ ├── debugAdapterFactory.ts │ ├── debugConfProvider.ts │ ├── debugSession.ts │ ├── utils.ts │ └── xdebugConnection.ts ├── explorer │ ├── explorer.ts │ ├── nodes.ts │ └── projectsExplorer.ts ├── extension.ts ├── languageConfiguration.ts ├── providers │ ├── CodeActionProvider.ts │ ├── DocumentContentProvider.ts │ ├── DocumentFormattingEditProvider.ts │ ├── DocumentLinkProvider.ts │ ├── FileDecorationProvider.ts │ ├── FileSystemProvider │ │ ├── FileSearchProvider.ts │ │ ├── FileSystemProvider.ts │ │ └── TextSearchProvider.ts │ ├── Formatter.ts │ ├── LowCodeEditorProvider.ts │ ├── ObjectScriptClassFoldingRangeProvider.ts │ ├── ObjectScriptClassSymbolProvider.ts │ ├── ObjectScriptCodeLensProvider.ts │ ├── ObjectScriptCompletionItemProvider.ts │ ├── ObjectScriptDefinitionProvider.ts │ ├── ObjectScriptDiagnosticProvider.ts │ ├── ObjectScriptFoldingRangeProvider.ts │ ├── ObjectScriptHoverProvider.ts │ ├── ObjectScriptRoutineSymbolProvider.ts │ ├── WorkspaceSymbolProvider.ts │ ├── XmlContentProvider.ts │ └── completion │ │ ├── commands.json │ │ ├── model.ts │ │ ├── structuredSystemVariables.json │ │ ├── systemFunctions.json │ │ └── systemVariables.json ├── test │ ├── runTest.ts │ └── suite │ │ ├── extension.test.ts │ │ └── index.ts ├── utils │ ├── FileProviderUtil.ts │ ├── classDefinition.ts │ ├── documentIndex.ts │ ├── documentPicker.ts │ ├── getCSPToken.ts │ └── index.ts └── web-extension.ts ├── syntaxes ├── objectscript-class.tmLanguage.json ├── objectscript-class_codeblock.json ├── objectscript-csp.tmLanguage.json ├── objectscript-macros.tmLanguage.json ├── objectscript.tmLanguage.json ├── objectscript_codeblock.json └── vscode-objectscript-output.tmLanguage.json ├── test-fixtures └── test.code-workspace ├── test.cls ├── tsconfig.base.json ├── tsconfig.json ├── webpack.config.js └── webview └── elements-1.14.0.js /.artifactignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !*.vsix 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @isc-bsaviano 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | This PR fixes # -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '41 18 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v4 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v3 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v3 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v3 68 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - ".vscode/**" 9 | - ".github/**" 10 | - "**/*.md" 11 | pull_request: 12 | branches: 13 | - master 14 | release: 15 | types: 16 | - released 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.base_ref || github.run_id }} 20 | cancel-in-progress: false 21 | 22 | jobs: 23 | build: 24 | timeout-minutes: 10 25 | runs-on: ubuntu-latest 26 | outputs: 27 | taggedbranch: ${{ steps.find-branch.outputs.taggedbranch }} 28 | steps: 29 | - uses: actions/checkout@v4 30 | - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* 31 | - name: Find which branch the release tag points at 32 | id: find-branch 33 | if: github.event_name == 'release' 34 | shell: bash 35 | run: | 36 | git fetch --depth=1 origin +refs/heads/*:refs/heads/* 37 | set -x 38 | TAGGEDBRANCH=$(git for-each-ref --points-at=${{github.sha}} --format='%(refname:lstrip=2)' refs/heads/) 39 | echo "taggedbranch=$TAGGEDBRANCH" >> $GITHUB_OUTPUT 40 | - name: Set an output 41 | id: set-version 42 | run: | 43 | set -x 44 | VERSION=$(jq -r '.version' package.json | cut -d- -f1) 45 | [ $GITHUB_EVENT_NAME == 'release' ] && VERSION=${{ github.event.release.tag_name }} && VERSION=${VERSION/v/} 46 | CHANGELOG=$(cat CHANGELOG.md | sed -n "/## \[${VERSION}\]/,/## /p" | sed '/^$/d;1d;$d') 47 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" 48 | echo "changelog=$CHANGELOG" >> $GITHUB_OUTPUT 49 | git tag -l | cat 50 | [ $GITHUB_EVENT_NAME == 'push' ] && VERSION+=-beta && VERSION+=.$(($(git tag -l "v$VERSION.*" | sort -nt. -k4 2>/dev/null | tail -1 | cut -d. -f4)+1)) 51 | [ $GITHUB_EVENT_NAME == 'pull_request' ] && VERSION+=-dev.${{ github.event.pull_request.number }} 52 | echo "version=$VERSION" >> $GITHUB_OUTPUT 53 | NAME=$(jq -r '.name' package.json)-$VERSION 54 | echo "name=$NAME" >> $GITHUB_OUTPUT 55 | tmp=$(mktemp) 56 | jq --arg version "$VERSION" '.version = $version' package.json > "$tmp" && mv "$tmp" package.json 57 | mkdir dist 58 | echo $VERSION > meta.version 59 | echo $NAME > meta.name 60 | - name: Use Node.js 61 | uses: actions/setup-node@v4 62 | with: 63 | node-version: 20 64 | - run: npm install 65 | - name: lint 66 | run: npm run lint 67 | - run: npm run compile 68 | - name: npm test 69 | run: xvfb-run npm test 70 | - name: Build package 71 | run: | 72 | npx vsce package -o ${{ steps.set-version.outputs.name }}.vsix 73 | - uses: actions/upload-artifact@v4 74 | if: github.event_name != 'release' 75 | with: 76 | name: ${{ steps.set-version.outputs.name }}.vsix 77 | path: ${{ steps.set-version.outputs.name }}.vsix 78 | - uses: actions/upload-artifact@v4 79 | with: 80 | name: meta 81 | path: | 82 | meta.name 83 | meta.version 84 | beta: 85 | if: (github.event_name == 'push') 86 | runs-on: ubuntu-latest 87 | needs: build 88 | steps: 89 | - uses: actions/download-artifact@v4 90 | with: 91 | name: meta 92 | path: . 93 | - name: Set an output 94 | id: set-version 95 | run: | 96 | set -x 97 | echo "version=`cat meta.version`" >> $GITHUB_OUTPUT 98 | echo "name=`cat meta.name`" >> $GITHUB_OUTPUT 99 | - uses: actions/download-artifact@v4 100 | with: 101 | name: ${{ steps.set-version.outputs.name }}.vsix 102 | - name: Create Release 103 | id: create-release 104 | uses: softprops/action-gh-release@v1 105 | with: 106 | tag_name: v${{ steps.set-version.outputs.version }} 107 | prerelease: ${{ github.event_name != 'release' }} 108 | files: ${{ steps.set-version.outputs.name }}.vsix 109 | token: ${{ secrets.GITHUB_TOKEN }} 110 | publish: 111 | needs: build 112 | if: github.event_name == 'release' && needs.build.outputs.taggedbranch == 'master' 113 | runs-on: ubuntu-latest 114 | steps: 115 | - uses: actions/checkout@v4 116 | with: 117 | ref: master 118 | token: ${{ secrets.TOKEN }} 119 | - uses: actions/download-artifact@v4 120 | with: 121 | name: meta 122 | path: . 123 | - name: Use Node.js 124 | uses: actions/setup-node@v4 125 | with: 126 | node-version: 20 127 | - name: Prepare build 128 | id: set-version 129 | run: | 130 | VERSION=`cat meta.version` 131 | NEXT_VERSION=`cat meta.version | awk -F. '/[0-9]+\./{$NF++;print}' OFS=.` 132 | echo "name=`cat meta.name`" >> $GITHUB_OUTPUT 133 | tmp=$(mktemp) 134 | git config --global user.name 'ProjectBot' 135 | git config --global user.email 'bot@users.noreply.github.com' 136 | jq --arg version "${NEXT_VERSION}-SNAPSHOT" '.version = $version' package.json > "$tmp" && mv "$tmp" package.json 137 | git add package.json 138 | git commit -m 'auto bump version with release' 139 | jq --arg version "$VERSION" '.version = $version' package.json > "$tmp" && mv "$tmp" package.json 140 | npm install 141 | jq 'del(.enableProposedApi,.enabledApiProposals)' package.json > "$tmp" && mv "$tmp" package.json 142 | git push 143 | - name: Build package 144 | run: | 145 | npx vsce package -o ${{ steps.set-version.outputs.name }}.vsix 146 | - name: Upload Release Asset 147 | id: upload-release-asset 148 | uses: softprops/action-gh-release@v1 149 | with: 150 | tag_name: ${{ github.event.release.tag_name }} 151 | files: ${{ steps.set-version.outputs.name }}.vsix 152 | token: ${{ secrets.GITHUB_TOKEN }} 153 | - name: Publish to VSCode Marketplace 154 | run: | 155 | [ -n "${{ secrets.VSCE_TOKEN }}" ] && \ 156 | npx vsce publish --packagePath ${{ steps.set-version.outputs.name }}.vsix -p ${{ secrets.VSCE_TOKEN }} || true 157 | - name: Publish to Open VSX Registry 158 | timeout-minutes: 5 159 | run: | 160 | [ -n "${{ secrets.OVSX_TOKEN }}" ] && \ 161 | npx ovsx publish ${{ steps.set-version.outputs.name }}.vsix --pat ${{ secrets.OVSX_TOKEN }} || true 162 | -------------------------------------------------------------------------------- /.github/workflows/prerelease.yml: -------------------------------------------------------------------------------- 1 | name: CI-prerelease 2 | 3 | on: 4 | push: 5 | branches: 6 | - prerelease 7 | paths-ignore: 8 | - ".vscode/**" 9 | - ".github/**" 10 | - "**/*.md" 11 | pull_request: 12 | branches: 13 | - prerelease 14 | release: 15 | types: 16 | - released 17 | jobs: 18 | build: 19 | timeout-minutes: 10 20 | runs-on: ubuntu-latest 21 | outputs: 22 | taggedbranch: ${{ steps.find-branch.outputs.taggedbranch }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* 26 | - name: Find which branch the release tag points at 27 | id: find-branch 28 | if: github.event_name == 'release' 29 | shell: bash 30 | run: | 31 | git fetch --depth=1 origin +refs/heads/*:refs/heads/* 32 | set -x 33 | TAGGEDBRANCH=$(git for-each-ref --points-at=${{github.sha}} --format='%(refname:lstrip=2)' refs/heads/) 34 | echo "taggedbranch=$TAGGEDBRANCH" >> $GITHUB_OUTPUT 35 | - name: Set an output 36 | id: set-version 37 | run: | 38 | set -x 39 | VERSION=$(jq -r '.version' package.json | cut -d- -f1) 40 | [ $GITHUB_EVENT_NAME == 'release' ] && VERSION=${{ github.event.release.tag_name }} && VERSION=${VERSION/v/} 41 | CHANGELOG=$(cat CHANGELOG.md | sed -n "/## \[${VERSION}\]/,/## /p" | sed '/^$/d;1d;$d') 42 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" 43 | echo "changelog=$CHANGELOG" >> $GITHUB_OUTPUT 44 | git tag -l | cat 45 | [ $GITHUB_EVENT_NAME == 'push' ] && VERSION+=-beta && VERSION+=.$(($(git tag -l "v$VERSION.*" | sort -nt. -k4 2>/dev/null | tail -1 | cut -d. -f4)+1)) 46 | [ $GITHUB_EVENT_NAME == 'pull_request' ] && VERSION+=-dev.${{ github.event.pull_request.number }} 47 | echo "version=$VERSION" >> $GITHUB_OUTPUT 48 | NAME=$(jq -r '.name' package.json)-$VERSION 49 | echo "name=$NAME" >> $GITHUB_OUTPUT 50 | tmp=$(mktemp) 51 | jq --arg version "$VERSION" '.version = $version' package.json > "$tmp" && mv "$tmp" package.json 52 | mkdir dist 53 | echo $VERSION > meta.version 54 | echo $NAME > meta.name 55 | - name: Use Node.js 56 | uses: actions/setup-node@v4 57 | with: 58 | node-version: 20 59 | - run: npm install 60 | - name: lint 61 | run: npm run lint 62 | - run: npm run compile 63 | - name: npm test 64 | run: xvfb-run npm test 65 | - name: Build pre-release package 66 | run: | 67 | npx vsce package --pre-release -o ${{ steps.set-version.outputs.name }}.vsix 68 | - uses: actions/upload-artifact@v4 69 | if: github.event_name != 'release' 70 | with: 71 | name: ${{ steps.set-version.outputs.name }}.vsix 72 | path: ${{ steps.set-version.outputs.name }}.vsix 73 | - uses: actions/upload-artifact@v4 74 | with: 75 | name: meta 76 | path: | 77 | meta.name 78 | meta.version 79 | beta: 80 | if: (github.event_name == 'push') 81 | runs-on: ubuntu-latest 82 | needs: build 83 | steps: 84 | - uses: actions/download-artifact@v4 85 | with: 86 | name: meta 87 | path: . 88 | - name: Set an output 89 | id: set-version 90 | run: | 91 | set -x 92 | echo "version=`cat meta.version`" >> $GITHUB_OUTPUT 93 | echo "name=`cat meta.name`" >> $GITHUB_OUTPUT 94 | - uses: actions/download-artifact@v4 95 | with: 96 | name: ${{ steps.set-version.outputs.name }}.vsix 97 | - name: Create Pre-Release 98 | id: create-release 99 | uses: softprops/action-gh-release@v1 100 | with: 101 | tag_name: v${{ steps.set-version.outputs.version }} 102 | prerelease: ${{ github.event_name != 'release' }} 103 | files: ${{ steps.set-version.outputs.name }}.vsix 104 | token: ${{ secrets.GITHUB_TOKEN }} 105 | publish: 106 | needs: build 107 | if: github.event_name == 'release' && needs.build.outputs.taggedbranch == 'prerelease' 108 | runs-on: ubuntu-latest 109 | steps: 110 | - uses: actions/checkout@v4 111 | with: 112 | ref: prerelease 113 | token: ${{ secrets.TOKEN }} 114 | - uses: actions/download-artifact@v4 115 | with: 116 | name: meta 117 | path: . 118 | - name: Use Node.js 119 | uses: actions/setup-node@v4 120 | with: 121 | node-version: 20 122 | - name: Prepare pre-release build 123 | id: set-version 124 | run: | 125 | VERSION=`cat meta.version` 126 | NEXT_VERSION=`cat meta.version | awk -F. '/[0-9]+\./{$NF++;print}' OFS=.` 127 | echo "name=`cat meta.name`" >> $GITHUB_OUTPUT 128 | tmp=$(mktemp) 129 | git config --global user.name 'ProjectBot' 130 | git config --global user.email 'bot@users.noreply.github.com' 131 | jq --arg version "${NEXT_VERSION}-SNAPSHOT" '.version = $version' package.json > "$tmp" && mv "$tmp" package.json 132 | git add package.json 133 | git commit -m 'auto bump version after pre-release' 134 | jq --arg version "$VERSION" '.version = $version' package.json > "$tmp" && mv "$tmp" package.json 135 | npm install 136 | jq 'del(.enableProposedApi,.enabledApiProposals)' package.json > "$tmp" && mv "$tmp" package.json 137 | git push 138 | - name: Build pre-release package 139 | run: | 140 | npx vsce package --pre-release -o ${{ steps.set-version.outputs.name }}.vsix 141 | - name: Upload Release Asset 142 | id: upload-release-asset 143 | uses: softprops/action-gh-release@v1 144 | with: 145 | tag_name: ${{ github.event.release.tag_name }} 146 | files: ${{ steps.set-version.outputs.name }}.vsix 147 | token: ${{ secrets.GITHUB_TOKEN }} 148 | - name: Publish to VSCode Marketplace 149 | run: | 150 | [ -n "${{ secrets.VSCE_TOKEN }}" ] && \ 151 | npx vsce publish --pre-release --packagePath ${{ steps.set-version.outputs.name }}.vsix -p ${{ secrets.VSCE_TOKEN }} || true 152 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea 3 | *.iml 4 | .vscode-test/ 5 | out/ 6 | dist/ 7 | *.vsix 8 | vscode*.d.ts 9 | test-fixtures 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6, 3 | "asi": true 4 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "auto", 3 | "singleQuote": false, 4 | "trailingComma": "es5", 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | "EditorConfig.EditorConfig" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension Alone", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--disable-extensions", 12 | "--extensionDevelopmentPath=${workspaceRoot}" 13 | ], 14 | "stopOnEntry": false, 15 | "sourceMaps": true, 16 | "outFiles": [ 17 | "${workspaceRoot}/dist/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: webpack" 20 | }, 21 | { 22 | "name": "Launch Extension", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceRoot}" 28 | ], 29 | "stopOnEntry": false, 30 | "sourceMaps": true, 31 | "outFiles": [ 32 | "${workspaceRoot}/dist/**/*.js" 33 | ], 34 | "preLaunchTask": "npm: webpack" 35 | }, 36 | { 37 | "name": "Extension Tests", 38 | "type": "extensionHost", 39 | "request": "launch", 40 | "runtimeExecutable": "${execPath}", 41 | "args": [ 42 | "${workspaceFolder}/test-fixtures/test.code-workspace", 43 | "--disable-extensions", 44 | "--extensionDevelopmentPath=${workspaceFolder}", 45 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 46 | ], 47 | "outFiles": [ 48 | "${workspaceFolder}/out/test/**/*.js" 49 | ], 50 | "preLaunchTask": "npm: test-compile" 51 | }, 52 | { 53 | "name": "Run Web Extension in VS Code", 54 | "type": "pwa-extensionHost", 55 | "debugWebWorkerHost": true, 56 | "request": "launch", 57 | "args": [ 58 | "--extensionDevelopmentPath=${workspaceFolder}", 59 | "--extensionDevelopmentKind=web" 60 | ], 61 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 62 | "preLaunchTask": "npm: webpack" 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.tabSize": 2, 5 | "editor.formatOnSave": true 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "2.0.0", 12 | "tasks": [ 13 | { 14 | "type": "npm", 15 | "script": "webpack-dev", 16 | "presentation": { 17 | "reveal": "never" 18 | }, 19 | "group": { 20 | "isDefault": true, 21 | "kind": "build" 22 | }, 23 | "isBackground": true, 24 | "problemMatcher": "$eslint-stylish" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | # Run 'vsce ls' to verify that the following lines exclude everything not needed in the VSIX. 2 | 3 | ** 4 | !dist/*.js 5 | !dist/*.txt 6 | !snippets/*.json 7 | !images/*.svg 8 | !images/*.png 9 | !syntaxes/*.json 10 | !webview/*.js 11 | !CHANGELOG.md 12 | !LICENSE 13 | !README.md 14 | !package.json 15 | !package.nls.json 16 | !language-configuration.json 17 | !*.jsonc 18 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [raj.singh@intersystems.com]. 65 | 66 | All complaints will be reviewed and investigated promptly and fairly. 67 | 68 | All community leaders are obligated to respect the privacy and security of the 69 | reporter of any incident. 70 | 71 | ## Enforcement Guidelines 72 | 73 | Community leaders will follow these Community Impact Guidelines in determining 74 | the consequences for any action they deem in violation of this Code of Conduct: 75 | 76 | ### 1. Correction 77 | 78 | **Community Impact**: Use of inappropriate language or other behavior deemed 79 | unprofessional or unwelcome in the community. 80 | 81 | **Consequence**: A private, written warning from community leaders, providing 82 | clarity around the nature of the violation and an explanation of why the 83 | behavior was inappropriate. A public apology may be requested. 84 | 85 | ### 2. Warning 86 | 87 | **Community Impact**: A violation through a single incident or series 88 | of actions. 89 | 90 | **Consequence**: A warning with consequences for continued behavior. No 91 | interaction with the people involved, including unsolicited interaction with 92 | those enforcing the Code of Conduct, for a specified period of time. This 93 | includes avoiding interactions in community spaces as well as external channels 94 | like social media. Violating these terms may lead to a temporary or 95 | permanent ban. 96 | 97 | ### 3. Temporary Ban 98 | 99 | **Community Impact**: A serious violation of community standards, including 100 | sustained inappropriate behavior. 101 | 102 | **Consequence**: A temporary ban from any sort of interaction or public 103 | communication with the community for a specified period of time. No public or 104 | private interaction with the people involved, including unsolicited interaction 105 | with those enforcing the Code of Conduct, is allowed during this period. 106 | Violating these terms may lead to a permanent ban. 107 | 108 | ### 4. Permanent Ban 109 | 110 | **Community Impact**: Demonstrating a pattern of violation of community 111 | standards, including sustained inappropriate behavior, harassment of an 112 | individual, or aggression toward or disparagement of classes of individuals. 113 | 114 | **Consequence**: A permanent ban from any sort of public interaction within 115 | the community. 116 | 117 | ## Attribution 118 | 119 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 120 | version 2.0, available at 121 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 122 | 123 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 124 | enforcement ladder](https://github.com/mozilla/diversity). 125 | 126 | homepage: . 127 | 128 | For answers to common questions about this code of conduct, see the FAQ at 129 | . Translations are available at 130 | . 131 | 132 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the ObjectScript extension for Visual Studio Code 2 | 3 | ## Contributing a pull request 4 | 5 | ### Prerequisites 6 | 7 | 1. [Node.js](https://nodejs.org/) 18.x 8 | 1. Windows, macOS, or Linux 9 | 1. [Visual Studio Code](https://code.visualstudio.com/) 10 | 1. The following VS Code extensions: 11 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 12 | - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 13 | - [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) 14 | 15 | ### Setup 16 | 17 | ```shell 18 | git clone https://github.com/intersystems-community/vscode-objectscript 19 | cd vscode-objectscript 20 | npm install 21 | ``` 22 | 23 | ### Errors and Warnings 24 | 25 | TypeScript errors and warnings will be displayed in the `PROBLEMS` panel of Visual Studio Code. 26 | 27 | ### Editing code snippets 28 | 29 | Code snippets are defined in files in the /snippets/ folder: 30 | 31 | * objectscript-class.json - snippets for class definition context 32 | * objectscript.json - snippets for objectscript context 33 | 34 | Snippets syntax is described [here](https://code.visualstudio.com/docs/editor/userdefinedsnippets). 35 | 36 | ### Run dev build and validate your changes 37 | 38 | To test changes, open the `vscode-objectscript` folder in VSCode. 39 | Then, open the debug panel by clicking the `Run and Debug` icon on the Activity Bar, select the `Launch Extension` 40 | option from the top menu, and click start. A new window will launch with the title 41 | `[Extension Development Host]`. Do your testing here. 42 | 43 | If you want to disable all other extensions when testing in the Extension Development Host, choose the `Launch Extension Alone` option instead. 44 | 45 | ### Pull requests 46 | 47 | Work should be done on a unique branch -- not the master branch. Pull requests require the approval of two PMC members, as described in the [Governance document](GOVERNANCE.md). PMC review is often high level, so in addition to that, you should request a review by someone familiar with the technical details of your particular pull request. 48 | 49 | We do expect CI to be passing for a pull request before we will consider merging it. CI executed by pull requests will produce a `vsix` file, which can be downloaded and installed manually to test proposed functionality. 50 | 51 | ## Beta versions 52 | 53 | Any change to `master` branch will call CI, which will produce [beta release](https://github.com/intersystems-community/vscode-objectscript/releases), which can be manually installed. 54 | 55 | ## Local Build 56 | 57 | Steps to build the extension on your machine once you've cloned the repo: 58 | 59 | ```bash 60 | > npm install -g vsce 61 | # Perform the next steps in the vscode-objectscript folder. 62 | > npm install 63 | > npm run package 64 | ``` 65 | 66 | Resulting in a `vscode-objectscript-$VERSION.vsix` file in your `vscode-objectscript` folder. 67 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # vscode-objectscript Governance 2 | 3 | ## Overview 4 | 5 | This is a consensus-based community project with strong leadership direction provided by the project management committee (PMC). Anyone with an interest in the project can join the community, contribute code, and participate in the decision making process. This document describes how that participation takes place and how to set about earning merit within the project community. 6 | 7 | ## Roles and Responsibilities 8 | 9 | ### Users 10 | 11 | Users are community members who have a need for the project. They are the most important members of the community and without them the project would have no purpose. Anyone can be a user; there are no special requirements. 12 | 13 | The project asks its users to participate in the project and community as much as possible. User contributions enable the project team to ensure that they are satisfying the needs of those users. Common user contributions include (but are not limited to): 14 | 15 | - evangelising about the project (e.g. a link on a website and word-of-mouth awareness raising) 16 | - informing developers of strengths and weaknesses from a new user perspective 17 | - providing moral support (a ‘thank you’ goes a long way) 18 | - providing financial support (the software is open source, but its developers need to eat) 19 | 20 | Users who continue to engage with the project and its community will often become more and more involved. Such users may find themselves becoming contributors, as described in the next section. 21 | 22 | ### Contributors 23 | 24 | Contributors are community members who contribute in concrete ways to the project. Anyone can become a contributor, and contributions can take many forms, as detailed in a separate document. There is no expectation of commitment to the project, no specific skill requirements and no selection process. 25 | 26 | In addition to their actions as users, contributors may also find themselves doing one or more of the following: 27 | 28 | - supporting new users (existing users are often the best people to support new users) 29 | - reporting bugs 30 | - identifying requirements 31 | - providing graphics and web design 32 | - programming 33 | - assisting with project infrastructure 34 | - writing documentation 35 | - fixing bugs 36 | - adding features 37 | 38 | Contributors engage with the project through the issue tracker, or by writing or editing documentation. They submit changes to the project itself via pull requests, which will be considered for inclusion in the project by existing committers (see next section). Pull requests with full test coverage will receive greater consideration than those without. 39 | 40 | As contributors gain experience and familiarity with the project, their profile within, and commitment to, the community will increase. At some stage, they may find themselves being nominated for committership. 41 | 42 | ### Committers 43 | 44 | Committers are contributors who have made several valuable contributions to the project and are now relied upon to both write code directly to the repository and screen the contributions of others. In many cases they are programmers but it is also possible that they contribute in a different role. Typically, a committer will focus on a specific aspect of the project, and will bring a level of expertise and understanding that earns them the respect of the community and the PMC. The role of committer is not an official one, it is simply a position that influential members of the community will find themselves in as the PMC looks to them for guidance and support. 45 | 46 | Committers have no authority over the overall direction of the project. However, they do have the ear of the PMC. It is a committer’s job to ensure that the PMC is aware of the community’s needs and collective objectives, and to help develop or elicit appropriate contributions to the project. Often, committers are given informal control over their specific areas of responsibility, and are assigned rights to directly modify certain areas of the source code. That is, although committers do not have explicit decision-making authority, they will often find that their actions are synonymous with the decisions made by the PMC. 47 | 48 | ### Project management committee 49 | 50 | The project management committee (PMC) sets the strategic objectives of the project and communicates these clearly to the community. It also has to understand the community as a whole and strive to satisfy as many conflicting needs as possible, while ensuring that the project's long term success. 51 | 52 | It consists of those individuals identified as ‘project owners’ on the development site. The PMC has additional responsibilities over and above those of a committer. These responsibilities ensure the smooth running of the project. PMC members are expected to review code contributions, participate in strategic planning, approve changes to the governance model and manage the copyrights within the project outputs. 53 | 54 | The PMC votes on new committers. It also makes decisions when community consensus cannot be reached. In addition, the PMC has access to the project’s private mailing list and its archives. This list is used for sensitive issues, such as votes for new committers and legal matters that cannot be discussed in public. It may also be used for project management or planning. 55 | 56 | Membership of the PMC is by invitation from the existing PMC members. A nomination will result in discussion and then a vote by the existing PMC members. PMC membership votes are subject to consensus approval of the current PMC members. 57 | 58 | ### PMC Chair 59 | 60 | The PMC Chair is a single individual, voted for by the PMC members. Once someone has been appointed Chair, they remain in that role until they choose to retire, or the PMC casts a two-thirds majority vote to remove them. 61 | 62 | The PMC Chair has no additional authority over other members of the PMC: the role is one of coordinator and facilitator. The Chair is also expected to ensure that all governance processes are adhered to, and has the casting vote when the project fails to reach consensus. 63 | 64 | ## Contribution process 65 | 66 | ## Decision making process 67 | 68 | In order to ensure that the project is not bogged down by endless discussion and continual voting, the project operates a policy of lazy consensus. This allows the majority of decisions to be made without resorting to a formal vote. 69 | 70 | ### Lazy consensus 71 | 72 | Decision making typically involves the following steps: 73 | 74 | - Proposal 75 | - Discussion 76 | - Vote (if consensus is not reached through discussion) 77 | - Decision 78 | 79 | Any community member can make a proposal for consideration by the community. In order to initiate a discussion about a new idea, they should file an issue and optionally submit a patch implementing the proposal. This will prompt a review and, if necessary, a discussion of the idea. The goal of this review and discussion is to gain approval for the contribution. Since most people in the project community have a shared vision, there is often little need for discussion in order to reach consensus. 80 | 81 | In general, as long as nobody explicitly opposes a proposal or patch, it is recognised as having the support of the community. This is called lazy consensus - that is, those who have not stated their opinion explicitly have implicitly agreed to the implementation of the proposal. 82 | 83 | At least 72 hours notice will be provided before assuming that there are no objections to the proposal. This requirement ensures that everyone is given enough time to read, digest and respond to the proposal. This time period is chosen so as to be as inclusive as possible of all participants, regardless of their location and time commitments. 84 | 85 | ### Voting 86 | 87 | Not all decisions can be made using lazy consensus. Issues such as those affecting the strategic direction or legal standing of the project must gain explicit approval in the form of a vote. Every member of the community is encouraged to express their opinions in all discussion and all votes. However, only project committers and/or PMC members (as defined above) have binding votes for the purposes of decision making. 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Original work Copyright (c) 2018 doublefint 4 | Modified work Copyright 2018 Dmitry Maslennikov 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Known Vulnerabilities](https://snyk.io/test/github/intersystems-community/vscode-objectscript/badge.svg)](https://snyk.io/test/github/intersystems-community/vscode-objectscript) 2 | ![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/intersystems-community.vscode-objectscript.svg) 3 | [![](https://img.shields.io/visual-studio-marketplace/i/intersystems-community.vscode-objectscript.svg)](https://marketplace.visualstudio.com/items?itemName=intersystems-community.vscode-objectscript) 4 | 5 | [![](https://img.shields.io/badge/InterSystems-IRIS-blue.svg)](https://www.intersystems.com/products/intersystems-iris/) 6 | [![](https://img.shields.io/badge/InterSystems-Caché-blue.svg)](https://www.intersystems.com/products/cache/) 7 | [![](https://img.shields.io/badge/InterSystems-Ensemble-blue.svg)](https://www.intersystems.com/products/ensemble/) 8 | 9 | # InterSystems ObjectScript extension for VS Code 10 | 11 | > **Note:** The best way to install and use this extension is by installing the [InterSystems ObjectScript Extension Pack](https://marketplace.visualstudio.com/items?itemName=intersystems-community.objectscript-pack) and following the [documentation here](https://docs.intersystems.com/components/csp/docbook/DocBook.UI.Page.cls?KEY=GVSCO). 12 | 13 | [InterSystems®](http://www.intersystems.com) ObjectScript language support for Visual Studio Code, from the [InterSystems Developer Community](https://community.intersystems.com/). 14 | 15 | - Documentation on the [InterSystems Documentation site](https://docs.intersystems.com/components/csp/docbook/DocBook.UI.Page.cls?KEY=GVSCO). 16 | 17 | - Guidance on [reporting issues](https://docs.intersystems.com/components/csp/docbook/DocBook.UI.Page.cls?KEY=GVSCO_reporting). 18 | 19 | ## Features 20 | 21 | - InterSystems ObjectScript code highlighting support. 22 | - Debugging ObjectScript code. 23 | - Intellisense support for commands, system functions, and class members. 24 | - Export of existing server sources into a working folder: 25 | - open Command Palette (F1 or /Ctrl+Shift+P) 26 | - start typing 'ObjectScript' 27 | - choose `ObjectScript: Export Code from Server` 28 | - press Enter 29 | - Save and compile a class: 30 | - press /Ctrl+F7 31 | - or, select `ObjectScript: Import and Compile Current File` from Command Palette 32 | - Direct access to edit or view server code in the VS Code Explorer via `isfs` and `isfs-readonly` FileSystemProviders (e.g. using a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces)). Server-side source control is respected. 33 | - Server Explorer view (InterSystems: Explorer) with ability to export items to your working folder. 34 | - Integration with with [InterSystems Server Manager](https://marketplace.visualstudio.com/items?itemName=intersystems-community.servermanager) for secure storage of connection passwords. 35 | 36 | ## Installation 37 | 38 | Install [Visual Studio Code](https://code.visualstudio.com/) first. 39 | 40 | Then to get a set of extensions that collaborate to bring you a great ObjectScript development experience, install the [InterSystems ObjectScript Extension Pack](https://marketplace.visualstudio.com/items?itemName=intersystems-community.objectscript-pack). 41 | 42 | When you install an extension pack VS Code installs any of its members that you don't already have. Then if you ever need to switch off all of those extensions (for example, in a VS Code workspace on a non-ObjectScript project) simply disable the extension pack at the desired level. Member extensions can still be managed individually. 43 | 44 | Open VS Code. Go to Extensions view (/Ctrl+Shift+X), use the search string **@id:intersystems-community.objectscript-pack** and install it. 45 | 46 | ## Enable Proposed APIs 47 | 48 | This extension is able to to take advantage of some VS Code APIs that have not yet been finalized. 49 | 50 | The additional features (and the APIs used) are: 51 | - Server-side [searching across files](https://code.visualstudio.com/docs/editor/codebasics#_search-across-files) being accessed using isfs (_TextSearchProvider_) 52 | - [Quick Open](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_quick-open) of isfs files (_FileSearchProvider_). 53 | 54 | To unlock these features (optional): 55 | 56 | 1. Download and install a beta version from GitHub. This is necessary because Marketplace does not allow publication of extensions that use proposed APIs. 57 | - Go to https://github.com/intersystems-community/vscode-objectscript/releases 58 | - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.0.2`, look for `3.0.3-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. 59 | - Download the VSIX file (for example `vscode-objectscript-3.0.3-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. 60 | 61 | 2. From [Command Palette](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_command-palette) choose `Preferences: Configure Runtime Arguments`. 62 | 3. In the argv.json file that opens, add this line (required for both Stable and Insiders versions of VS Code): 63 | ```json 64 | "enable-proposed-api": ["intersystems-community.vscode-objectscript"] 65 | ``` 66 | 4. Exit VS Code and relaunch it. 67 | 5. Verify that the ObjectScript channel of the Output panel reports this: 68 | ``` 69 | intersystems-community.vscode-objectscript version X.Y.Z-beta.1 activating with proposed APIs available. 70 | ``` 71 | 72 | After a subsequent update of the extension from Marketplace you will only have to download and install the new `vscode-objectscript-X.Y.Z-beta.1` VSIX. None of the other steps above are needed again. 73 | 74 | ## Notes 75 | 76 | - Connection-related output appears in the 'Output' view while switched to the 'ObjectScript' channel using the drop-down menu on the view titlebar. 77 | 78 | - The `/api/atelier/` web application used by this extension usually requires the authenticated user to have Use permission on the %Development resource ([read more](https://community.intersystems.com/post/using-atelier-rest-api)). One way is to assign the %Developer role to the user. 79 | 80 | - If you are getting `ERROR # 5540: SQLCODE: -99 Message: User xxx is not privileged for the operation` when you try to get or refresh lists of classes, routines or includes, then grant user xxx (or a SQL role they hold) Execute permission for the following SQL Procedure in the target namespace. 81 | 82 | ```SQL 83 | GRANT EXECUTE ON %Library.RoutineMgr_StudioOpenDialog TO xxx 84 | ``` 85 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import globals from "globals"; 3 | import tsParser from "@typescript-eslint/parser"; 4 | import path from "node:path"; 5 | import { fileURLToPath } from "node:url"; 6 | import js from "@eslint/js"; 7 | import { FlatCompat } from "@eslint/eslintrc"; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all 15 | }); 16 | 17 | export default [{ 18 | ignores: ["**/vscode.d.ts", "**/vscode.proposed.d.ts"], 19 | }, ...compat.extends( 20 | "eslint:recommended", 21 | "plugin:@typescript-eslint/recommended", 22 | "plugin:@typescript-eslint/eslint-recommended", 23 | "prettier", 24 | "plugin:prettier/recommended", 25 | ), { 26 | plugins: { 27 | "@typescript-eslint": typescriptEslint, 28 | }, 29 | 30 | languageOptions: { 31 | globals: { 32 | ...globals.node, 33 | }, 34 | 35 | parser: tsParser, 36 | ecmaVersion: 2018, 37 | sourceType: "module", 38 | 39 | parserOptions: { 40 | project: "./tsconfig.json", 41 | }, 42 | }, 43 | 44 | rules: { 45 | "@typescript-eslint/no-explicit-any": 0, 46 | "@typescript-eslint/explicit-function-return-type": 0, 47 | "@typescript-eslint/no-unused-vars": 0, 48 | "@typescript-eslint/no-require-imports": 0, 49 | "@typescript-eslint/no-unused-expressions": 0, 50 | }, 51 | }]; -------------------------------------------------------------------------------- /images/fileIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Product icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intersystems-community/vscode-objectscript/dad67460cd7e809758e88e7d530e64fc291a0a26/images/logo.png -------------------------------------------------------------------------------- /language-configuration-class.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": ["/*", "*/"] 5 | }, 6 | "brackets": [["{", "}"], ["(", ")"], ["[", "]"]], 7 | // symbols that are auto closed when typing 8 | "autoClosingPairs": [["{", "}"], ["(", ")"], ["[", "]"], ["\"", "\""]], 9 | // symbols that that can be used to surround a selection 10 | "surroundingPairs": [["{", "}"], ["(", ")"], ["\"", "\""], ["[", "]"]], 11 | "indentationRules": { 12 | "increaseIndentPattern": "^((Class|Client)?Method|Query|XData|Storage|Trigger)", 13 | "decreaseIndentPattern": "^}" 14 | }, 15 | "folding": { 16 | "markers": { 17 | "start": "^((Class|Client)?Method|Query|XData|Storage|Trigger)", 18 | "end": "^}" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#;", 4 | "blockComment": ["/*", "*/"] 5 | }, 6 | // symbols used as brackets 7 | "brackets": [["{", "}"], ["(", ")"]], 8 | // symbols that are auto closed when typing 9 | "autoClosingPairs": [["{", "}"], ["(", ")"], ["\"", "\""]], 10 | // symbols that that can be used to surround a selection 11 | "surroundingPairs": [["{", "}"], ["(", ")"], ["\"", "\""]], 12 | "indentationRules": { 13 | "increaseIndentPattern": "{", 14 | "decreaseIndentPattern": "}" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /snippets/objectscript-class.json: -------------------------------------------------------------------------------- 1 | { 2 | "ClassMethod definition": { 3 | "prefix": "ClassMethod", 4 | "body": [ 5 | "/// ${1:Description}", 6 | "ClassMethod ${2:MethodName}($3) As ${4:%Status}", 7 | "{", 8 | "\tSet ${5:sc} = \\$\\$\\$OK", 9 | "\t${0:// do something}", 10 | "\tReturn $5", 11 | "}" 12 | ] 13 | }, 14 | "Method definition": { 15 | "prefix": "Method", 16 | "body": [ 17 | "/// ${1:Description}", 18 | "Method ${2:MethodName}($3) As ${4:%Status}", 19 | "{", 20 | "\tSet ${5:sc} = \\$\\$\\$OK", 21 | "\t${0:// do something}", 22 | "\tReturn $5", 23 | "}" 24 | ] 25 | }, 26 | "Property": { 27 | "prefix": "Property", 28 | "body": [ 29 | "/// ${1:Description}", 30 | "Property ${2:PropertyName} As ${3:%String};" 31 | ] 32 | }, 33 | "Projection": { 34 | "prefix": "Projection", 35 | "body": [ 36 | "/// ${1:Description}", 37 | "Projection ${2:ProjectionName} As ${3:PackageName.ProjectionClassName};" 38 | ] 39 | }, 40 | "Unique Property": { 41 | "prefix": ["Unique", "Property"], 42 | "body": [ 43 | "/// ${1:Description}", 44 | "Property ${2:PropertyName} As ${3:%String};", 45 | "", 46 | "Index $2Index On $2 [Unique];" 47 | ] 48 | }, 49 | "Always-Computed Property": { 50 | "prefix": ["Computed", "Property"], 51 | "body" : [ 52 | "/// ${1:Description}", 53 | "Property ${2:PropertyName} As ${3:%String} [Calculated, SqlComputed, SqlComputeCode =", 54 | "\t{Set {$2} = {${4:expression}}}", 55 | "];" 56 | ] 57 | }, 58 | "Date/Time Property": { 59 | "prefix": ["Date", "Time", "Property"], 60 | "body" : [ 61 | "/// ${1:Description}", 62 | "Property ${2:PropertyName} As ${3|%Date,%Time|}(MINVAL = $4, MAXVAL = $5);" 63 | ] 64 | }, 65 | "Parameter": { 66 | "prefix": "Parameter", 67 | "body": [ 68 | "/// ${1:Description}", 69 | "Parameter ${2:PARAMETERNAME} = \"$0\";" 70 | ] 71 | }, 72 | "Index": { 73 | "prefix": "Index", 74 | "body": [ 75 | "/// ${1:Description}", 76 | "Index ${2:IndexName} On ${3:property};" 77 | ] 78 | }, 79 | "Unique Index": { 80 | "prefix": "Index", 81 | "body": [ 82 | "/// ${1:Description}", 83 | "Index ${2:IndexName} On ${3:property} [Unique];" 84 | ], 85 | "description": "Unique Index" 86 | }, 87 | "Basic Class Query": { 88 | "prefix":["Query"], 89 | "body":[ 90 | "/// ${1:Description}", 91 | "Query ${2:QueryName}($3) As %SQLQuery [ SqlProc ]", 92 | "{", 93 | "\tSELECT ${4:select-items}", 94 | "\tFROM ${5:table-refs}", 95 | "\tWHERE ${6:condition-expression}", 96 | "\tORDER BY ${7:ordering-items}", 97 | "}" 98 | ], 99 | "description": "Basic class query (%SQLQuery)" 100 | }, 101 | "Custom Class Query": { 102 | "prefix":["Query"], 103 | "body":[ 104 | "/// ${1:Description}", 105 | "Query ${2:QueryName}($3) As %Query(ROWSPEC = \"$4\") [ SqlProc ]", 106 | "{", 107 | "}", 108 | "", 109 | "ClassMethod ${2:QueryName}Execute(ByRef qHandle As %Binary${3/(\\s)|(.*)/${2:+, }$2/}) As %Status", 110 | "{", 111 | "\tQuit \\$\\$\\$OK", 112 | "}", 113 | "", 114 | "ClassMethod ${2:QueryName}Close(ByRef qHandle As %Binary) As %Status [ PlaceAfter = ${2:QueryName}Execute ]", 115 | "{", 116 | "\tQuit \\$\\$\\$OK", 117 | "}", 118 | "", 119 | "ClassMethod ${2:QueryName}Fetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = ${2:QueryName}Execute ]", 120 | "{", 121 | "\tQuit \\$\\$\\$OK", 122 | "}" 123 | ], 124 | "description": "Custom class query (%Query)" 125 | }, 126 | "Trigger": { 127 | "prefix": "Trigger", 128 | "body": [ 129 | "/// ${1:Description}", 130 | "Trigger ${2:TriggerName} [Event=${3|INSERT,UPDATE,DELETE|}, Time=${4|BEFORE,AFTER|}, Foreach=${5|row/object,row,statement|}]", 131 | "{", 132 | "\t${0:// do something}", 133 | "}" 134 | ], 135 | "description": "Trigger" 136 | }, 137 | "ForeignKey": { 138 | "prefix": "ForeignKey", 139 | "body": [ 140 | "/// ${1:Description}", 141 | "ForeignKey ${2:ForeignKeyName}(${3:property}) References ${4:referencedClass}(${5:refIndex});" 142 | ], 143 | "description": "ForeignKey" 144 | }, 145 | "Relationship": { 146 | "prefix": ["Relationship"], 147 | "body": [ 148 | "/// ${1:Description}", 149 | "Relationship ${2:RelationshipName} As ${3:classname} [ Cardinality = ${4|one,many,parent,children|}, Inverse = ${5:correspondingProperty} ];" 150 | ], 151 | "description": "Relationship" 152 | }, 153 | "XData": { 154 | "prefix": "XData", 155 | "body": [ 156 | "/// ${1:Description}", 157 | "XData ${2:XDataName}", 158 | "{", 159 | "$0", 160 | "}" 161 | ], 162 | "description": "XData" 163 | }, 164 | "Production": { 165 | "prefix": ["Production","Interoperability","ClassProduction"], 166 | "body": [ 167 | "/// ${1:Description}", 168 | "Class ${2:${TM_DIRECTORY/^.+[\\/\\\\](.*)$/$1/}.$TM_FILENAME_BASE} Extends Ens.Production", 169 | "{", 170 | "", 171 | "XData ProductionDefinition", 172 | "{", 173 | "\t", 174 | "\t\t2", 175 | "\t\t", 176 | "\t", 177 | "}", 178 | "}" 179 | ], 180 | "description": "Production Definition" 181 | }, 182 | "Request": { 183 | "prefix": ["Request","Interoperability","ClassRequest"], 184 | "body": [ 185 | "/// ${1:Description}", 186 | "Class ${2:${TM_DIRECTORY/^.+[\\/\\\\](.*)$/$1/}.$TM_FILENAME_BASE} Extends Ens.Request", 187 | "{", 188 | "$0", 189 | "}" 190 | ], 191 | "description": "Request Message Definition" 192 | }, 193 | "Response": { 194 | "prefix": ["Response","Interoperability","ClassResponse"], 195 | "body": [ 196 | "/// ${1:Description}", 197 | "Class ${2:${TM_DIRECTORY/^.+[\\/\\\\](.*)$/$1/}.$TM_FILENAME_BASE} Extends Ens.Response", 198 | "{", 199 | "$0", 200 | "}" 201 | ], 202 | "description": "Response Message Definition" 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /snippets/objectscript-int.json: -------------------------------------------------------------------------------- 1 | { 2 | "ForOrder": { 3 | "prefix": ["For"], 4 | "body": [ 5 | "Set ${1:key} = \"\"", 6 | "For {", 7 | "\tSet $1 = \\$ORDER(${2:array}($1))", 8 | "\tQuit:$1=\"\"", 9 | "\t${3:// process $2($1)}", 10 | "}" 11 | ], 12 | "description": "Iterate array with $Order" 13 | }, 14 | "SQL Statement": { 15 | "prefix": ["sql"], 16 | "body": [ 17 | "Set rs = ##class(%SQL.Statement).%ExecDirect(,\"SELECT ${1:*} FROM ${2:table}\")", 18 | "While rs.%Next() {", 19 | "\t${0:Write rs.ID, !}", 20 | "}" 21 | ], 22 | "description": "Prepare and execute SQL Query, then iterate result set 'rs'" 23 | }, 24 | "For": { 25 | "prefix": ["For"], 26 | "body": [ 27 | "For ${1:i} = ${2:1}:${3:1}:${4:9} {", 28 | "\t${0:Write $1, !}", 29 | "}" 30 | ], 31 | "description": "Typical For loop" 32 | }, 33 | "For Each": { 34 | "prefix": ["For"], 35 | "body": [ 36 | "For ${1:value} = \"${2:Red}\",\"${3:Green}\",\"${4:Blue}\" {", 37 | "\t${0:Write $1, !}", 38 | "}" 39 | ], 40 | "description": "Loop through series of values" 41 | }, 42 | "Do While": { 43 | "prefix": ["Do", "While"], 44 | "body": [ 45 | "Do {", 46 | "\t$0", 47 | "} While (${1:1 /* condition */})" 48 | ], 49 | "description": "Do While loop" 50 | }, 51 | "While": { 52 | "prefix": ["While"], 53 | "body": [ 54 | "While (${1:1 /* condition */}) {", 55 | "\t$0", 56 | "}" 57 | ], 58 | "description": "While loop" 59 | }, 60 | "Try Catch": { 61 | "prefix": ["Try"], 62 | "body": [ 63 | "Try {", 64 | "\t$0", 65 | "}", 66 | "Catch ex {", 67 | "\tSet tSC=ex.AsStatus()", 68 | "}" 69 | ], 70 | "description": "Try Catch" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /snippets/objectscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "ForOrder": { 3 | "prefix": ["For"], 4 | "body": [ 5 | "Set ${1:key} = \"\"", 6 | "For {", 7 | "\tSet $1 = \\$ORDER(${2:array}($1))", 8 | "\tQuit:$1=\"\"", 9 | "\t${3:// process $2($1)}", 10 | "}" 11 | ], 12 | "description": "Iterate array with $Order" 13 | }, 14 | "SQL Statement": { 15 | "prefix": ["sql"], 16 | "body": [ 17 | "Set rs = ##class(%SQL.Statement).%ExecDirect(,\"SELECT ${1:*} FROM ${2:table}\")", 18 | "While rs.%Next() {", 19 | "\t${0:Write rs.ID, !}", 20 | "}" 21 | ], 22 | "description": "Prepare and execute SQL Query, then iterate result set 'rs'" 23 | }, 24 | "For": { 25 | "prefix": ["For"], 26 | "body": [ 27 | "For ${1:i} = ${2:1}:${3:1}:${4:9} {", 28 | "\t${0:Write $1, !}", 29 | "}" 30 | ], 31 | "description": "Typical For loop" 32 | }, 33 | "For Each": { 34 | "prefix": ["For"], 35 | "body": [ 36 | "For ${1:value} = \"${2:Red}\",\"${3:Green}\",\"${4:Blue}\" {", 37 | "\t${0:Write $1, !}", 38 | "}" 39 | ], 40 | "description": "Loop through series of values" 41 | }, 42 | "Do While": { 43 | "prefix": ["Do", "While"], 44 | "body": [ 45 | "Do {", 46 | "\t$0", 47 | "} While (${1:1 /* condition */})" 48 | ], 49 | "description": "Do While loop" 50 | }, 51 | "While": { 52 | "prefix": ["While"], 53 | "body": [ 54 | "While (${1:1 /* condition */}) {", 55 | "\t$0", 56 | "}" 57 | ], 58 | "description": "While loop" 59 | }, 60 | "ThrowOnError": { 61 | "prefix": ["$$$ThrowOnError"], 62 | "body": ["\\$\\$\\$ThrowOnError(##class(${1:class}).${2:method}())"], 63 | "description": "Invoke classmethod, store result in variable 'sc', and Throw if an error" 64 | }, 65 | "Try Catch": { 66 | "prefix": ["Try"], 67 | "body": [ 68 | "Try {", 69 | "\t$0", 70 | "}", 71 | "Catch ex {", 72 | "\tSet tSC=ex.AsStatus()", 73 | "}" 74 | ], 75 | "description": "Try Catch" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/api/atelier.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Atelier API 3 | */ 4 | 5 | interface ResponseStatus { 6 | errors: string[]; 7 | summary: string; 8 | } 9 | 10 | interface Content { 11 | content: T; 12 | } 13 | 14 | export interface Response { 15 | status: ResponseStatus; 16 | console: string[]; 17 | result: T; 18 | /** Value of the `Retry-After` response header, if present */ 19 | retryafter?: string; 20 | } 21 | 22 | interface ServerInfoFeature { 23 | name: string; 24 | enabled: string; 25 | } 26 | 27 | export interface UserAction { 28 | action: number; 29 | target: string; 30 | message: string; 31 | reload: boolean; 32 | doc: any; 33 | errorText: string; 34 | } 35 | 36 | export interface Document { 37 | name: string; 38 | db: string; 39 | ts: string; 40 | upd: boolean; 41 | cat: "RTN" | "CLS" | "CSP" | "OTH"; 42 | status: string; 43 | enc: boolean; 44 | flags: number; 45 | content: string[] | Buffer; 46 | ext?: UserAction | UserAction[]; 47 | } 48 | 49 | export interface ServerInfo { 50 | version: string; 51 | id: string; 52 | api: number; 53 | features: ServerInfoFeature[]; 54 | namespaces: string[]; 55 | } 56 | 57 | export interface SearchMatch { 58 | text: string; 59 | line?: string | number; 60 | member?: string; 61 | attr?: string; 62 | attrline?: number; 63 | } 64 | 65 | export interface SearchResult { 66 | doc: string; 67 | matches: SearchMatch[]; 68 | } 69 | 70 | export interface AtelierJob { 71 | pid: number; 72 | namespace: string; 73 | routine: string; 74 | state: string; 75 | device: string; 76 | } 77 | 78 | interface AsyncCompileRequest { 79 | request: "compile"; 80 | documents: string[]; 81 | source?: boolean; 82 | flags?: string; 83 | } 84 | 85 | interface AsyncSearchRequest { 86 | request: "search"; 87 | query: string; 88 | regex?: boolean; 89 | project?: string; 90 | word?: boolean; 91 | case?: boolean; 92 | wild?: boolean; 93 | documents?: string; 94 | system?: boolean; 95 | generated?: boolean; 96 | mapped?: boolean; 97 | max?: number; 98 | include?: string; 99 | exclude?: string; 100 | console: false; 101 | } 102 | 103 | interface AsyncUnitTestRequest { 104 | request: "unittest"; 105 | tests: { class: string; methods?: string[] }[]; 106 | load?: { file: string; content: string[] }[]; 107 | console?: boolean; 108 | debug?: boolean; 109 | } 110 | 111 | export type AsyncRequest = AsyncCompileRequest | AsyncSearchRequest | AsyncUnitTestRequest; 112 | -------------------------------------------------------------------------------- /src/commands/connectFolderToServerNamespace.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { AtelierAPI } from "../api"; 3 | import { panel, resolveConnectionSpec, getResolvedConnectionSpec, smExtensionId } from "../extension"; 4 | import { notIsfs } from "../utils"; 5 | 6 | interface ConnSettings { 7 | server: string; 8 | ns: string; 9 | active: boolean; 10 | } 11 | 12 | export async function connectFolderToServerNamespace(): Promise { 13 | const serverManagerApi = await getServerManagerApi(); 14 | if (!serverManagerApi) { 15 | vscode.window.showErrorMessage( 16 | "Connecting a folder to a server namespace requires the [InterSystems Server Manager extension](https://marketplace.visualstudio.com/items?itemName=intersystems-community.servermanager) to be installed and enabled." 17 | ); 18 | return; 19 | } 20 | // Which folder? 21 | const allFolders = vscode.workspace.workspaceFolders; 22 | const items: vscode.QuickPickItem[] = allFolders 23 | .filter((folder) => notIsfs(folder.uri)) 24 | .map((folder) => { 25 | const config = vscode.workspace.getConfiguration("objectscript", folder); 26 | const conn: ConnSettings = config.get("conn"); 27 | return { 28 | label: folder.name, 29 | description: folder.uri.fsPath, 30 | detail: !conn.server ? undefined : `Currently connected to ${conn.ns} on ${conn.server}`, 31 | }; 32 | }); 33 | if (!items.length) { 34 | vscode.window.showErrorMessage("No local folders in the workspace."); 35 | return; 36 | } 37 | const pick = 38 | items.length === 1 && !items[0].detail 39 | ? items[0] 40 | : await vscode.window.showQuickPick(items, { title: "Pick a folder" }); 41 | const folder = allFolders.find((el) => el.name === pick.label); 42 | // Get user's choice of server 43 | const options: vscode.QuickPickOptions = {}; 44 | const serverName: string = await serverManagerApi.pickServer(undefined, options); 45 | if (!serverName) { 46 | return; 47 | } 48 | // Get its namespace list 49 | const uri = vscode.Uri.parse(`isfs://${serverName}/?ns=%SYS`); 50 | await resolveConnectionSpec(serverName); 51 | // Prepare a displayable form of its connection spec as a hint to the user 52 | // This will never return the default value (second parameter) because we only just resolved the connection spec. 53 | const connSpec = getResolvedConnectionSpec(serverName, undefined); 54 | const connDisplayString = `${connSpec.webServer.scheme}://${connSpec.webServer.host}:${connSpec.webServer.port}/${connSpec.webServer.pathPrefix}`; 55 | // Connect and fetch namespaces 56 | const api = new AtelierAPI(uri); 57 | const allNamespaces: string[] | undefined = await api 58 | .serverInfo(false) 59 | .then((data) => data.result.content.namespaces) 60 | .catch((reason) => { 61 | // Notify user about serverInfo failure 62 | vscode.window.showErrorMessage( 63 | reason.message || `Failed to fetch namespace list from server at ${connDisplayString}` 64 | ); 65 | return undefined; 66 | }); 67 | // Clear the panel entry created by the connection 68 | panel.text = ""; 69 | panel.tooltip = ""; 70 | // Handle serverInfo failure 71 | if (!allNamespaces) { 72 | return; 73 | } 74 | // Handle serverInfo having returned no namespaces 75 | if (!allNamespaces.length) { 76 | vscode.window.showErrorMessage(`No namespace list returned by server at ${connDisplayString}`); 77 | return; 78 | } 79 | // Get user's choice of namespace 80 | const namespace = await vscode.window.showQuickPick(allNamespaces, { 81 | title: `Pick a namespace on server '${serverName}' (${connDisplayString})`, 82 | }); 83 | if (!namespace) { 84 | return; 85 | } 86 | // Update folder's config object 87 | const config = vscode.workspace.getConfiguration("objectscript", folder); 88 | const conn: any = config.inspect("conn").workspaceFolderValue; 89 | await config.update("conn", { ...conn, server: serverName, ns: namespace, active: true }); 90 | } 91 | 92 | async function getServerManagerApi(): Promise { 93 | const targetExtension = vscode.extensions.getExtension(smExtensionId); 94 | if (!targetExtension) { 95 | return undefined; 96 | } 97 | if (!targetExtension.isActive) { 98 | await targetExtension.activate(); 99 | } 100 | const api = targetExtension.exports; 101 | 102 | if (!api) { 103 | return undefined; 104 | } 105 | return api; 106 | } 107 | -------------------------------------------------------------------------------- /src/commands/delete.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import { AtelierAPI } from "../api"; 4 | import { FILESYSTEM_SCHEMA, explorerProvider } from "../extension"; 5 | import { outputChannel, uriOfWorkspaceFolder } from "../utils"; 6 | import { OtherStudioAction, fireOtherStudioAction } from "./studio"; 7 | import { DocumentContentProvider } from "../providers/DocumentContentProvider"; 8 | import { UserAction } from "../api/atelier"; 9 | import { NodeBase, PackageNode, RootNode } from "../explorer/nodes"; 10 | 11 | function deleteList(items: string[], workspaceFolder: string, namespace: string): Promise { 12 | if (!items || !items.length) { 13 | vscode.window.showWarningMessage("Nothing to delete"); 14 | } 15 | 16 | const wsFolderUri = uriOfWorkspaceFolder(workspaceFolder); 17 | const api = new AtelierAPI(workspaceFolder); 18 | api.setNamespace(namespace); 19 | return Promise.all(items.map((item) => api.deleteDoc(item))).then((files) => { 20 | files.forEach((file) => { 21 | if (file.result.ext && wsFolderUri?.scheme == FILESYSTEM_SCHEMA) { 22 | // Only process source control output if we're in an isfs folder 23 | const uri = DocumentContentProvider.getUri(file.result.name); 24 | fireOtherStudioAction(OtherStudioAction.DeletedDocument, uri, file.result.ext); 25 | } 26 | }); 27 | outputChannel.appendLine(`Deleted items: ${files.filter((el) => el.result).length}`); 28 | }); 29 | } 30 | 31 | export async function deleteExplorerItems(nodes: NodeBase[]): Promise { 32 | const { workspaceFolder, namespace } = nodes[0]; 33 | const nodesPromiseList: Promise[] = []; 34 | for (const node of nodes) { 35 | nodesPromiseList.push(node instanceof RootNode ? node.getChildren(node) : Promise.resolve([node])); 36 | } 37 | return Promise.all(nodesPromiseList) 38 | .then((nodesList) => nodesList.flat()) 39 | .then((allNodes) => 40 | allNodes.reduce( 41 | (list, subNode) => list.concat(subNode instanceof PackageNode ? subNode.getClasses() : [subNode.fullName]), 42 | [] 43 | ) 44 | ) 45 | .then(async (items) => { 46 | if (items.length) { 47 | // Ask the user to confirm 48 | const confirm = await vscode.window.showWarningMessage( 49 | `About to delete ${ 50 | items.length > 1 ? `${items.length} documents` : `'${items[0]}'` 51 | }. Are you sure you want to proceed?`, 52 | "Cancel", 53 | "Confirm" 54 | ); 55 | if (confirm !== "Confirm") { 56 | // Don't delete without confirmation 57 | return; 58 | } 59 | deleteList(items, workspaceFolder, namespace); 60 | explorerProvider.refresh(); 61 | } 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/commands/jumpToTagAndOffset.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { DocumentContentProvider } from "../providers/DocumentContentProvider"; 3 | import { handleError } from "../utils"; 4 | 5 | export async function jumpToTagAndOffset(): Promise { 6 | const editor = vscode.window.activeTextEditor; 7 | if (!editor) return; 8 | const document = editor.document; 9 | if (!["objectscript", "objectscript-int"].includes(document.languageId)) { 10 | vscode.window.showWarningMessage("Jump to Tag and Offset only supports .int and .mac routines.", "Dismiss"); 11 | return; 12 | } 13 | 14 | // Get the labels from the document symbol provider 15 | const map = new Map(); 16 | const symbols: vscode.DocumentSymbol[] = await vscode.commands.executeCommand( 17 | "vscode.executeDocumentSymbolProvider", 18 | document.uri 19 | ); 20 | if (!Array.isArray(symbols) || !symbols.length) return; 21 | const items: vscode.QuickPickItem[] = symbols 22 | .filter((symbol) => symbol.kind === vscode.SymbolKind.Method) 23 | .map((symbol) => { 24 | map.set(symbol.name, symbol.range.start.line); 25 | return { 26 | label: symbol.name, 27 | }; 28 | }); 29 | const quickPick = vscode.window.createQuickPick(); 30 | quickPick.title = "Jump to Tag + Offset"; 31 | quickPick.items = items; 32 | quickPick.canSelectMany = false; 33 | quickPick.onDidAccept(() => { 34 | if ( 35 | quickPick.selectedItems.length && 36 | !new RegExp(`^${quickPick.selectedItems[0].label}(\\+\\d+)?$`).test(quickPick.value) 37 | ) { 38 | // Update the value to correct case and allow users to add/update the offset 39 | quickPick.value = quickPick.value.includes("+") 40 | ? `${quickPick.selectedItems[0].label}+${quickPick.value.split("+")[1]}` 41 | : quickPick.selectedItems[0].label; 42 | return; 43 | } 44 | const parts = quickPick.value.trim().split("+"); 45 | let offset = 0; 46 | if (parts[0].length) { 47 | const labelLine = map.get(parts[0]); 48 | if (labelLine == undefined) return; // Not a valid label 49 | offset = labelLine; 50 | } 51 | if (parts.length > 1) { 52 | offset += parseInt(parts[1], 10); 53 | } 54 | const line = document.lineAt(offset); 55 | const range = new vscode.Range(line.range.start, line.range.start); 56 | editor.selection = new vscode.Selection(range.start, range.start); 57 | editor.revealRange(range, vscode.TextEditorRevealType.AtTop); 58 | quickPick.hide(); 59 | }); 60 | quickPick.show(); 61 | } 62 | 63 | /** Prompt the user for an error location of the form `label+offset^routine`, then open it. */ 64 | export async function openErrorLocation(): Promise { 65 | // Prompt the user for a location 66 | const regex = /^(%?[\p{L}\d]+)?(?:\+(\d+))?\^(%?[\p{L}\d.]+)$/u; 67 | const location = await vscode.window.showInputBox({ 68 | title: "Enter the location to open", 69 | placeHolder: "label+offset^routine", 70 | validateInput: (v) => (regex.test(v.trim()) ? undefined : "Input is not in the format 'label+offset^routine'"), 71 | }); 72 | if (!location) { 73 | return; 74 | } 75 | const [, label, offset, routine] = location.trim().match(regex); 76 | // Get the uri for the routine 77 | const uri = DocumentContentProvider.getUri(`${routine}.int`); 78 | if (!uri) { 79 | return; 80 | } 81 | let selection = new vscode.Range(0, 0, 0, 0); 82 | try { 83 | if (label) { 84 | // Find the location of the tag within the document 85 | const symbols: vscode.DocumentSymbol[] = await vscode.commands.executeCommand( 86 | "vscode.executeDocumentSymbolProvider", 87 | uri 88 | ); 89 | for (const symbol of symbols) { 90 | if (symbol.name == label) { 91 | selection = new vscode.Range(symbol.selectionRange.start.line, 0, symbol.selectionRange.start.line, 0); 92 | break; 93 | } 94 | } 95 | } 96 | if (offset) { 97 | // Add the offset 98 | selection = new vscode.Range(selection.start.line + Number(offset), 0, selection.start.line + Number(offset), 0); 99 | } 100 | // Show the document 101 | await vscode.window.showTextDocument(uri, { preview: false, selection }); 102 | } catch (error) { 103 | handleError(error, `Failed to open routine '${routine}.int'.`); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/commands/subclass.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { AtelierAPI } from "../api"; 3 | import { DocumentContentProvider } from "../providers/DocumentContentProvider"; 4 | import { currentFile } from "../utils"; 5 | import { ClassDefinition } from "../utils/classDefinition"; 6 | 7 | export async function subclass(): Promise { 8 | const file = currentFile(); 9 | if (!file || !file.name.toLowerCase().endsWith(".cls")) { 10 | return; 11 | } 12 | const className = file.name.split(".").slice(0, -1).join("."); 13 | const api = new AtelierAPI(file.uri); 14 | if (!api.active) { 15 | return; 16 | } 17 | 18 | const open = (item) => { 19 | const uri = DocumentContentProvider.getUri(ClassDefinition.normalizeClassName(item, true)); 20 | vscode.window.showTextDocument(uri); 21 | }; 22 | 23 | return api 24 | .actionQuery("CALL %Dictionary.ClassDefinitionQuery_SubclassOf(?)", [className]) 25 | .then((data) => { 26 | const list = data.result.content.slice(0, 100) || []; 27 | if (!list.length) { 28 | return; 29 | } 30 | vscode.window 31 | .showQuickPick( 32 | list.map((el) => el.Name), 33 | { title: "Pick a subclass" } 34 | ) 35 | .then((item) => { 36 | open(item); 37 | }); 38 | }) 39 | .catch((err) => console.error(err)); 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/superclass.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { config } from "../extension"; 3 | import { DocumentContentProvider } from "../providers/DocumentContentProvider"; 4 | import { currentFile, notIsfs } from "../utils"; 5 | import { ClassDefinition } from "../utils/classDefinition"; 6 | 7 | export async function superclass(): Promise { 8 | const file = currentFile(); 9 | if (!file || !file.name.toLowerCase().endsWith(".cls") || (notIsfs(file.uri) && !config("conn").active)) { 10 | return; 11 | } 12 | 13 | const open = (item) => { 14 | const uri = DocumentContentProvider.getUri(ClassDefinition.normalizeClassName(item, true)); 15 | vscode.window.showTextDocument(uri); 16 | }; 17 | 18 | const classDefinition = new ClassDefinition(file.name); 19 | return classDefinition 20 | .super() 21 | .then((data) => { 22 | const list = data || []; 23 | if (!list.length) { 24 | return; 25 | } 26 | vscode.window.showQuickPick(list, { title: "Pick a superclass" }).then((item) => { 27 | open(item); 28 | }); 29 | }) 30 | .catch((err) => console.error(err)); 31 | } 32 | -------------------------------------------------------------------------------- /src/commands/viewOthers.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { AtelierAPI } from "../api"; 3 | import { DocumentContentProvider } from "../providers/DocumentContentProvider"; 4 | import { currentFile, handleError, parseClassMemberDefinition } from "../utils"; 5 | 6 | export async function viewOthers(forceEditable = false): Promise { 7 | const file = currentFile(); 8 | if (!file) return; 9 | 10 | const open = async (item: string, forceEditable: boolean) => { 11 | const colonidx: number = item.indexOf(":"); 12 | if (colonidx !== -1) { 13 | // A location is appened to the name of the other document 14 | const options: vscode.TextDocumentShowOptions = {}; 15 | 16 | // Split the document name form the location 17 | let loc = item.slice(colonidx + 1); 18 | item = item.slice(0, colonidx); 19 | let uri: vscode.Uri; 20 | if (forceEditable) { 21 | uri = DocumentContentProvider.getUri(item, undefined, undefined, forceEditable); 22 | } else { 23 | uri = DocumentContentProvider.getUri(item); 24 | } 25 | 26 | if (item.endsWith(".cls")) { 27 | // Locations in classes are of the format method+offset+namespace 28 | loc = loc.slice(0, loc.lastIndexOf("+")); 29 | let method = loc.slice(0, loc.lastIndexOf("+")); 30 | 31 | // Properly delimit method name if it contains invalid characters 32 | if (method.match(/(^([A-Za-z]|%)$)|(^([A-Za-z]|%)([A-Za-z]|\d|[^\x20-\x7F])+$)/g) === null) { 33 | method = '"' + method.replace(/"/g, '""') + '"'; 34 | } 35 | 36 | // Find the location of the given method in the class 37 | const symbols: vscode.DocumentSymbol[] = await vscode.commands.executeCommand( 38 | "vscode.executeDocumentSymbolProvider", 39 | uri 40 | ); 41 | if (symbols !== undefined) { 42 | for (const symbol of symbols[0].children) { 43 | if (symbol.name === method) { 44 | // This is symbol that the location is in 45 | const doc = await vscode.workspace.openTextDocument(uri); 46 | 47 | // Need to find the actual start of the method 48 | for ( 49 | let methodlinenum = symbol.selectionRange.start.line; 50 | methodlinenum <= symbol.range.end.line; 51 | methodlinenum++ 52 | ) { 53 | const methodlinetext: string = doc.lineAt(methodlinenum).text.trim(); 54 | if (methodlinetext.endsWith("{")) { 55 | // This is the last line of the method definition, so count from here 56 | const selectionline: number = methodlinenum + +loc.slice(loc.lastIndexOf("+") + 1); 57 | options.selection = new vscode.Range(selectionline, 0, selectionline, 0); 58 | break; 59 | } 60 | } 61 | break; 62 | } 63 | } 64 | } 65 | } else { 66 | if (item.endsWith(".mac")) { 67 | // Locations in MAC routines are of the format +offset+namespace 68 | loc = loc.slice(0, loc.lastIndexOf("+")); 69 | } 70 | // Locations in INT routines are of the format +offset 71 | const linenum: number = +loc.slice(1); 72 | options.selection = new vscode.Range(linenum, 0, linenum, 0); 73 | } 74 | vscode.window.showTextDocument(uri, options); 75 | } else { 76 | let uri: vscode.Uri; 77 | if (forceEditable) { 78 | uri = DocumentContentProvider.getUri(item, undefined, undefined, forceEditable); 79 | } else { 80 | uri = DocumentContentProvider.getUri(item); 81 | } 82 | vscode.window.showTextDocument(uri); 83 | } 84 | }; 85 | 86 | const getOthers = (info) => { 87 | return info.result.content[0].others; 88 | }; 89 | 90 | const api = new AtelierAPI(file.uri); 91 | if (!api.active) return; 92 | let indexarg: string = file.name; 93 | const cursorpos: vscode.Position = vscode.window.activeTextEditor.selection.active; 94 | const fileExt: string = file.name.split(".").pop().toLowerCase(); 95 | 96 | if ( 97 | api.config.apiVersion >= 4 && 98 | (fileExt === "cls" || fileExt === "mac" || fileExt === "int") && 99 | !/^%sqlcq/i.test(indexarg) 100 | ) { 101 | // Send the server the current position in the document appended to the name if it supports it 102 | let symbols: vscode.DocumentSymbol[] = await vscode.commands.executeCommand( 103 | "vscode.executeDocumentSymbolProvider", 104 | file.uri 105 | ); 106 | if (symbols !== undefined) { 107 | if (fileExt === "cls") { 108 | symbols = symbols[0].children; 109 | } 110 | 111 | let currentSymbol: vscode.DocumentSymbol; 112 | for (const symbol of symbols) { 113 | if (symbol.range.contains(cursorpos)) { 114 | currentSymbol = symbol; 115 | break; 116 | } 117 | } 118 | 119 | if ( 120 | currentSymbol !== undefined && 121 | currentSymbol.kind === vscode.SymbolKind.Method && 122 | currentSymbol.detail.toLowerCase() !== "query" && 123 | currentSymbol.name.charAt(0) !== '"' && 124 | currentSymbol.name.charAt(currentSymbol.name.length - 1) !== '"' 125 | ) { 126 | // The current position is in a symbol that we can convert into a label+offset that the server understands 127 | let offset: number = cursorpos.line - currentSymbol.selectionRange.start.line; 128 | 129 | let isObjectScript = true; 130 | if (fileExt === "cls") { 131 | const memberInfo = parseClassMemberDefinition(vscode.window.activeTextEditor.document, currentSymbol); 132 | if (memberInfo) { 133 | const { defEndLine, language } = memberInfo; 134 | offset = cursorpos.line - defEndLine; 135 | isObjectScript = ["cache", "objectscript"].includes(language); 136 | } 137 | } 138 | 139 | offset = offset < 0 ? 0 : offset; 140 | if (isObjectScript) { 141 | // Only provide label+offset if method language is ObjectScript 142 | indexarg = indexarg + ":" + currentSymbol.name + "+" + offset; 143 | } 144 | } 145 | } 146 | } 147 | 148 | return api 149 | .actionIndex([indexarg]) 150 | .then((info) => { 151 | const listOthers = getOthers(info) || []; 152 | if (!listOthers.length) { 153 | vscode.window.showInformationMessage("There are no other documents to open.", "Dismiss"); 154 | return; 155 | } 156 | if (listOthers.length === 1) { 157 | open(listOthers[0], forceEditable); 158 | } else { 159 | vscode.window.showQuickPick(listOthers).then((item) => { 160 | open(item, forceEditable); 161 | }); 162 | } 163 | }) 164 | .catch((error) => handleError(error, "Failed to get other documents.")); 165 | } 166 | -------------------------------------------------------------------------------- /src/commands/xmlToUdl.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import path = require("path"); 3 | import { config, OBJECTSCRIPTXML_FILE_SCHEMA, xmlContentProvider } from "../extension"; 4 | import { AtelierAPI } from "../api"; 5 | import { replaceFile, fileExists, getWsFolder, handleError, notIsfs, outputChannel } from "../utils"; 6 | import { getFileName } from "./export"; 7 | 8 | const exportHeader = /^\s* { 11 | const uri = textEditor.document.uri; 12 | const content = textEditor.document.getText(); 13 | if (notIsfs(uri) && uri.path.toLowerCase().endsWith("xml") && textEditor.document.lineCount > 2) { 14 | if (exportHeader.test(textEditor.document.lineAt(1).text)) { 15 | const api = new AtelierAPI(uri); 16 | if (!api.active) { 17 | vscode.window.showErrorMessage("'Preview XML as UDL' command requires an active server connection.", "Dismiss"); 18 | return; 19 | } 20 | try { 21 | // Convert the file 22 | const udlDocs: { name: string; content: string[] }[] = await api 23 | .cvtXmlUdl(content) 24 | .then((data) => data.result.content); 25 | if (udlDocs.length == 0) { 26 | vscode.window.showErrorMessage( 27 | `File '${uri.toString(true)}' contains no documents that can be previewed.`, 28 | "Dismiss" 29 | ); 30 | return; 31 | } 32 | // Prompt the user for documents to preview 33 | const docsToPreview = await vscode.window.showQuickPick( 34 | udlDocs.map((d) => { 35 | return { label: d.name, picked: true }; 36 | }), 37 | { 38 | canPickMany: true, 39 | title: "Select the documents to preview", 40 | } 41 | ); 42 | if (docsToPreview == undefined || docsToPreview.length == 0) { 43 | return; 44 | } 45 | const docWhitelist = docsToPreview.map((d) => d.label); 46 | // Send the UDL text to the content provider 47 | xmlContentProvider.addUdlDocsForFile(uri.toString(), udlDocs); 48 | // Open the files 49 | for (const udlDoc of udlDocs) { 50 | if (!docWhitelist.includes(udlDoc.name)) continue; // This file wasn't selected 51 | // await for response so we know when it's safe to clear the provider's cache 52 | await vscode.window 53 | .showTextDocument( 54 | vscode.Uri.from({ 55 | path: udlDoc.name, 56 | fragment: uri.toString(), 57 | scheme: OBJECTSCRIPTXML_FILE_SCHEMA, 58 | }), 59 | { 60 | preserveFocus: true, 61 | preview: false, 62 | viewColumn: vscode.ViewColumn.Beside, 63 | } 64 | ) 65 | .then( 66 | () => { 67 | // Don't need return value 68 | }, 69 | () => { 70 | // Swallow errors 71 | } 72 | ); 73 | } 74 | // Remove the UDL text from the content provider's cache 75 | xmlContentProvider.removeUdlDocsForFile(uri.toString()); 76 | } catch (error) { 77 | handleError(error, "Error executing 'Preview XML as UDL' command."); 78 | } 79 | } else if (!auto) { 80 | vscode.window.showErrorMessage(`XML file '${uri.toString(true)}' is not an InterSystems export.`, "Dismiss"); 81 | } 82 | } 83 | } 84 | 85 | /** Extract the source documents in an XML file as UDL and create the UDL files using the export settings. */ 86 | export async function extractXMLFileContents(xmlUri?: vscode.Uri): Promise { 87 | if (!xmlUri && vscode.window.activeTextEditor) { 88 | // Check if the active text editor contains an XML file 89 | const activeDoc = vscode.window.activeTextEditor.document; 90 | if (notIsfs(activeDoc.uri) && activeDoc.uri.path.toLowerCase().endsWith("xml") && activeDoc.lineCount > 2) { 91 | // The active text editor contains an XML file, so process it 92 | xmlUri = activeDoc.uri; 93 | } 94 | } 95 | try { 96 | // Determine the workspace folder 97 | let wsFolder: vscode.WorkspaceFolder; 98 | if (xmlUri) { 99 | wsFolder = vscode.workspace.getWorkspaceFolder(xmlUri); 100 | } else { 101 | // Use the server connection from a workspace folder 102 | wsFolder = await getWsFolder("Pick the workspace folder to run the command in", false, false, true, true); 103 | if (!wsFolder) { 104 | if (wsFolder === undefined) { 105 | // Strict equality needed because undefined == null 106 | vscode.window.showErrorMessage( 107 | "'Extract Documents from XML File...' command requires a non-isfs workspace folder with an active server connection.", 108 | "Dismiss" 109 | ); 110 | } 111 | return; 112 | } 113 | } 114 | if (!wsFolder) return; 115 | const api = new AtelierAPI(wsFolder.uri); 116 | if (!xmlUri) { 117 | // Prompt the user the file to extract 118 | const uris = await vscode.window.showOpenDialog({ 119 | canSelectFiles: true, 120 | canSelectFolders: false, 121 | canSelectMany: false, 122 | openLabel: "Extract", 123 | filters: { 124 | "XML Files": ["xml"], 125 | }, 126 | defaultUri: wsFolder.uri, 127 | }); 128 | if (!Array.isArray(uris) || uris.length == 0) { 129 | // No file to extract 130 | return; 131 | } 132 | xmlUri = uris[0]; 133 | if (xmlUri.path.split(".").pop().toLowerCase() != "xml") { 134 | vscode.window.showErrorMessage("The selected file was not XML.", "Dismiss"); 135 | return; 136 | } 137 | } 138 | // Read the XML file 139 | const xmlContent = new TextDecoder().decode(await vscode.workspace.fs.readFile(xmlUri)).split(/\r?\n/); 140 | if (xmlContent.length < 3 || !exportHeader.test(xmlContent[1])) { 141 | vscode.window.showErrorMessage(`XML file '${xmlUri.toString(true)}' is not an InterSystems export.`, "Dismiss"); 142 | return; 143 | } 144 | // Convert the file 145 | const udlDocs: { name: string; content: string[] }[] = await api 146 | .cvtXmlUdl(xmlContent.join("\n")) 147 | .then((data) => data.result.content); 148 | if (udlDocs.length == 0) { 149 | vscode.window.showErrorMessage( 150 | `File '${xmlUri.toString(true)}' contains no documents that can be extracted.`, 151 | "Dismiss" 152 | ); 153 | return; 154 | } 155 | // Prompt the user for documents to extract 156 | const docsToExtract = await vscode.window.showQuickPick( 157 | udlDocs.map((d) => { 158 | return { label: d.name, picked: true }; 159 | }), 160 | { 161 | canPickMany: true, 162 | ignoreFocusOut: true, 163 | title: "Pick the documents to extract", 164 | placeHolder: "Files are created using your 'objectscript.export' settings", 165 | } 166 | ); 167 | if (docsToExtract == undefined || docsToExtract.length == 0) { 168 | return; 169 | } 170 | const docWhitelist = docsToExtract.map((d) => d.label); 171 | // Write the UDL files 172 | const { atelier, folder, addCategory, map } = config("export", wsFolder.name); 173 | const rootFolder = 174 | wsFolder.uri.path + (typeof folder == "string" && folder.length ? `/${folder.replaceAll(path.sep, "/")}` : ""); 175 | let errs = 0; 176 | for (const udlDoc of udlDocs) { 177 | if (!docWhitelist.includes(udlDoc.name)) continue; // This file wasn't selected 178 | const fileUri = wsFolder.uri.with({ path: getFileName(rootFolder, udlDoc.name, atelier, addCategory, map, "/") }); 179 | if (await fileExists(fileUri)) { 180 | outputChannel.appendLine(`File '${fileUri.toString(true)}' already exists.`); 181 | errs++; 182 | continue; 183 | } 184 | try { 185 | await replaceFile(fileUri, udlDoc.content); 186 | } catch (error) { 187 | outputChannel.appendLine( 188 | typeof error == "string" ? error : error instanceof Error ? error.toString() : JSON.stringify(error) 189 | ); 190 | errs++; 191 | } 192 | } 193 | if (errs) { 194 | vscode.window.showErrorMessage( 195 | `Failed to write ${errs} file${errs > 1 ? "s" : ""}. Check the 'ObjectScript' Output channel for details.`, 196 | "Dismiss" 197 | ); 198 | } 199 | } catch (error) { 200 | handleError(error, "Error executing 'Extract Documents from XML File...' command."); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/debug/dbgp.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import * as WebSocket from "ws"; 3 | import * as iconv from "iconv-lite"; 4 | import { DOMParser } from "@xmldom/xmldom"; 5 | 6 | /** The encoding all XDebug messages are encoded with */ 7 | export const ENCODING = "iso-8859-1"; 8 | 9 | /** The two states the connection switches between */ 10 | enum ParsingState { 11 | DataLength, 12 | Response, 13 | } 14 | 15 | /** Wraps the NodeJS Socket and calls handleResponse() whenever a full response arrives */ 16 | export class DbgpConnection extends EventEmitter { 17 | private _socket: WebSocket; 18 | private _parsingState: ParsingState; 19 | private _chunksDataLength: number; 20 | private _chunks: Buffer[]; 21 | private _dataLength: number; 22 | private _parser: DOMParser; 23 | private _messages: Buffer[] = []; 24 | private _processingMessages = false; 25 | 26 | public constructor(socket: WebSocket) { 27 | super(); 28 | this._socket = socket; 29 | this._parsingState = ParsingState.DataLength; 30 | this._chunksDataLength = 0; 31 | this._chunks = []; 32 | socket.on("message", (data: string): void => { 33 | this._messages.push(Buffer.from(data)); 34 | if (!this._processingMessages) { 35 | this._processingMessages = true; 36 | this._handleDataChunk(); 37 | this._processingMessages = false; 38 | } 39 | }); 40 | socket.on("error", (error: Error): boolean => this.emit("error", error)); 41 | socket.on("close", (): boolean => this.emit("close")); 42 | this._parser = new DOMParser({ 43 | onError: (level, msg) => { 44 | if (level == "warning") { 45 | this.emit("warning", msg); 46 | } else { 47 | this.emit("error", new Error(msg)); 48 | } 49 | }, 50 | }); 51 | } 52 | 53 | public write(command: Buffer): Promise { 54 | return new Promise((resolve): void => { 55 | this._socket.send(command, (): void => { 56 | resolve(); 57 | }); 58 | }); 59 | } 60 | 61 | /** closes the underlying socket */ 62 | public close(): Promise { 63 | return new Promise((resolve, reject) => { 64 | this._socket.once("close", resolve); 65 | this._socket.close(); 66 | }); 67 | } 68 | 69 | private _handleDataChunk() { 70 | if (!this._messages.length) return; // Shouldn't ever happen 71 | const data: Buffer = this._messages.shift(); 72 | if (this._parsingState === ParsingState.DataLength) { 73 | // does data contain a NULL byte? 74 | const separatorIndex = data.indexOf("|"); 75 | if (separatorIndex !== -1) { 76 | // YES -> we received the data length and are ready to receive the response 77 | const lastPiece = data.slice(0, separatorIndex); 78 | this._chunks.push(lastPiece); 79 | this._chunksDataLength += lastPiece.length; 80 | this._dataLength = parseInt(iconv.decode(Buffer.concat(this._chunks, this._chunksDataLength), ENCODING)); 81 | // reset buffered chunks 82 | this._chunks = []; 83 | this._chunksDataLength = 0; 84 | // switch to response parsing state 85 | this._parsingState = ParsingState.Response; 86 | // if data contains more info (except the NULL byte) 87 | if (data.length > separatorIndex + 1) { 88 | // handle the rest of the packet as part of the response 89 | const rest = data.slice(separatorIndex + 1, this._dataLength + separatorIndex + 1); 90 | this._messages.unshift(rest); 91 | this._handleDataChunk(); 92 | // more then one data chunk in one message 93 | const restData = data.slice(this._dataLength + separatorIndex + 1); 94 | if (restData.length) { 95 | this._messages.unshift(restData); 96 | this._handleDataChunk(); 97 | } 98 | } 99 | } else { 100 | // NO -> this is only part of the data length. We wait for the next data event 101 | this._chunks.push(data); 102 | this._chunksDataLength += data.length; 103 | } 104 | } else if (this._parsingState === ParsingState.Response) { 105 | // does the new data together with the buffered data add up to the data length? 106 | if (this._chunksDataLength + data.length >= this._dataLength) { 107 | // YES -> we received the whole response 108 | // append the last piece of the response 109 | const lastResponsePiece = data.slice(0, this._dataLength - this._chunksDataLength); 110 | this._chunks.push(lastResponsePiece); 111 | this._chunksDataLength += lastResponsePiece.length; 112 | const response = Buffer.concat(this._chunks, this._chunksDataLength).toString("ascii"); 113 | // call response handler 114 | const xml = iconv.decode(Buffer.from(response, "base64"), ENCODING); 115 | const document = this._parser.parseFromString(xml, "application/xml"); 116 | this.emit("message", document); 117 | // reset buffer 118 | this._chunks = []; 119 | this._chunksDataLength = 0; 120 | // switch to data length parsing state 121 | this._parsingState = ParsingState.DataLength; 122 | // if data contains more info 123 | if (data.length > lastResponsePiece.length) { 124 | // handle the rest of the packet as data length 125 | const rest = data.slice(lastResponsePiece.length); 126 | this._messages.unshift(rest); 127 | this._handleDataChunk(); 128 | } 129 | } else { 130 | // NO -> this is not the whole response yet. We buffer it and wait for the next data event. 131 | this._chunks.push(data); 132 | this._chunksDataLength += data.length; 133 | } 134 | } 135 | while (this._messages.length) this._handleDataChunk(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/debug/debugAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ObjectScriptDebugSession } from "./debugSession"; 2 | 3 | ObjectScriptDebugSession.run(ObjectScriptDebugSession); 4 | -------------------------------------------------------------------------------- /src/debug/debugAdapterFactory.ts: -------------------------------------------------------------------------------- 1 | import net = require("net"); 2 | import vscode = require("vscode"); 3 | import { ObjectScriptDebugSession } from "./debugSession"; 4 | 5 | export class ObjectScriptDebugAdapterDescriptorFactory 6 | implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable 7 | { 8 | private serverMap = new Map(); 9 | 10 | public createDebugAdapterDescriptor( 11 | session: vscode.DebugSession, 12 | executable: vscode.DebugAdapterExecutable | undefined 13 | ): vscode.ProviderResult { 14 | const debugSession = new ObjectScriptDebugSession(); 15 | 16 | // pickProcess may have added a suffix to inform us which folder's connection it used 17 | const workspaceFolderIndex = (session.configuration.processId as string)?.split("@")[1]; 18 | const workspaceFolderUri = workspaceFolderIndex 19 | ? vscode.workspace.workspaceFolders[parseInt(workspaceFolderIndex)]?.uri 20 | : undefined; 21 | debugSession.setupAPI(workspaceFolderUri); 22 | 23 | const serverId = debugSession.serverId; 24 | let server = this.serverMap.get(serverId); 25 | if (!server) { 26 | // start listening on a random port 27 | server = net 28 | .createServer((socket) => { 29 | debugSession.setRunAsServer(true); 30 | debugSession.start(socket as NodeJS.ReadableStream, socket); 31 | }) 32 | .listen(0); 33 | this.serverMap.set(serverId, server); 34 | } 35 | 36 | // make VS Code connect to this debug server 37 | const address = server.address(); 38 | const port = typeof address !== "string" ? address.port : 9000; 39 | return new vscode.DebugAdapterServer(port); 40 | } 41 | 42 | public dispose(): void { 43 | this.serverMap.forEach((server) => server.close()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/debug/debugConfProvider.ts: -------------------------------------------------------------------------------- 1 | import vscode = require("vscode"); 2 | 3 | import { 4 | CancellationToken, 5 | DebugConfiguration, 6 | DebugConfigurationProvider, 7 | ProviderResult, 8 | WorkspaceFolder, 9 | } from "vscode"; 10 | 11 | export class ObjectScriptConfigurationProvider implements DebugConfigurationProvider { 12 | /** 13 | * Massage a debug configuration just before a debug session is being launched, 14 | * e.g. add all missing attributes to the debug configuration. 15 | */ 16 | public resolveDebugConfiguration( 17 | folder: WorkspaceFolder | undefined, 18 | config: DebugConfiguration, 19 | token?: CancellationToken 20 | ): ProviderResult { 21 | // if launch.json is missing or empty 22 | if (!config.type && !config.request && !config.name) { 23 | const editor = vscode.window.activeTextEditor; 24 | if (editor && editor.document.languageId === "markdown") { 25 | config.type = "objectscript"; 26 | config.name = "Launch"; 27 | config.request = "launch"; 28 | config.program = "${file}"; 29 | // config.stopOnEntry = true; 30 | } 31 | } 32 | 33 | if (config.request === "launch" && !config.program) { 34 | return vscode.window.showInformationMessage("Cannot find a program to debug").then((_) => { 35 | return undefined; // abort launch 36 | }); 37 | } 38 | 39 | if (config.request === "attach" && !config.processId && !config.cspDebugId) { 40 | config.processId = "${command:PickProcess}"; 41 | } 42 | 43 | return config; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/debug/utils.ts: -------------------------------------------------------------------------------- 1 | import { BaseProperty } from "./xdebugConnection"; 2 | 3 | export function formatPropertyValue(property: BaseProperty): string { 4 | let displayValue: string; 5 | if (property.hasChildren || property.type === "array" || property.type === "object") { 6 | if (property.type === "array") { 7 | // for arrays, show the length, like a var_dump would do 8 | displayValue = "array(" + (property.hasChildren ? property.numberOfChildren : 0) + ")"; 9 | } else if (property.type === "object" && property.class) { 10 | // for objects, show the class name as type (if specified) 11 | displayValue = property.class; 12 | } else { 13 | // edge case: show the type of the property as the value 14 | displayValue = property.type; 15 | } 16 | } else { 17 | // for null, uninitialized, resource, etc. show the type 18 | displayValue = property.value || property.type === "string" ? property.value : property.type; 19 | if (property.type === "string") { 20 | displayValue = '"' + displayValue + '"'; 21 | } else if (property.type === "bool") { 22 | displayValue = !!parseInt(displayValue) + ""; 23 | } 24 | } 25 | return displayValue; 26 | } 27 | -------------------------------------------------------------------------------- /src/explorer/projectsExplorer.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { AtelierAPI } from "../api"; 3 | import { notIsfs } from "../utils"; 4 | import { NodeBase, ProjectsServerNsNode } from "./nodes"; 5 | 6 | export class ProjectsExplorerProvider implements vscode.TreeDataProvider { 7 | public onDidChangeTreeData: vscode.Event; 8 | private _onDidChangeTreeData: vscode.EventEmitter; 9 | 10 | /** The labels of all current root nodes */ 11 | private _roots: string[] = []; 12 | 13 | /** The server:ns string for all extra root nodes */ 14 | private readonly _extraRoots: string[] = []; 15 | 16 | public constructor() { 17 | this._onDidChangeTreeData = new vscode.EventEmitter(); 18 | this.onDidChangeTreeData = this._onDidChangeTreeData.event; 19 | } 20 | 21 | public refresh(): void { 22 | this._onDidChangeTreeData.fire(null); 23 | } 24 | public getTreeItem(element: NodeBase): vscode.TreeItem { 25 | return element.getTreeItem(); 26 | } 27 | 28 | public async getChildren(element?: NodeBase): Promise { 29 | if (!element) { 30 | return this.getRootNodes(); 31 | } 32 | return element.getChildren(element); 33 | } 34 | 35 | public openExtraServerNs(serverNs: { serverName: string; namespace: string }): void { 36 | // Check if this server namespace is already open 37 | if (this._roots.includes(`${serverNs.serverName}[${serverNs.namespace}]`)) { 38 | vscode.window.showWarningMessage( 39 | `Namespace '${serverNs.namespace}' on server '${serverNs.serverName}' is already open in the Projects Explorer`, 40 | "Dismiss" 41 | ); 42 | return; 43 | } 44 | // Add the extra root node 45 | this._extraRoots.push(`${serverNs.serverName}:${serverNs.namespace}`); 46 | // Refresh the explorer 47 | this.refresh(); 48 | } 49 | 50 | public closeExtraServerNs(node: ProjectsServerNsNode): void { 51 | const label = node.getTreeItem().label; 52 | const serverName = label.slice(0, label.lastIndexOf("[")); 53 | const namespace = label.slice(label.lastIndexOf("[") + 1, -1); 54 | const idx = this._extraRoots.findIndex((authority) => authority == `${serverName}:${namespace}`); 55 | if (idx != -1) { 56 | // Remove the extra root node 57 | this._extraRoots.splice(idx, 1); 58 | // Refresh the explorer 59 | this.refresh(); 60 | } 61 | } 62 | 63 | private async getRootNodes(): Promise { 64 | const rootNodes: NodeBase[] = []; 65 | let node: NodeBase; 66 | 67 | const workspaceFolders = vscode.workspace.workspaceFolders || []; 68 | const alreadyAdded: string[] = []; 69 | // Add the workspace root nodes 70 | workspaceFolders 71 | .filter((workspaceFolder) => workspaceFolder.uri && !notIsfs(workspaceFolder.uri)) 72 | .forEach((workspaceFolder) => { 73 | const conn = new AtelierAPI(workspaceFolder.uri).config; 74 | if (conn.active && conn.ns) { 75 | node = new ProjectsServerNsNode(workspaceFolder.name, this._onDidChangeTreeData, workspaceFolder.uri); 76 | const label = node.getTreeItem().label; 77 | if (!alreadyAdded.includes(label)) { 78 | alreadyAdded.push(label); 79 | rootNodes.push(node); 80 | } 81 | } 82 | }); 83 | // Add the extra root nodes 84 | this._extraRoots.forEach((authority) => { 85 | node = new ProjectsServerNsNode( 86 | "", 87 | this._onDidChangeTreeData, 88 | vscode.Uri.parse(`isfs-readonly://${authority}/`), 89 | true 90 | ); 91 | const label = node.getTreeItem().label; 92 | if (!alreadyAdded.includes(label)) { 93 | alreadyAdded.push(label); 94 | rootNodes.push(node); 95 | } 96 | }); 97 | this._roots = alreadyAdded; 98 | await vscode.commands.executeCommand( 99 | "setContext", 100 | "vscode-objectscript.projectsExplorerRootCount", 101 | rootNodes.length 102 | ); 103 | return rootNodes; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/languageConfiguration.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | export function getLanguageConfiguration(lang: string): vscode.LanguageConfiguration { 4 | const conf = vscode.workspace.getConfiguration("objectscript"); 5 | return { 6 | wordPattern: 7 | /((?<=(class|extends|as|of) )(%?\b[a-z0-9]+(\.[a-z0-9]+)*\b))|(\^[a-z0-9]+(\.[a-z0-9]+)*)|((\${1,3}|[irm]?%|\^|#)?[a-z0-9]+)/i, 8 | brackets: [ 9 | ["{", "}"], 10 | ["(", ")"], 11 | ], 12 | comments: { 13 | lineComment: 14 | lang == "objectscript-class" 15 | ? "//" 16 | : ["objectscript", "objectscript-macros"].includes(lang) 17 | ? conf.get("commentToken") 18 | : conf.get("intCommentToken"), 19 | blockComment: ["/*", "*/"], 20 | }, 21 | autoClosingPairs: [ 22 | { 23 | open: "/*", 24 | close: "*/", 25 | notIn: [vscode.SyntaxTokenType.Comment, vscode.SyntaxTokenType.String, vscode.SyntaxTokenType.RegEx], 26 | }, 27 | { 28 | open: "{", 29 | close: "}", 30 | notIn: [vscode.SyntaxTokenType.Comment, vscode.SyntaxTokenType.String, vscode.SyntaxTokenType.RegEx], 31 | }, 32 | { 33 | open: "(", 34 | close: ")", 35 | notIn: [vscode.SyntaxTokenType.Comment, vscode.SyntaxTokenType.String, vscode.SyntaxTokenType.RegEx], 36 | }, 37 | { 38 | open: '"', 39 | close: '"', 40 | notIn: [vscode.SyntaxTokenType.Comment, vscode.SyntaxTokenType.String, vscode.SyntaxTokenType.RegEx], 41 | }, 42 | ], 43 | onEnterRules: 44 | lang == "objectscript-class" 45 | ? [ 46 | { 47 | beforeText: /^\/\/\//, 48 | action: { indentAction: vscode.IndentAction.None, appendText: "/// " }, 49 | }, 50 | ] 51 | : undefined, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/providers/CodeActionProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Formatter } from "./Formatter"; 3 | 4 | export class CodeActionProvider implements vscode.CodeActionProvider { 5 | private _formatter: Formatter; 6 | public constructor() { 7 | this._formatter = new Formatter(); 8 | } 9 | 10 | public provideCodeActions( 11 | document: vscode.TextDocument, 12 | range: vscode.Range | vscode.Selection, 13 | context: vscode.CodeActionContext, 14 | token: vscode.CancellationToken 15 | ): vscode.ProviderResult<(vscode.Command | vscode.CodeAction)[]> { 16 | const codeActions: vscode.CodeAction[] = []; 17 | context.diagnostics.forEach((diagnostic) => { 18 | if (diagnostic.code === "$zobjxxx") { 19 | const text = document.getText(diagnostic.range).toLowerCase(); 20 | let replacement = ""; 21 | switch (text) { 22 | case "$zobjclassmethod": 23 | replacement = "$classmethod"; 24 | break; 25 | case "$zobjmethod": 26 | replacement = "$method"; 27 | break; 28 | case "$zobjproperty": 29 | replacement = "$property"; 30 | break; 31 | case "$zobjclass": 32 | replacement = "$classname"; 33 | break; 34 | default: 35 | } 36 | if (replacement.length) { 37 | replacement = this._formatter.function(replacement); 38 | codeActions.push(this.createFix(document, diagnostic.range, replacement)); 39 | } 40 | } 41 | }); 42 | return codeActions; 43 | } 44 | 45 | private createFix(document: vscode.TextDocument, range: vscode.Range, replacement: string): vscode.CodeAction { 46 | const fix = new vscode.CodeAction(`Replace with ${replacement}`, vscode.CodeActionKind.QuickFix); 47 | fix.edit = new vscode.WorkspaceEdit(); 48 | fix.edit.replace(document.uri, range, replacement); 49 | return fix; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/providers/DocumentFormattingEditProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Formatter } from "./Formatter"; 3 | import commands = require("./completion/commands.json"); 4 | import systemFunctions = require("./completion/systemFunctions.json"); 5 | import systemVariables = require("./completion/systemVariables.json"); 6 | 7 | export class DocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider { 8 | private _formatter: Formatter; 9 | public constructor() { 10 | this._formatter = new Formatter(); 11 | } 12 | 13 | public provideDocumentFormattingEdits( 14 | document: vscode.TextDocument, 15 | options: vscode.FormattingOptions, 16 | token: vscode.CancellationToken 17 | ): vscode.ProviderResult { 18 | return [...this.commands(document, options), ...this.functions(document, options)]; 19 | } 20 | 21 | private commands(document: vscode.TextDocument, options: vscode.FormattingOptions): vscode.TextEdit[] { 22 | const edits = []; 23 | let indent = 1; 24 | const isClass = document.fileName.toLowerCase().endsWith(".cls"); 25 | 26 | let inComment = false; 27 | let isCode = !isClass; 28 | let jsScript = false; 29 | let sql = false; 30 | let sqlParens = 0; 31 | const lineFrom = isClass ? 0 : 1; // just skip ROUTINE header 32 | for (let i = lineFrom; i < document.lineCount; i++) { 33 | const line = document.lineAt(i); 34 | const text = this.stripLineComments(line.text); 35 | 36 | if (!text.replace(/[\s\t]+/g, "").length) { 37 | continue; 38 | } 39 | 40 | if (text.match(/