├── .config_license-checker.json ├── .editorconfig ├── .eslintrc.yml ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 00-bug.yml │ ├── 01-feature.yml │ └── config.yml └── workflows │ ├── pr.yml │ └── veracode.yml ├── .gitignore ├── .mocharc.yml ├── .npmrc ├── .nycrc.json ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASE.md ├── images ├── OpenFolder.png └── SendScriptToTerminal.png ├── keithley-logo.ico ├── npm.licenses.json ├── package-lock.json ├── package.json ├── resources ├── DemoVideoThumbnail.png ├── TSP_Toolkit_128x128.png ├── dark │ ├── connect.svg │ ├── dependency.svg │ ├── refresh.svg │ └── tsp-terminal-icon.svg ├── light │ ├── connect.svg │ ├── dependency.svg │ ├── refresh.svg │ └── tsp-terminal-icon.svg ├── tsp-toolkit-view-container-icon.svg └── walkthrough │ ├── ConnectToInstrument.png │ ├── FetchConfig.png │ ├── InstrumentExplorer.png │ ├── OpenFolder.png │ ├── RunTspScript.png │ ├── TSPExampleRepo.png │ ├── TSPResources.png │ ├── TerminalUsage.png │ ├── WindowsDeps.md │ └── connect_instrument.md ├── scripts └── link-dev-ki-comms.js ├── src ├── ConifgWebView.ts ├── connection.ts ├── extension.ts ├── helpDocumentWebView.ts ├── instrument.ts ├── instrumentExplorer.ts ├── instrumentProvider.ts ├── kic-cli.ts ├── logging.ts ├── resourceManager.ts ├── styles.css ├── systemConfig.js ├── test │ └── extension.test.ts ├── utility.ts └── workspaceManager.ts ├── third-party ├── CONTRIBUTING.md └── README.md ├── tsconfig.json └── vsc-extension-quickstart.md /.config_license-checker.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "version": "", 4 | "description": "", 5 | "licenses": "", 6 | "copyright": "", 7 | "licenseText": "none", 8 | "licenseModified": "no" 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{yml,yaml}] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | reportUnusedDisableDirectives: true 3 | env: 4 | node: true 5 | es2021: true 6 | plugins: 7 | - "@typescript-eslint" 8 | - import 9 | - jsdoc 10 | - prettier 11 | extends: 12 | - eslint:recommended 13 | # - plugin:@typescript-eslint/eslint-recommended 14 | - plugin:@typescript-eslint/recommended 15 | - plugin:@typescript-eslint/recommended-requiring-type-checking 16 | - plugin:import/recommended 17 | - plugin:import/typescript 18 | - plugin:prettier/recommended 19 | parser: "@typescript-eslint/parser" 20 | parserOptions: 21 | ecmaVersion: 13 22 | sourceType: module 23 | ecmaFeatures: 24 | impliedStrict: true 25 | project: 26 | - ./tsconfig.json 27 | ignorePatterns: 28 | # Directory globs 29 | - "**/target/" 30 | # File globs 31 | - "*.js" 32 | # Directories 33 | - ".vscode/" 34 | - "dist/" 35 | - "out/" 36 | settings: 37 | import/resolver: 38 | typescript: 39 | alwaysTryTypes: true 40 | rules: 41 | # eslint 42 | indent: 43 | - "error" 44 | - 4 45 | - SwitchCase: 1 46 | linebreak-style: 47 | - error 48 | - unix 49 | no-constant-condition: 50 | - error 51 | - checkLoops: false 52 | quotes: 53 | - error 54 | - double 55 | - avoidEscape: true 56 | allowTemplateLiterals: false 57 | # typescript 58 | "@typescript-eslint/no-namespace": 59 | - error 60 | - allowDeclarations: false 61 | allowDefinitionFiles: true 62 | "@typescript-eslint/no-floating-promises": error 63 | semi: off 64 | "@typescript-eslint/semi": off 65 | "@typescript-eslint/unified-signatures": warn 66 | # eslint-plugin-import 67 | sort-imports: 68 | - warn 69 | - ignoreCase: true 70 | ignoreDeclarationSort: true 71 | import/order: error 72 | # eslint-plugin-jsdoc 73 | jsdoc/check-alignment: error 74 | # eslint-plugin-prettier 75 | prettier/prettier: 76 | - warn 77 | - {} 78 | - usePrettierrc: true 79 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto eol=lf 3 | 4 | 5 | # Tell GitLab that tsp files should be highlighted as Lua 6 | *.tsp gitlab-language=lua 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/00-bug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | description: Create an issue to let us know about a problem. 4 | title: "[Bug]: " 5 | labels: 6 | - bug 7 | - triage 8 | body: 9 | - type: checkboxes 10 | attributes: 11 | label: Pre-check 12 | description: | 13 | Please search through existing [issues](https://github.com/tektronix/tsp-toolkit/issues/) 14 | and [discussions](https://github.com/tektronix/tsp-toolkit/discussions) before entering a new issue. 15 | 16 | Feel free to 👍 any open issues that match the problem you encountered! 17 | options: 18 | - label: I have searched the existing issues and discussions to make sure my problem has not already been addressed. 19 | required: true 20 | - label: I have looked at the "Known Issues" Section of the marketplace page of all TSP Toolkit extensions. 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: Search Terms 25 | description: | 26 | What terms did you use to search for existing issues? These terms will help others 27 | to find this issue later on. 28 | placeholder: | 29 | e.g. "debugger", "crash", "lost connection" 30 | - type: dropdown 31 | attributes: 32 | label: TSP Toolkit Version 33 | description: The version of `tektronix.tsp-toolkit` that you have installed. 34 | multiple: false 35 | options: 36 | - "0.15.0" 37 | - type: dropdown 38 | attributes: 39 | label: TSP Toolkit Develop Version 40 | description: The version of `tektronix.tsp-toolkit-develop` that you have installed 41 | multiple: false 42 | options: 43 | - "0.15.0" 44 | - type: textarea 45 | attributes: 46 | label: "Steps to Reproduce" 47 | description: List the steps you took to exhibit the issue. 48 | placeholder: | 49 | Example: 50 | 1. Open Command Pallette and enter "TSP: Connect" 51 | 2. Select "New Connection" 52 | 3. Type in IP address for 2450 53 | 4. After connection, enter `print("some string that breaks the terminal")` 54 | - type: textarea 55 | attributes: 56 | label: Expected Behavior 57 | description: Let us know what you expect to happen. 58 | placeholder: | 59 | Example: 60 | The terminal should receive `some string that breaks the terminal` back from 61 | from the instrument. Followed by a new prompt. 62 | - type: textarea 63 | attributes: 64 | label: Actual Behavior 65 | description: Let us know what actually happened 66 | placeholder: | 67 | Example: 68 | The terminal immediately disappeared with no messages. 69 | 70 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01-feature.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | description: Let us know about a feature that you think would be a good addition to TSP Toolkit! 4 | title: "[Feature]: " 5 | labels: 6 | - bug 7 | - triage 8 | body: 9 | - type: checkboxes 10 | attributes: 11 | label: Pre-check 12 | description: | 13 | Please search through existing [issues](https://github.com/tektronix/tsp-toolkit/issues/) 14 | and [discussions](https://github.com/tektronix/tsp-toolkit/discussions) before entering a new feature request. 15 | 16 | Feel free to 👍 any open issues that match the problem you encountered! 17 | options: 18 | - label: I have searched the existing issues and discussions to make sure my problem has not already been addressed. 19 | required: true 20 | - label: I have looked at the "Known Issues" Section of the marketplace page of all TSP Toolkit extensions. 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: Feature Idea 25 | description: Let us know in as much detail as you can the feature you would like. 26 | placeholder: | 27 | Example: 28 | As a user, I would like to be able to see my instruments list in a side panel 29 | as they are discovered so I can find the instrument I'm looking for quickly. 30 | 31 | This would idealy include the model number, serial number, and connection 32 | information for each instrument. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: Community Support 5 | url: https://github.com/tektronix/tsp-toolkit/discussions 6 | about: Please ask and answer questions here. 7 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - closed 10 | push: 11 | tags: 12 | - "*" 13 | 14 | jobs: 15 | style: 16 | name: style 17 | runs-on: ubuntu-latest 18 | container: 19 | image: ghcr.io/tektronix/tsp-toolkit-build:latest 20 | credentials: 21 | username: ${{github.actor}} 22 | password: ${{secrets.GITHUB_TOKEN}} 23 | 24 | steps: 25 | - name: Tool Versions 26 | run: npx prettier --version 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | - name: run prettier 30 | run: npx prettier --list-different 31 | 32 | lint: 33 | name: lint 34 | runs-on: ubuntu-latest 35 | container: 36 | image: ghcr.io/tektronix/tsp-toolkit-build:latest 37 | credentials: 38 | username: ${{github.actor}} 39 | password: ${{secrets.GITHUB_TOKEN}} 40 | 41 | steps: 42 | - name: Tool Versions 43 | run: npx eslint --version 44 | - name: Checkout 45 | uses: actions/checkout@v4 46 | - name: Install dependencies 47 | run: | 48 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc 49 | npm install 50 | - name: Run eslint 51 | run: 'npx eslint --rule "{ prettier/prettier: off }" src' 52 | 53 | sbom: 54 | name: Generate CycloneDX 55 | # Temporarily skip this step because the cyclonedx package is has a vulnerability 56 | # https://github.com/CycloneDX/cyclonedx-node-npm/issues/1224 57 | if: false 58 | runs-on: ubuntu-latest 59 | container: 60 | image: ghcr.io/tektronix/tsp-toolkit-build:latest 61 | credentials: 62 | username: ${{github.actor}} 63 | password: ${{secrets.GITHUB_TOKEN}} 64 | steps: 65 | - name: Checkout 66 | uses: actions/checkout@v4 67 | - name: Install Tools 68 | run: | 69 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc 70 | npm ci --devDependencies 71 | - name: Generate NPM BOM 72 | run: npx @cyclonedx/cyclonedx-npm --output-format JSON --package-lock-only --output-reproducible --output-file npm.cdx.json 73 | - name: Upload Results 74 | uses: actions/upload-artifact@v4 75 | with: 76 | name: software-bom 77 | path: | 78 | **/*.cdx.json 79 | 80 | test: 81 | name: test 82 | runs-on: ubuntu-latest 83 | container: 84 | image: ghcr.io/tektronix/tsp-toolkit-build:latest 85 | credentials: 86 | username: ${{github.actor}} 87 | password: ${{secrets.GITHUB_TOKEN}} 88 | 89 | steps: 90 | - name: Tool Versions 91 | run: npx prettier --version 92 | - name: Checkout 93 | uses: actions/checkout@v4 94 | - name: Install dependencies 95 | run: | 96 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc 97 | npm install 98 | - name: Run Tests with Coverage 99 | run: npx nyc --nycrc-path=.nycrc.json npm run test-ci 100 | - name: Generate Coverage Reports 101 | run: npx nyc report --reporter=cobertura --reporter=text 102 | 103 | compile: 104 | name: Build and Package 105 | strategy: 106 | matrix: 107 | include: 108 | - runner: ubuntu-latest 109 | triple: x86_64-unknown-linux-gnu 110 | vscode-platform: linux-x64 111 | os: linux 112 | arch: x64 113 | - runner: windows-latest 114 | triple: x86_64-pc-windows-msvc 115 | vscode-platform: win32-x64 116 | os: win32 117 | arch: x64 118 | - runner: macos-latest 119 | triple: aarch64-apple-darwin 120 | vscode-platform: darwin-arm64 121 | os: darwin 122 | arch: arm64 123 | runs-on: ${{matrix.runner}} 124 | steps: 125 | - name: Tool Versions 126 | run: | 127 | npm --version 128 | - name: Checkout 129 | uses: actions/checkout@v4 130 | - name: Install dependencies 131 | run: | 132 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc 133 | npm install 134 | - name: Build 135 | run: npm run compile 136 | - name: npm Package 137 | run: | 138 | npx vsce package --target ${{matrix.vscode-platform}} 139 | - name: Upload Artifacts 140 | uses: actions/upload-artifact@v4 141 | with: 142 | name: package-${{matrix.vscode-platform}} 143 | path: "*.vsix" 144 | 145 | scan: 146 | name: AV Scan 147 | runs-on: ubuntu-latest 148 | strategy: 149 | matrix: 150 | include: 151 | - runner: ubuntu-latest 152 | triple: x86_64-unknown-linux-gnu 153 | vscode-platform: linux-x64 154 | os: linux 155 | arch: x64 156 | - runner: windows-latest 157 | triple: x86_64-pc-windows-msvc 158 | vscode-platform: win32-x64 159 | os: win32 160 | arch: x64 161 | - runner: macos-latest 162 | triple: aarch64-apple-darwin 163 | vscode-platform: darwin-arm64 164 | os: darwin 165 | arch: arm64 166 | needs: 167 | - compile 168 | container: 169 | image: ghcr.io/tektronix/tsp-toolkit-build:latest 170 | credentials: 171 | username: ${{github.actor}} 172 | password: ${{secrets.GITHUB_TOKEN}} 173 | steps: 174 | - name: Get Artifacts 175 | uses: actions/download-artifact@v4 176 | with: 177 | name: package-${{matrix.vscode-platform}} 178 | path: extension 179 | - name: Update ClamAV 180 | run: freshclam 181 | - name: Tool Versions 182 | run: | 183 | clamscan -V 184 | - name: Run ClamAV 185 | run: clamscan -v extension/*.vsix 186 | 187 | publish: 188 | name: Publish 189 | runs-on: ubuntu-latest 190 | permissions: 191 | contents: write 192 | checks: write 193 | pull-requests: read 194 | packages: read 195 | needs: 196 | - compile 197 | - test 198 | #- sbom 199 | - lint 200 | - style 201 | if: ${{ (endsWith(github.base_ref, 'main') && (contains(github.head_ref, 'release/')) && github.event.pull_request.merged ) }} 202 | steps: 203 | - name: Checkout 204 | uses: actions/checkout@v4 205 | with: 206 | fetch-depth: 0 207 | fetch-tags: true 208 | - name: Get Artifacts 209 | uses: actions/download-artifact@v4 210 | with: 211 | pattern: package-* 212 | merge-multiple: true 213 | path: extension 214 | - name: Publish to VSCode Marketplace 215 | run: | 216 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" > .npmrc 217 | # install vsce 218 | npm install --devDependencies 219 | for f in extension/*.vsix; do 220 | npx vsce publish --packagePath "$f" 221 | done 222 | env: 223 | VSCE_PAT: ${{secrets.VSCE_PAT}} 224 | 225 | release: 226 | name: Release 227 | runs-on: ubuntu-latest 228 | permissions: 229 | contents: write 230 | checks: write 231 | pull-requests: read 232 | needs: 233 | - compile 234 | - test 235 | #- sbom 236 | - lint 237 | - style 238 | if: ${{ (endsWith(github.base_ref, 'main') && (contains(github.head_ref, 'release/')) || github.event.pull_request.merged ) }} 239 | steps: 240 | - name: Checkout 241 | uses: actions/checkout@v4 242 | with: 243 | fetch-depth: 0 244 | fetch-tags: true 245 | - name: Get RC Version 246 | id: lasttag 247 | run: | 248 | COMMIT="${{github.sha}}" 249 | if ${{contains(github.head_ref, 'release/')}}; then 250 | V="${{github.head_ref}}" 251 | V="${V#release/}" 252 | else 253 | V="$(npm pkg get version)" 254 | echo "Extracted Version: $V" 255 | V="$(echo v"$V" | sed 's/\"//g')" 256 | echo "Cleaned up Version: $V" 257 | fi 258 | 259 | # Check to see if the version tag already exists 260 | # If it does, print a message and exit with an error code 261 | if [ $(git tag --list "$V") ]; then 262 | echo "Version tag already exists. Did you bump the version number?" 263 | exit 1 264 | fi 265 | 266 | # Create an RC release if 267 | # 1) This PR is a release branch that hasn't been merged to main. 268 | # 2) This is a feature branch being merged into the main branch. 269 | if ${{(! github.event.pull_request.merged && contains(github.head_ref, 'release/')) || (github.event.pull_request.merged && !contains(github.head_ref, 'release/'))}}; then 270 | V="${V}-$(git tag --list ${V}* | wc -l)" 271 | echo "RC Version: $V" 272 | fi 273 | 274 | CL=${V#v} 275 | CL=${CL%-*} 276 | 277 | echo "version=${V}" >> $GITHUB_OUTPUT 278 | echo "cl_version=${CL}" >> $GITHUB_OUTPUT 279 | echo "commit=${COMMIT}" >> $GITHUB_OUTPUT 280 | 281 | - run: 'git tag --list ${V}*' 282 | - name: Get Artifacts 283 | uses: actions/download-artifact@v4 284 | with: 285 | pattern: package-* 286 | merge-multiple: true 287 | path: extension 288 | #- name: Get SBOM 289 | # uses: actions/download-artifact@v4 290 | # with: 291 | # name: software-bom 292 | # path: sbom 293 | - name: Get Changelog for this Tag 294 | id: changelog 295 | uses: coditory/changelog-parser@v1 296 | with: 297 | version: ${{steps.lasttag.outputs.cl_version}} 298 | - name: Create Release 299 | uses: ncipollo/release-action@v1 300 | with: 301 | name: ${{steps.lasttag.outputs.version}} 302 | #artifacts: "extension/*.vsix,sbom/**/*" 303 | artifacts: "extension/*.vsix" 304 | body: | 305 | ## Features Requests / Bugs 306 | 307 | If you find issues or have a feature request, please enter a [new issue on GitHub](${{github.server_url}}/${{github.repository}}/issues/new). 308 | 309 | ## Installation 310 | 311 | View the installation instructions in the [README](${{github.server_url}}/${{github.repository}}/blob/main/README.md) 312 | 313 | ## Changelog 314 | 315 | ${{steps.changelog.outputs.description}} 316 | 317 | prerelease: ${{ (! github.event.pull_request.merged) || (github.event.pull_request.merged && ! contains(github.head_ref, 'release/')) }} 318 | commit: ${{steps.lasttag.outputs.commit}} 319 | makeLatest: true 320 | tag: ${{steps.lasttag.outputs.version}} 321 | 322 | -------------------------------------------------------------------------------- /.github/workflows/veracode.yml: -------------------------------------------------------------------------------- 1 | name: Veracode Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | veracode-sast-task: 11 | runs-on: ubuntu-latest 12 | name: Veracode SAST policy scan 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: create new package-lock.json 18 | run: | 19 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" > .npmrc 20 | npm install 21 | - name: ZIP source folder 22 | run: zip -r app.zip src package-lock.json 23 | - name: Run Veracode Policy scan 24 | uses: veracode/veracode-uploadandscan-action@0.2.6 25 | with: 26 | appname: "tsp-toolkit" 27 | createprofile: false 28 | version: ${{ github.sha }} 29 | filepath: "app.zip" 30 | scantimeout: 30 31 | vid: ${{ secrets.VERACODE_API_ID }} 32 | vkey: ${{ secrets.VERACODE_API_KEY }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # Don't track vsix files 133 | **/*.vsix 134 | -------------------------------------------------------------------------------- /.mocharc.yml: -------------------------------------------------------------------------------- 1 | recursive: true 2 | require: 3 | - ts-node/register 4 | - source-map-support/register 5 | spec: src/test/*.test.ts 6 | ui: tdd 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ;;;; 2 | ; npm projectconfig file: C:\git\Teaspoon\tsp-toolkit\.npmrc 3 | ; this is a simple ini-formatted file 4 | ; lines that start with semi-colons are comments 5 | ; run `npm help 7 config` for documentation of the various options 6 | ; 7 | ; Configs like `@scope:registry` map a scope to a given registry url. 8 | ; 9 | ; Configs like `///:_authToken` are auth that is restricted 10 | ; to the registry host specified. 11 | 12 | @tektronix:registry=https://npm.pkg.github.com 13 | 14 | ;;;; 15 | ; all available options shown below with default values 16 | ;;;; 17 | 18 | 19 | ; _auth=null 20 | ; access=null 21 | ; all=false 22 | ; allow-same-version=false 23 | ; also=null 24 | ; audit=true 25 | ; audit-level=null 26 | ; auth-type=web 27 | ; before=null 28 | ; bin-links=true 29 | ; browser=null 30 | ; ca=null 31 | ; cache=C:\Users\esarver1\AppData\Local/npm-cache 32 | ; cache-max=null 33 | ; cache-min=0 34 | ; cafile=null 35 | ; call= 36 | ; cert=null 37 | ; ci-name=null 38 | ; cidr=null 39 | ; color=true 40 | ; commit-hooks=true 41 | ; depth=null 42 | ; description=true 43 | ; dev=false 44 | ; 45 | ; diff-ignore-all-space=false 46 | ; diff-name-only=false 47 | ; diff-no-prefix=false 48 | ; diff-dst-prefix=b/ 49 | ; diff-src-prefix=a/ 50 | ; diff-text=false 51 | ; diff-unified=3 52 | ; dry-run=false 53 | ; editor=C:\windows\notepad.exe 54 | ; engine-strict=false 55 | ; fetch-retries=2 56 | ; fetch-retry-factor=10 57 | ; fetch-retry-maxtimeout=60000 58 | ; fetch-retry-mintimeout=10000 59 | ; fetch-timeout=300000 60 | ; force=false 61 | ; foreground-scripts=false 62 | ; format-package-lock=true 63 | ; fund=true 64 | ; git=git 65 | ; git-tag-version=true 66 | ; global=false 67 | ; globalconfig= 68 | ; global-style=false 69 | ; heading=npm 70 | ; https-proxy=null 71 | ; if-present=false 72 | ; ignore-scripts=false 73 | ; 74 | ; include-staged=false 75 | ; include-workspace-root=false 76 | ; init-author-email= 77 | ; init-author-name= 78 | ; init-author-url= 79 | ; init-license=ISC 80 | ; init-module=~/.npm-init.js 81 | ; init-version=1.0.0 82 | ; init.author.email= 83 | ; init.author.name= 84 | ; init.author.url= 85 | ; init.license=ISC 86 | ; init.module=~/.npm-init.js 87 | ; init.version=1.0.0 88 | ; install-links=true 89 | ; install-strategy=hoisted 90 | ; json=false 91 | ; key=null 92 | ; legacy-bundling=false 93 | ; legacy-peer-deps=false 94 | ; link=false 95 | ; local-address=null 96 | ; location=user 97 | ; lockfile-version=null 98 | ; loglevel=notice 99 | ; logs-dir=null 100 | ; logs-max=10 101 | ; long=false 102 | ; maxsockets=15 103 | ; message=%s 104 | ; node-options=null 105 | ; noproxy= 106 | ; offline=false 107 | ; 108 | ; omit-lockfile-registry-resolved=false 109 | ; only=null 110 | ; optional=null 111 | ; otp=null 112 | ; 113 | ; package-lock=true 114 | ; package-lock-only=false 115 | ; pack-destination=. 116 | ; parseable=false 117 | ; prefer-offline=false 118 | ; prefer-online=false 119 | ; prefix= 120 | ; preid= 121 | ; production=null 122 | ; progress=true 123 | ; proxy=null 124 | ; read-only=false 125 | ; rebuild-bundle=true 126 | ; registry=https://registry.npmjs.org/ 127 | ; replace-registry-host=npmjs 128 | ; save=true 129 | ; save-bundle=false 130 | ; save-dev=false 131 | ; save-exact=false 132 | ; save-optional=false 133 | ; save-peer=false 134 | ; save-prefix=^ 135 | ; save-prod=false 136 | ; scope= 137 | ; script-shell=null 138 | ; searchexclude= 139 | ; searchlimit=20 140 | ; searchopts= 141 | ; searchstaleness=900 142 | ; shell=C:\windows\system32\cmd.exe 143 | ; shrinkwrap=true 144 | ; sign-git-commit=false 145 | ; sign-git-tag=false 146 | ; strict-peer-deps=false 147 | ; strict-ssl=true 148 | ; tag=latest 149 | ; tag-version-prefix=v 150 | ; timing=false 151 | ; tmp=C:\Users\esarver1\AppData\Local\Temp 152 | ; umask=0 153 | ; unicode=false 154 | ; update-notifier=true 155 | ; usage=false 156 | ; user-agent=npm/{npm-version} node/{node-version} {platform} {arch} workspaces/{workspaces} {ci} 157 | ; userconfig=~/.npmrc 158 | ; version=false 159 | ; versions=false 160 | ; viewer=browser 161 | ; which=null 162 | ; 163 | ; workspaces=null 164 | ; workspaces-update=true 165 | ; yes=null 166 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@istanbuljs/nyc-config-typescript", 3 | "all": true, 4 | "cache": false, 5 | "clean": false, 6 | "silent": false, 7 | "include": [ 8 | "out/**", 9 | "src/**" 10 | ], 11 | "exclude": [ 12 | "**/test/**" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Directory globs 2 | **/target/ 3 | **/.cargo/ 4 | # File globs 5 | *.md 6 | # Directories 7 | .nyc_output/ 8 | .vscode/ 9 | coverage/ 10 | out/ 11 | # Files 12 | .gitlab-ci.yml 13 | .nycrc.json 14 | tsconfig.json 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": false, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "editorconfig.editorconfig", 6 | "dbaeumer.vscode-eslint", 7 | "esbenp.prettier-vscode", 8 | "rust-lang.rust-analyzer", 9 | "tamasfe.even-better-toml" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--profile-temp", 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: compile" 20 | }, 21 | { 22 | "name": "Mocha Tests", 23 | "request": "launch", 24 | "type": "node", 25 | "internalConsoleOptions": "openOnSessionStart", 26 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 27 | "args": [ 28 | "--config", 29 | ".mocharc.yml", 30 | ], 31 | "skipFiles": [ 32 | "/**" 33 | ] 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[json]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[yaml]": { 12 | "editor.autoIndent": "advanced", 13 | "editor.defaultFormatter": "esbenp.prettier-vscode" 14 | }, 15 | "search.exclude": { 16 | "coverage": true, 17 | "node_modules": true, 18 | "out": true, 19 | }, 20 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 21 | "typescript.tsc.autoDetect": "off", 22 | "cSpell.words": [ 23 | "categ", 24 | "IIDN", 25 | "LOGLOC", 26 | "rclick" 27 | ], 28 | "Lua.workspace.ignoreDir": [], 29 | "Lua.diagnostics.libraryFiles": "Disable", 30 | "Lua.runtime.version": "Lua 5.1", 31 | "Lua.runtime.builtin": { 32 | "basic": "disable", 33 | "bit": "disable", 34 | "bit32": "disable", 35 | "builtin": "disable", 36 | "coroutine": "disable", 37 | "debug": "disable", 38 | "ffi": "disable", 39 | "io": "disable", 40 | "jit": "disable", 41 | "jit.profile": "disable", 42 | "jit.util": "disable", 43 | "math": "disable", 44 | "os": "disable", 45 | "package": "disable", 46 | "string": "disable", 47 | "string.buffer": "disable", 48 | "table": "disable", 49 | "table.clear": "disable", 50 | "table.new": "disable", 51 | "utf8": "disable" 52 | }, 53 | "Lua.workspace.library": [ 54 | "c:\\Users\\esarver1\\.vscode\\extensions\\tektronix.tsp-toolkit-1.2.2-win32-x64\\node_modules\\@tektronix\\keithley_instrument_libraries\\keithley_instrument_libraries\\tsp-lua-5.0" 55 | ], 56 | } 57 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitlab/** 4 | src/** 5 | .gitattributes 6 | .gitignore 7 | vsc-extension-quickstart.md 8 | **/.editorconfig 9 | **/.eslintrc.yml 10 | **/.nycrc.json 11 | **/.prettierignore 12 | **/.prettierrc 13 | **/tsconfig.json 14 | **/*.map 15 | **/*.ts 16 | 17 | **/Gulpfile.js 18 | 19 | **/ki-comms/.vscode/ 20 | **/ki-comms/scripts/ 21 | **/ki-comms/src/ 22 | **/ki-comms/target/ 23 | 24 | **/*.d 25 | 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | 18 | 19 | ## [1.2.4] 20 | 21 | ### Changed 22 | - Sanitize log files to remove sensitive information 23 | ### Fixed 24 | - (**tsp-toolkit-kic-cli**) Unable to fetch TSPLink network information from Trebuchet 25 | ### Added 26 | - (**tsp-toolkit-webhelp-to-json**) Language feature support has been added for MP5103, MPSU502ST and MSMU60-2 27 | - (**tsp-toolkit-webhelp**) Added MP5103, MPSU502ST and MSMU60-2 webhelp documentation 28 | 29 | ## [1.2.3] 30 | 31 | ### Fixed 32 | - Deprecate old system configurations 33 | - Node and slot table autocompletion problem 34 | - System configurations UI controls alignment issues 35 | - (**tsp-toolkit-kic-lib**) Further progress indicator improvements 36 | - (**tsp-toolkit-kic-cli**) Recognize when socket has been terminated from device-side more quickly and close. 37 | 38 | ### Added 39 | - Update support for already saved systems 40 | 41 | ### Changed 42 | - Update branding to be Tektronix instead of Keithley 43 | 44 | ## [1.2.2] 45 | 46 | ### Added 47 | 48 | - System configurations webview UI implemented 49 | - slot input support for modules 50 | - UI option to fetch localnode and tsplink nodes for active connection 51 | - (**tsp-toolkit-kic-lib**) Progress indicators for very large scripts and firmware files 52 | 53 | ### Changed 54 | 55 | - (**tsp-toolkit-kic-cli**) JSON structure updated to include module info 56 | - (**tsp-toolkit-kic-cli**) Write fetched configuration to setting.json file 57 | - (**tsp-toolkit-kic-lib**) No longer need to call `slot.stop` and `slot.start` since that is done by firmware now 58 | 59 | ### Fixed 60 | 61 | - (**tsp-toolkit-kic-lib**) Issues with firmware updates over USBTMC on some instruments 62 | 63 | 64 | ## [1.2.1] 65 | 66 | ### Fixed 67 | 68 | - Fix changes that broke advanced feature compatibility (closed beta) 69 | 70 | ### Changed 71 | 72 | - Updated branding in README and package.json 73 | 74 | ## [1.2.0] 75 | 76 | ### Changed 77 | 78 | - Major visual overhaul to the instruments pane 79 | 80 | ### Fixed 81 | 82 | - Don't create a `.vscode` folder with `config.tsp.json` if the workspace folder does not 83 | contain a `.tsp` file. 84 | 85 | ### Added 86 | 87 | - Optionally use `firmware.valid` attribute for applicable instruments to only run 88 | upgrade on valid firmware 89 | 90 | ## [1.1.1] 91 | 92 | ### Added 93 | - support dynamically creating enums for configured nodes 94 | - handling dynamically creating enums for trigger.model.setblock() command parameter 95 | 96 | ### Changed 97 | 98 | - Change location of TSP Resources sections in Walkthrough and README 99 | 100 | ### Fixed 101 | - making sure that node[N].execute(), node[N].getglobal() and node[N].setglobal() only 102 | visible in TSP-Link systems 103 | - **tsp-toolkit-kic-cli:** be sure not to call TSP commands when getting instrument 104 | information since the instrument (TTI) could be in a non-TSP language mode. 105 | - **tsp-toolkit-kic-cli:** change the language of an instrument when getting instrument 106 | information 107 | 108 | ## [1.1.0] 109 | 110 | ### Added 111 | 112 | - Added `tsp.dumpQueueOnConnect` setting to dump the instrument output queue before 113 | clearing it so it can be printed when opening the terminal 114 | - Added progress indication to connection notification 115 | 116 | ### Changed 117 | 118 | - Discover will use LXI identification page to get instrument information instead of `*IDN?` 119 | 120 | ### Fixed 121 | 122 | - Fixed Readme links and worked around markdown parser error 123 | - Close and reset instrument connections when extension is deactivated 124 | 125 | 126 | ## [1.0.0] 127 | 128 | ### Added 129 | 130 | - Add reset menu to reset instrument from the instruments list 131 | 132 | ### Fixed 133 | - Fix issue with error propagation of send script to terminal command 134 | - Fix issue where connecting to an instrument can fail with a mysterious error message 135 | - Connection name needs to be same in Instruments pane, terminal and quick pick for a given instrument connection 136 | 137 | 138 | ## [0.18.2] 139 | 140 | ### Added 141 | 142 | - Added walkthrough document 143 | - Added macOS support (LAN only) 144 | - Added file logging for extension code 145 | 146 | ### Changed 147 | 148 | - Change TSP view container icon to improve visibility 149 | - Change TSP terminal icon to improve visibility 150 | 151 | ### Fixed 152 | 153 | - **tsp-toolkit-kic-cli**: Only call `readSTB` after a command or script is written to 154 | the instrument 155 | - When entering only visa instrument address, connection is saved with correct model and serial number (TSP-839) 156 | 157 | ### Removed 158 | 159 | - Removed VSCode Output pane logging 160 | 161 | ## [0.18.1] 162 | 163 | ### Added 164 | 165 | - Support for lua 5.0 library 166 | 167 | ### Changed 168 | 169 | - Updated sub commands help text 170 | 171 | ### Fixed 172 | 173 | - Showing correct model and serial number information instead of undefined (TSP-809) 174 | 175 | ## [0.18.0] 176 | 177 | ### Fixed 178 | 179 | - Many notification at TSP Toolkit Activation 180 | - Make *.tsp=lua file association a User-level setting instead of a workspace setting 181 | 182 | 183 | ### Added 184 | 185 | - Added VISA support for connecting to an instrument 186 | - Added platform-specific extension versions for Windows x86\_64 and Linux x86\_64 systems 187 | - Added a + button to the Instruments pane title bar 188 | - Added icon to tsp-toolkit connection terminal 189 | - Added run button to runs the current script 190 | - **tsp-toolkit-webhelp-to-json:** Added language feature support for 2651A, 2657A and 2601B-PULSE models 191 | - **tsp-toolkit-webhelp:** Added webhelp documents for 2651A, 2657A and 2601B-PULSE models 192 | 193 | ### Changed 194 | 195 | - Automatically assume a new connection is desired if the input to the "TSP: Connect" input box 196 | has no results and is a valid connection string. 197 | 198 | 199 | ### Removed 200 | 201 | - Raw USBTMC support has been removed in favor of VISA 202 | 203 | ## [0.17.0] 204 | 205 | ### Fixed 206 | 207 | - Successful connection to tspop adds the instrument details to Instruments pane (TSP-773) 208 | - **tsp-toolkit-kic-cli:** Fixed an indexing issue for upgrading module firmware (TSP-761) *Open Source Contribution: c3charvat, amcooper181* 209 | 210 | ### Added 211 | 212 | - Reset instrument if closed unexpectedly using the trashcan on the terminal (TSP-730) 213 | - Add logging for terminal and discover 214 | - Default friendly name if user doesn't provide one (TSP-757) 215 | 216 | ## [0.16.4] 217 | 218 | ### Changed 219 | 220 | - "TSP: Open Terminal" command has been renamed to "TSP: Connect" 221 | - "Open Terminal" should just open the terminal, not just in the command palette (TSP-464) 222 | 223 | ### Fixed 224 | 225 | - Send script to all terminals is failing(TSP-598) 226 | - **tsp-toolkit-kic-cli:** Renamed update to upgrade for firmware upgrade in CLI arguments (TSP-741) 227 | 228 | ## [0.16.1] 229 | 230 | ### Fixed 231 | 232 | - If instrument connection address changes, it is updated in "Instruments" pane (TSP-634) 233 | - Instrument tree is updated only when new instrument is discovered/saved/removed (TSP-634) 234 | - Renamed update to upgrade for firmware upgrade (TSP-463) 235 | - **tsp-toolkit-kic-cli:** renamed update to upgrade (TSP-463) 236 | - **tsp-toolkit-kic-cli:** changed lxi and usb device info struct's instrument address field to same name (TSP-634) 237 | - **tsp-toolkit-kic-cli:** Fix Support for FW flash on the 3706B and 70xB (_Open Source Contribution: c3charvat_) 238 | - **tsp-toolkit-webhelp:** display.input.option() command signature has been corrected for all tti models 239 | 240 | 241 | ## [0.15.3] 242 | 243 | ### Fixed 244 | 245 | - Corrected extension description 246 | - Remove `:` from port number 247 | - Adding tsplink nodes in config.tsp.json file does not load definitions for added node lua table 248 | - Changed literal `\` to `path.join()` when populating config.tsp.json 249 | - Remove debugger-related items from `package.json` 250 | - **tsp-toolkit-kic-cli:** Fix issue where unrecognized model number causes kic-cli to never exit (TSP-645) 251 | - **tsp-toolkit-kic-cli:** Fix issue in which the prompt would be displayed immediately after loading a script 252 | 253 | 254 | ## [0.15.2] 255 | 256 | ### Fixed 257 | 258 | - Removed debugger related code from package.json (TSP-436) 259 | 260 | ## [0.15.1] 261 | 262 | ### Changed 263 | 264 | - **tsp-toolkit-kic-lib:** Clean up instrument connections when an AsyncStream 265 | stream is dropped 266 | 267 | ### Fixed 268 | 269 | - Only single entry per instrument in settings.json file, irrespective of number of times it is saved (TSP-616) 270 | - **tsp-toolkit-kic-cli:** Remove errors when fetching nodes with `.nodes` command 271 | 272 | ### Security 273 | 274 | - **tsp-toolkit-kic-cli:** Bump `h2` crate version 275 | 276 | ## [0.15.0] 277 | 278 | ### Fixed 279 | 280 | - Saved instruments persist in the Instruments pane after restarting the extension (TSP-510) 281 | - **tsp-tookit-kic-cli:** Change language to `TSP` after connection to TTI instrument (TSP-561) 282 | - **tsp-toolkit-kic-cli:** Fix script name issues if the name contains special characters (TSP-505) 283 | - **tsp-toolkit-kic-lib:** Use `*TST?` to check login state instead of 284 | `print("unlocked")` just in case we are in a SCPI command set mode. 285 | 286 | ## [0.14.1] 287 | 288 | ### Changed 289 | 290 | - **kic-cli:** Prepend `kic_` to scripts loaded by kic cli to prevent name-collisions (TSP-505) 291 | 292 | ### Fixed 293 | 294 | - **kic-cli:** Update Dependencies (TSP-576) 295 | - **keithley-instrument-libraries:** Fix command-set issues for legacy instruments (TSP-569) 296 | 297 | ## [0.13.2] 298 | 299 | ### Changed 300 | 301 | - Change references to `KIC` to be `TSP` instead, add additional Marketplace metadata (TSP-457) 302 | - Prepare README.md for Marketplace (TSP-509) 303 | 304 | ### Added 305 | 306 | - Added discovery progress message (TSP-504) 307 | 308 | ### Fixed 309 | 310 | - Model number in TSP config is no longer case sensitive (TSP-514) 311 | 312 | 313 | ## [0.13.0] 314 | 315 | ### Added 316 | 317 | - Added TSP Toolkit Logo (TSP-498) 318 | 319 | ## [0.12.2] 320 | 321 | ### Fixed 322 | 323 | - Fix remove saved instrument issue (TSP-483) 324 | 325 | ## [0.12.1] 326 | 327 | ### Changed 328 | 329 | - Restore password hide feature back after ki-comms refactor (TSP-363) 330 | - Implement Password prompt (TSP-480) 331 | 332 | ### Fixed 333 | 334 | - Extension wants a password when there isn't one (TSP-416) 335 | 336 | ## [0.12.0] 337 | 338 | ### Added 339 | 340 | - Add message when starting FW upgrade (TSP-455) 341 | - Feature to retrieve TSP-Link network details 342 | 343 | 344 | [Unreleased]: https://github.com/tektronix/tsp-toolkit/compare/v1.2.4...HEAD 345 | [1.2.4]: https://github.com/tektronix/tsp-toolkit/releases/tag/v1.2.4 346 | [1.2.3]: https://github.com/tektronix/tsp-toolkit/releases/tag/v1.2.3 347 | [1.2.2]: https://github.com/tektronix/tsp-toolkit/releases/tag/v1.2.2 348 | [1.2.1]: https://github.com/tektronix/tsp-toolkit/releases/tag/v1.2.1 349 | [1.2.0]: https://github.com/tektronix/tsp-toolkit/releases/tag/v1.2.0 350 | [1.1.1]: https://github.com/tektronix/tsp-toolkit/releases/tag/v1.1.1 351 | [1.1.0]: https://github.com/tektronix/tsp-toolkit/releases/tag/v1.1.0 352 | [1.0.0]: https://github.com/tektronix/tsp-toolkit/releases/tag/v1.0.0 353 | [0.18.2]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.18.2 354 | [0.18.1]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.18.1 355 | [0.18.0]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.18.0 356 | [0.17.0]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.17.0 357 | [0.16.4]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.16.4 358 | [0.16.1]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.16.1 359 | [0.15.3]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.15.3 360 | [0.15.2]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.15.2 361 | [0.15.1]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.15.1 362 | [0.15.0]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.15.0 363 | [0.14.1]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.14.1 364 | [0.13.2]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.13.2 365 | [0.13.0]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.13.0 366 | [0.12.2]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.12.2 367 | [0.12.1]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.12.1 368 | [0.12.0]: https://github.com/tektronix/tsp-toolkit/releases/tag/v0.12.0 369 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @tektronix/tsp-toolkit 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | ## Dependencies 4 | 5 | In order to contribute code to this project, you must have the following dependencies installed: 6 | 7 | * NodeJS version 20.11.0 or later 8 | * Rust (latest stable version) 9 | 10 | ### To check 11 | 12 | * Check NodeJS is installed and is correct version: `node --version` 13 | * `node` should show 20.11.x where "x" is any number `>= 0` 14 | * Check that `npm` is installed: `npm --version` 15 | * `npm` should be present, the version is not as important 16 | * Check Rust version with `rustup --version` or `rustc --version` 17 | * `rustup` should indicate that the stable Rust compiler is installed 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2024 Tektronix, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tektronix TSP™ Toolkit 2 | 3 | This is an open source project from Tektronix. We welcome 4 | any [feedback][tsp-toolkit-issues] on the GitHub repo for this project. 5 | 6 | Tektronix TSP™ Toolkit is a [Visual Studio Code][code] extension that provides rich 7 | support for Test Script Processor ([TSP][tsp]) technology to edit and execute 8 | scripts on TSP-enabled Tektronix and Keithley instruments. The extension includes command-set documentation and language features such as 9 | syntax error detection and code navigation (provided by [sumneko.lua][sumneko]) as well as 10 | code-completion suggestions, inline help, and TSP command documentation. 11 | 12 | ## Demo Video 13 | 14 | 15 | 16 | ## Resources and Tutorials 17 | 18 | - [TSP Toolkit Feature Walkthrough][tsp-toolkit-feature-walkthrough] 19 | - [TSP Landing Page on Tek.com][tsp] 20 | - [TSP Video Series][tsp-video-series] 21 | - [App Note: How to Write TSP Scripts for TSP][app-note-how-to-write-tsp-scripts] 22 | - [TSP Script Example Repository][tsp-script-examples] 23 | 24 | 25 | ## Installed Extensions 26 | 27 | Tektronix TSP Toolkit will automatically install the [sumneko.lua][sumneko] 28 | extension to use all of the language features it provides. 29 | 30 | Extensions installed through the marketplace are subject to the [Marketplace Terms of Use][marketplace-tou]. 31 | 32 | ## Quick Start 33 | 34 | - **Step 1.** Connect your TSP-enabled Tektronix and Keithley instrument to your local network (LAN). 35 | - **Step 2.** Install the [Tektronix TSP Toolkit Visual Studio Code Extension][tsp-toolkit-marketplace]. 36 | - **Step 3.** Open or create a folder for your TSP project. 37 | 38 | ![Open Folder][pic-open-folder] 39 | 40 | - **Step 4.** [Configure your project](#configure-your-project) for your [TSP-Link™][tsp-link] instrument configuration. 41 | - **Step 5.** Edit and run your TSP scripts by right-clicking them in the file explorer, 42 | file tabs, or editor window and selecting "Send Script to Terminal" 43 | 44 | ![Send Script to Terminal][pic-send-script-to-terminal] 45 | 46 | ### Usage Notes 47 | 48 | When running scripts or commands via the terminal, errors are only fetched _after_ the 49 | requested action completes. No new errors will be printed while the operation is in 50 | progress. 51 | 52 | ## Useful Commands 53 | 54 | Open the Command Pallette (Cmd+Shift+P on macOS and 55 | Ctrl+Shift+P on Windows or Linux), then type one of the 56 | following commands: 57 | 58 | | Command | Description | Shortcut | 59 | |:---------------------------------|:------------------------------------------------------------------------------------|:------------------------------------------------| 60 | | TSP: Connect | Opens a new terminal session to an instrument (be sure to close with `.exit`, see the [Known Issues](#known-issues) section below) | | 61 | | TSP: Send Script to Terminal | Sends the script in the current editor window to the currently connected instrument | | 62 | 63 | To see all available Tektronix TSP Toolkit commands, open the Command Pallette and type `TSP`. 64 | 65 | To see all available context-sensitive options, right-click on your active editor window while a `*.tsp` file is open: 66 | 67 | | Context-Sensitive Option | Description | 68 | |:-----------------------------|:----------------------------------------------------------------------------------------------| 69 | | Send Script to All Terminals | Send the current script to all the currently connected instruments with active terminals open | 70 | | Send Script to Terminal | Send the current script to the currently connected instrument | 71 | 72 | 73 | ## Configure Your Project 74 | 75 | There are two ways to configure your project to have language features for your TSP-Link 76 | node network: Automatic or Manual. 77 | After completing either method, you will be shown relevant code completion suggestions, 78 | signature help, and documentation for the instruments in your TSP-Link network. 79 | 80 | ### Automatic Configuration 81 | 82 | If you are already connected to a physical instrument with your TSP-Link network configured, 83 | then it is possible to have TSP Toolkit automatically configure your project for you. 84 | 85 | 1. Open any workspace folder in VSCode. 86 | 2. Connect to your instrument using the discovery pane or the `TSP: Connect` command. 87 | 3. Open the side bar by clicking the TSP Toolkit icon in the Activity Bar (usually on the left side of VSCode). 88 | 4. In the TSP Toolkit side pane, expand the **SYSTEM CONFIGURATIONS** section. 89 | 5. Click the **Fetch connected instrument and its TSP-Link nodes** icon. 90 | 6. Enter a name for the new system when prompted. 91 | 92 | 93 | ### Manual Configuration 94 | 95 | 1. Open any workspace folder in VSCode. 96 | 2. Open the side bar by clicking the TSP Toolkit icon in the Activity Bar (usually on the left side of VSCode). 97 | 3. In the TSP Toolkit side pane, expand the **SYSTEM CONFIGURATIONS** section. 98 | 4. Click the **Add new System** icon. 99 | 5. Enter the required system details and save. 100 | 101 | 102 | 103 | ## Feature Details 104 | 105 | The Tektronix TSP Toolkit includes: 106 | 107 | - **Language Features:** Write your code with the assistance of autocompletion and 108 | syntax checking 109 | - **Hover Help:** Access detailed information on individual commands such as definition, 110 | accepted parameters, and usage examples 111 | - **Command Interface:** Send commands and interact directly with your instruments 112 | through the terminal 113 | - **Instrument Autodiscovery:** Discover available instruments on your local network 114 | - **Instrument Firmware Upgrade:** Remotely upgrade the instrument firmware 115 | 116 | ## Supported Locales 117 | 118 | The extension is currently only available in English. 119 | 120 | 121 | ## Questions, Issues, Feature Requests, and Contributions 122 | - If you come across a problem with the extension, please file an [issue][tsp-toolkit-issues] 123 | - Any and all feedback is appreciated and welcome! 124 | - If someone has already filed an [issue][tsp-toolkit-issues] that encompasses your 125 | feedback, please leave a 👍/👎 reaction on the issue. Otherwise please start a new 126 | [discussion][tsp-toolkit-discussions] 127 | - If on Windows, you must have [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170). Please ensure you have this installed. 128 | 129 | 130 | ## Known Issues 131 | 132 | We are constantly working to improve the stability and reliability of this software. Here 133 | are the known issues that we are working to fix. If you come across new issues, 134 | please let us know! See the [next section](#questions-issues-feature-requests-and-contributions) 135 | for more information. 136 | 137 | - Due to limitations in instrument firmware, script names longer than 27 characters will 138 | be truncated to 27 characters. If multiple scripts have names that are the same up to 139 | the 27th character, the second script will overwrite the first. 140 | - Some instruments (2450, 2460, 2461, 2470, DAQ6500, DMM7510, 3706A, 707B, 708B) encounter 141 | a fatal error if the trash can is used to close the connected terminal if the instrument 142 | is connected via GPIB on VISA. Be sure to type `.exit` when connected to one of these 143 | instruments over GPIB. 144 | - The list of instruments that support language features is limited to the following: 145 | - 2450 146 | - 2460 147 | - 2461 148 | - 2470 149 | - 2601B 150 | - 2601B-PULSE 151 | - 2602B 152 | - 2604B 153 | - 2611B 154 | - 2612B 155 | - 2614B 156 | - 2634B 157 | - 2635B 158 | - 2636B 159 | - 2651A 160 | - 2657A 161 | - DMM7510 162 | - Upgrading firmware on the 3706A, 707B, and 708B instruments is not successful. This will NOT 163 | render the instrument inoperable, but will not complete successfully. 164 | 165 | 166 | [app-note-how-to-write-tsp-scripts]: https://www.tek.com/en/documents/application-note/how-to-write-scripts-for-test-script-processing-(tsp) 167 | [code]: https://code.visualstudio.com/ 168 | [marketplace-tou]: https://cdn.vsassets.io/v/M146_20190123.39/_content/Microsoft-Visual-Studio-Marketplace-Terms-of-Use.pdf 169 | [sumneko]: https://marketplace.visualstudio.com/items?itemName=sumneko.lua 170 | [tsp-link]: https://www.tek.com/en/video/product-features/what-is-tsp-link 171 | [tsp-script-examples]: https://github.com/tektronix/keithley/ 172 | [tsp-toolkit-feature-walkthrough]: https://www.tek.com/en/documents/application-note/harness-the-power-of-tsp-toolkit-software 173 | [tsp-toolkit-marketplace]: https://marketplace.visualstudio.com/items?itemName=Tektronix.tsp-toolkit 174 | [tsp-toolkit]: https://www.tek.com/software/tsp-toolkit-scripting-tool 175 | [tsp-toolkit-issues]: https://github.com/tektronix/tsp-toolkit/issues 176 | [tsp-toolkit-contributing]: ./CONTRIBUTING.md 177 | [tsp-toolkit-discussions]: https://github.com/tektronix/tsp-toolkit/discussions 178 | [tsp-toolkit-dev-process]: ./CONTRIBUTING.md#development-process 179 | [tsp-video-series]: https://www.youtube.com/@tektronix/search?query=TSP 180 | [tsp]: https://www.tek.com/en/solutions/application/test-automation/tsp-for-test-automation 181 | 182 | 183 | [pic-send-script-to-terminal]: https://github.com/tektronix/tsp-toolkit/blob/main/images/SendScriptToTerminal.png?raw=true 184 | [pic-open-folder]: https://github.com/tektronix/tsp-toolkit/blob/main/images/OpenFolder.png?raw=true 185 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # `teaspoon` Release Process 2 | 3 | ## Dependencies 4 | 5 | `teaspoon` has several dependencies, be sure to follow the `RELEASE.md` document in each of those projects before following the rest of this process. 6 | 7 | * `ki-comms` 8 | * `keithley_instrument_libraries` (project: jsonToLuaParser) 9 | * `tsp-2600series-web-help-documents` 10 | 11 | ## Process 12 | 13 | ### 1. Pull Latest 14 | 15 | On your development machine, pull the latest `dev` and `main` branches 16 | 17 | ```bash 18 | # Development Machine 19 | git switch dev 20 | git pull 21 | git switch main 22 | git pull 23 | ``` 24 | 25 | ### 2. Create Release Branch from `main` 26 | 27 | On your development machine, create a release branch off of main 28 | 29 | ```bash 30 | # Development Machine 31 | git switch main 32 | git switch -c release/vX.Y.Z 33 | ``` 34 | 35 | ### 3. Merge `dev` to New Release Branch 36 | 37 | Merge `dev` into the new release branch, handling any merge conflicts. 38 | 39 | ```bash 40 | # Development Machine 41 | git switch release/vX.Y.Z 42 | git merge dev 43 | ``` 44 | 45 | If there are conflicts on the `package-lock.json` file, simply delete it and run 46 | `npm install --devDependencies`. 47 | 48 | Commit the fully-resolved merge with the default merge commit message. 49 | 50 | ### 4. Update the `CHANGELOG.md` File 51 | 52 | Change the "Unreleased" section to the current version number, with the link to the 53 | release underneath (see all previous versions for examples). 54 | 55 | ```diff 56 | -## [Unreleased] 57 | + 58 | +## [vX.Y.Z] 59 | +[vX.Y.Z Release Page] 60 | ``` 61 | 62 | Be sure to add the associated links at the bottom of the file underneath the "Unreleased" reference: 63 | 64 | ```diff 65 | [Unreleased]: https://git.keithley.com/trebuchet/teaspoon/teaspoon/-/tree/dev 66 | +[vX.Y.Z]: https://git.keithley.com/trebuchet/teaspoon/teaspoon/-/compare/v(X.Y.Z - 1)...vX.Y.Z?from_project_id=33 67 | +[vX.Y.Z Release Page]: https://git.keithley.com/trebuchet/teaspoon/teaspoon/-/releases/vX.Y.Z 68 | ``` 69 | (Where `v(X.Y.Z - 1)` is the previous released version) 70 | 71 | Be sure to check the merge requests for this past version to make sure everything was added to the changelog. Add anything that was missed (with JIRA issue number). 72 | 73 | You can do that with a query like the one at this link: https://git.keithley.com/trebuchet/teaspoon/teaspoon/-/merge_requests?scope=all&state=merged&deployed_after=03%2F29%2F2023&deployed_before=04%2F14%2F2023 74 | 75 | ### 5. Update the Version Information 76 | 77 | Update the `package.json` version numbers: 78 | 79 | > **package.json** 80 | > ```diff 81 | > - "version": "0.6.0", 82 | > + "version": "X.Y.Z", 83 | > ``` 84 | 85 | Update the version number of all dependencies: 86 | 87 | > **package.json** 88 | > ```diff 89 | > - "@trebuchet/tsp-2600series-web-help-documents": "^0.6.0", 90 | > - "@trebuchet/keithley_instrument_libraries": "^0.6.0", 91 | > - "@trebuchet/ki-comms": "^0.6.0", 92 | > + "@trebuchet/tsp-2600series-web-help-documents": "^X.Y.Z", 93 | > + "@trebuchet/keithley_instrument_libraries": "^X.Y.Z", 94 | > + "@trebuchet/ki-comms": "^X.Y.Z", 95 | > ``` 96 | 97 | After updating these files, make sure to update the lock files 98 | 99 | ```bash 100 | # Development Machine 101 | npm install --devDependencies 102 | ``` 103 | 104 | Commit the CHANGELOG.md and package version changes in a single commit with something similar to: 105 | 106 | ``` 107 | Update Version Numbers 108 | ``` 109 | 110 | ### 5. Create Merge Requests 111 | 112 | Push the release branch to GitLab: 113 | 114 | ```bash 115 | git push -u origin release/vX.Y.Z 116 | ``` 117 | 118 | #### Create a merge request from the `release/vX.Y.Z` branch into `main` with the following details: 119 | 120 | > * From `release/vX.Y.Z` into `main` (you will need to "Change branches" for this, as it defaults to `dev`) 121 | > * **Title:** Release vX.Y.Z 122 | > * **Description:** Delete template text and insert "Internal vX.Y.Z Release" 123 | > * **Assignee:** Yourself 124 | > * **Reviewer:** Pick someone 125 | > * **Milestone:** Select the appropriate milestone (vX.Y.Z) 126 | > * **Labels:** Add "Release" 127 | > * **Merge Options** 128 | > - Select **ONLY** "Squash commits when merge request is accepted" 129 | 130 | 131 | #### Create a merge request from the `release/vX.Y.Z` branch into `dev` with the following details: 132 | 133 | > * From `release/vX.Y.Z` into `dev` 134 | > * **Title:** Release vX.Y.Z into dev 135 | > * **Description:** Delete template text and insert "Internal vX.Y.Z Release into dev" 136 | > * **Assignee:** Yourself 137 | > * **Reviewer:** Pick someone 138 | > * **Milestone:** Select the appropriate milestone (vX.Y.Z) 139 | > * **Labels:** Add "Release: Dev" 140 | > * **Merge Options** 141 | > - Select **ONLY** "Delete source branch when merge request is accepted" 142 | 143 | ### 6. Complete Merge into `main` FIRST 144 | 145 | Complete the merge into the `main` branch. 146 | 147 | 148 | ### 7. Tag `main` with `vX.Y.Z` 149 | 150 | Create a [tag](https://git.keithley.com/trebuchet/teaspoon/teaspoon/-/tags) for the new version. 151 | > * **Tag Name:** `vX.Y.Z` 152 | > * **Create from:** `main` (This is NOT default, make sure to change it) 153 | > * **Message:** Internal vX.Y.Z Release 154 | 155 | Wait for the automatically started [pipeline](https://git.keithley.com/trebuchet/teaspoon/teaspoon/-/pipelines) to complete. 156 | 157 | ### 8. Verify Release 158 | 159 | Check the following to ensure that the release completed successfully: 160 | 161 | * [Releases page](https://git.keithley.com/trebuchet/teaspoon/teaspoon/-/releases) should show `vX.Y.Z` 162 | 163 | ### 9. Update Release Notes 164 | 165 | The release notes will have "TBD" in the "Added Features" and "Known Issues" sections. Copy all the sections from the CHANGELOG.md file for the current version release and past them into the "Added Features" section. List any known issues in the "Known Issues" section. 166 | 167 | ### 10. Complete Merge into `dev` 168 | 169 | This will delete the `release/vX.Y.Z` branch. 170 | 171 | -------------------------------------------------------------------------------- /images/OpenFolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/images/OpenFolder.png -------------------------------------------------------------------------------- /images/SendScriptToTerminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/images/SendScriptToTerminal.png -------------------------------------------------------------------------------- /keithley-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/keithley-logo.ico -------------------------------------------------------------------------------- /npm.licenses.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/npm.licenses.json -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsp-toolkit", 3 | "publisher": "Tektronix", 4 | "displayName": "Tektronix TSP Toolkit", 5 | "description": "VSCode extension for Tektronix and Keithley instruments that support Test Script Processor (TSP)", 6 | "version": "1.2.4", 7 | "icon": "./resources/TSP_Toolkit_128x128.png", 8 | "galleryBanner": { 9 | "color": "#EEEEEE", 10 | "theme": "light" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/tektronix/tsp-toolkit.git" 15 | }, 16 | "engines": { 17 | "vscode": "^1.92.0" 18 | }, 19 | "categories": [ 20 | "Other" 21 | ], 22 | "activationEvents": [ 23 | "onStartupFinished" 24 | ], 25 | "pricing": "Free", 26 | "bugs": { 27 | "url": "https://github.com/tektronix/tsp-toolkit/issues" 28 | }, 29 | "qna": "https://github.com/tektronix/tsp-toolkit/discussions", 30 | "contributors": [ 31 | "esarver" 32 | ], 33 | "maintainers": [ 34 | "esarver" 35 | ], 36 | "license": "Apache-2.0", 37 | "main": "./out/extension.js", 38 | "contributes": { 39 | "walkthroughs": [ 40 | { 41 | "id": "getting-started", 42 | "title": "Getting Started with TSP Toolkit", 43 | "description": "This guide will walk you through some setup and the features of the Tektronix TSP™ Toolkit.", 44 | "when": "true", 45 | "icon": "./resources/TSP_Toolkit_128x128.png", 46 | "featuredFor": [ 47 | "**/*.tsp" 48 | ], 49 | "steps": [ 50 | { 51 | "id": "resources", 52 | "title": "TSP Resources", 53 | "description": "- [Application Note: How to write scripts for TSP](https://www.tek.com/en/documents/application-note/how-to-write-scripts-for-test-script-processing-(tsp%29)\n- [TSP page on Tek.com](https://www.tek.com/en/solutions/application/test-automation/tsp-for-test-automation)\n- [TSP video series](https://www.youtube.com/@tektronix/search?query=TSP)", 54 | "media": { 55 | "image": "./resources/walkthrough/TSPResources.png", 56 | "altText": "TSP Product Page" 57 | }, 58 | "completionEvents": [ 59 | "onLink:https://www.tek.com/en/documents/application-note/how-to-write-scripts-for-test-script-processing-(tsp)", 60 | "onLink:https://www.tek.com/en/solutions/application/test-automation/tsp-for-test-automation", 61 | "onLink:https://www.youtube.com/@tektronix/search?query=TSP" 62 | ] 63 | }, 64 | { 65 | "id": "windows-deps", 66 | "title": "Install Dependencies", 67 | "description": "The Microsoft Windows version of this extension requires [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version) to be installed. If you don't have it installed, you will need to install it before instrument discovery or the terminal will work.", 68 | "media": { 69 | "markdown": "./resources/walkthrough/WindowsDeps.md" 70 | }, 71 | "when": "isWindows", 72 | "completionEvents": [ 73 | "onLink:https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version" 74 | ] 75 | }, 76 | { 77 | "id": "linux-deps", 78 | "title": "Install Dependencies", 79 | "description": "", 80 | "media": { 81 | "markdown": "./example.md" 82 | }, 83 | "when": "isLinux", 84 | "completionEvents": [] 85 | }, 86 | { 87 | "id": "open-tsp-folder", 88 | "title": "Open TSP Project Folder", 89 | "description": "Open a folder containing TSP files or open a folder and create TSP files within the folder.\n[Open Folder](command:vscode.openFolder)", 90 | "media": { 91 | "image": "./resources/walkthrough/OpenFolder.png", 92 | "altText": "Open a TSP folder" 93 | }, 94 | "completionEvents": [ 95 | "onCommand:vscode.openFolder" 96 | ] 97 | }, 98 | { 99 | "id": "connect-instrument", 100 | "title": "Connect to an Instrument", 101 | "description": "Connect to an instrument by opening the TSP view container and right-clicking on one of your discovered instruments.\nIf your instrument was not found due to network configuration but you know the IP address or the VISA connection string (if you have a VISA driver installed), you can use the ``+`` button to connect to it.", 102 | "media": { 103 | "image": "./resources/walkthrough/ConnectToInstrument.png", 104 | "altText": "Open Instrument Explorer and Connect" 105 | }, 106 | "completionEvents": [ 107 | "onView:InstrumentsExplorer", 108 | "onCommand:tsp.openTerminalIP" 109 | ] 110 | }, 111 | { 112 | "id": "run-script", 113 | "title": "Run TSP Script", 114 | "description": "While connected to an instrument with a TSP file open in an editor, either click on the play button in the upper-right of the editor or right-click in the editor and select \"Send Script to Terminal\".", 115 | "media": { 116 | "image": "./resources/walkthrough/RunTspScript.png", 117 | "altText": "Run a TSP script" 118 | }, 119 | "completionEvents": [ 120 | "onCommand:tsp.sendFile" 121 | ] 122 | }, 123 | { 124 | "id": "using-terminal", 125 | "title": "Using the Terminal", 126 | "description": "While connected to an instrument, you can use ``.help`` to view more terminal commands and ``.exit`` to close the connection.", 127 | "media": { 128 | "image": "./resources/walkthrough/TerminalUsage.png", 129 | "altText": "Open a TSP folder" 130 | }, 131 | "completionEvents": [ 132 | "onStepSelected" 133 | ] 134 | }, 135 | { 136 | "id": "example-repo", 137 | "title": "TSP Examples", 138 | "description": "To find TSP examples to get you started, you can visit the [Keithley GitHub Repository](https://github.com/tektronix/keithley/).\n1. You can select the ``Application_Specific`` or ``Instrument_Examples`` folder.\n 2. Navigate the folders to find example scripts.\n3. You can click on a script to view the code within Github. Select the download icon to download the script to your computer.\n4. After the download completes, copy the TSP file into your project folder.", 139 | "media": { 140 | "image": "./resources/walkthrough/TSPExampleRepo.png", 141 | "altText": "The Keithley TSP Example Repo" 142 | }, 143 | "completionEvents": [ 144 | "onLink:https://github.com/tektronix/keithley/" 145 | ] 146 | } 147 | ] 148 | } 149 | ], 150 | "commands": [ 151 | { 152 | "command": "tsp.openTerminal", 153 | "title": "Connect", 154 | "category": "TSP" 155 | }, 156 | { 157 | "command": "tsp.sendFile", 158 | "title": "Send Script to Terminal", 159 | "category": "TSP", 160 | "icon": "$(run)" 161 | }, 162 | { 163 | "command": "tsp.sendFileToAllInstr", 164 | "title": "Send Script to All Terminals", 165 | "enablement": "isKicTerminalActive", 166 | "category": "TSP" 167 | }, 168 | { 169 | "command": "tsp.openTerminalIP", 170 | "title": "Connect", 171 | "category": "TSP", 172 | "icon": "$(run)" 173 | }, 174 | { 175 | "command": "InstrumentsExplorer.refresh", 176 | "title": "Refresh", 177 | "icon": { 178 | "light": "resources/light/refresh.svg", 179 | "dark": "resources/dark/refresh.svg" 180 | } 181 | }, 182 | { 183 | "command": "InstrumentsExplorer.connect", 184 | "title": "New Connection", 185 | "icon": { 186 | "light": "resources/light/connect.svg", 187 | "dark": "resources/dark/connect.svg" 188 | } 189 | }, 190 | { 191 | "command": "InstrumentsExplorer.showTerm", 192 | "title": "Show Terminal", 193 | "icon": "$(terminal)" 194 | }, 195 | { 196 | "command": "InstrumentsExplorer.rename", 197 | "title": "Rename", 198 | "icon": "$(edit)" 199 | }, 200 | { 201 | "command": "InstrumentsExplorer.reset", 202 | "title": "Reset", 203 | "icon": "$(discard)" 204 | }, 205 | { 206 | "command": "InstrumentsExplorer.upgradeFirmware", 207 | "title": "Upgrade Firmware", 208 | "category": "TSP", 209 | "icon": "$(chip)" 210 | }, 211 | { 212 | "command": "InstrumentsExplorer.save", 213 | "title": "Save", 214 | "category": "TSP", 215 | "icon": "$(save)" 216 | }, 217 | { 218 | "command": "InstrumentsExplorer.remove", 219 | "title": "Remove", 220 | "category": "TSP", 221 | "icon": "$(trash)" 222 | }, 223 | { 224 | "command": "systemConfigurations.addSystem", 225 | "title": "Add new system", 226 | "icon": { 227 | "light": "resources/light/connect.svg", 228 | "dark": "resources/dark/connect.svg" 229 | } 230 | }, 231 | { 232 | "command": "systemConfigurations.fetchConnectionNodes", 233 | "title": "Fetch connected instrument and its TSP-Link nodes", 234 | "icon": { 235 | "light": "resources/light/dependency.svg", 236 | "dark": "resources/dark/dependency.svg" 237 | } 238 | } 239 | ], 240 | "configuration": { 241 | "type": "object", 242 | "title": "TSP Toolkit", 243 | "properties": { 244 | "tsp.savedInstruments": { 245 | "type": "array", 246 | "default": [], 247 | "description": "A list of saved instrument details", 248 | "scope": "application" 249 | }, 250 | "tsp.dumpQueueOnConnect": { 251 | "type": "boolean", 252 | "default": false, 253 | "description": "If `true`, collect the contents of the instrument output queue and print it to the terminal when connecting.", 254 | "scope": "application" 255 | }, 256 | "tsp.tspLinkSystemConfigurations": { 257 | "type": "array", 258 | "default": [], 259 | "scope": "window", 260 | "description": "A list of dynamically switchable sets of instruments" 261 | } 262 | } 263 | }, 264 | "menus": { 265 | "editor/context": [ 266 | { 267 | "when": "activeEditor && resourceExtname == .tsp", 268 | "command": "tsp.sendFile", 269 | "group": "navigation" 270 | }, 271 | { 272 | "when": "activeEditor && resourceExtname == .tsp", 273 | "command": "tsp.sendFileToAllInstr", 274 | "group": "navigation" 275 | } 276 | ], 277 | "explorer/context": [ 278 | { 279 | "when": "activeEditor && resourceExtname == .tsp", 280 | "command": "tsp.sendFile", 281 | "group": "navigation" 282 | }, 283 | { 284 | "when": "activeEditor && resourceExtname == .tsp", 285 | "command": "tsp.sendFileToAllInstr", 286 | "group": "navigation" 287 | } 288 | ], 289 | "editor/title/context": [ 290 | { 291 | "when": "activeEditor && resourceExtname == .tsp", 292 | "command": "tsp.sendFile", 293 | "group": "navigation" 294 | }, 295 | { 296 | "when": "activeEditor && resourceExtname == .tsp", 297 | "command": "tsp.sendFileToAllInstr", 298 | "group": "navigation" 299 | } 300 | ], 301 | "view/title": [ 302 | { 303 | "command": "InstrumentsExplorer.refresh", 304 | "when": "view == InstrumentsExplorer", 305 | "group": "navigation" 306 | }, 307 | { 308 | "command": "InstrumentsExplorer.connect", 309 | "when": "view == InstrumentsExplorer", 310 | "group": "navigation" 311 | }, 312 | { 313 | "command": "systemConfigurations.addSystem", 314 | "when": "view == systemConfigurations", 315 | "group": "navigation" 316 | }, 317 | { 318 | "command": "systemConfigurations.fetchConnectionNodes", 319 | "when": "view == systemConfigurations", 320 | "group": "navigation" 321 | } 322 | ], 323 | "commandPalette": [ 324 | { 325 | "command": "tsp.openTerminalIP", 326 | "when": "false" 327 | }, 328 | { 329 | "command": "InstrumentsExplorer.upgradeFirmware", 330 | "when": "false" 331 | }, 332 | { 333 | "command": "InstrumentsExplorer.save", 334 | "when": "false" 335 | }, 336 | { 337 | "command": "InstrumentsExplorer.remove", 338 | "when": "false" 339 | }, 340 | { 341 | "command": "tsp.sendFile", 342 | "when": "false" 343 | }, 344 | { 345 | "command": "systemConfigurations.addSystem", 346 | "when": "false" 347 | }, 348 | { 349 | "command": "systemConfigurations.fetchConnectionNodes", 350 | "when": "false" 351 | } 352 | ], 353 | "view/item/context": [ 354 | { 355 | "command": "InstrumentsExplorer.rename", 356 | "when": "view == InstrumentsExplorer && viewItem =~ /Instr.*Saved.*/ && !(viewItem =~ /.*Connected.*/)", 357 | "group": "inline" 358 | }, 359 | { 360 | "command": "InstrumentsExplorer.reset", 361 | "when": "view == InstrumentsExplorer && viewItem =~ /CONN.*Enabled.*/ && (viewItem =~ /CONN.*Connected.*/ || viewItem =~ /CONN.*Active.*/ )", 362 | "group": "inline" 363 | }, 364 | { 365 | "command": "InstrumentsExplorer.showTerm", 366 | "when": "view == InstrumentsExplorer && viewItem =~ /CONN.*Connected.*/", 367 | "group": "inline" 368 | }, 369 | { 370 | "command": "tsp.openTerminalIP", 371 | "when": "view == InstrumentsExplorer && viewItem =~ /CONN.*Enabled.*/ && viewItem =~ /CONN.*Active.*/", 372 | "group": "inline" 373 | }, 374 | { 375 | "command": "InstrumentsExplorer.upgradeFirmware", 376 | "when": "view == InstrumentsExplorer && viewItem =~ /Instr.*Connected.*/", 377 | "group": "inline" 378 | }, 379 | { 380 | "command": "InstrumentsExplorer.save", 381 | "when": "view == InstrumentsExplorer && viewItem =~ /Instr.*/ && !(viewItem =~ /.*Saved.*/)", 382 | "group": "inline" 383 | }, 384 | { 385 | "command": "InstrumentsExplorer.remove", 386 | "when": "view == InstrumentsExplorer && viewItem =~ /Instr.*Saved.*/ && !(viewItem =~ /.*Connected.*/)", 387 | "group": "inline" 388 | } 389 | ], 390 | "editor/title/run": [ 391 | { 392 | "command": "tsp.sendFile", 393 | "when": "resourceExtname == .tsp", 394 | "group": "navigation" 395 | } 396 | ] 397 | }, 398 | "viewsContainers": { 399 | "activitybar": [ 400 | { 401 | "id": "tsp-toolkit-container", 402 | "icon": "resources/tsp-toolkit-view-container-icon.svg", 403 | "title": "TSP Toolkit" 404 | } 405 | ] 406 | }, 407 | "views": { 408 | "tsp-toolkit-container": [ 409 | { 410 | "id": "InstrumentsExplorer", 411 | "name": "Instruments" 412 | 413 | }, 414 | { 415 | "id": "systemConfigurations", 416 | "name": "System Configurations", 417 | "type": "webview" 418 | } 419 | ] 420 | }, 421 | "submenus": [ 422 | { 423 | "label": "Module", 424 | "id": "modulesubmenu" 425 | } 426 | ] 427 | }, 428 | "scripts": { 429 | "pretest": "npm run compile && npm run lint", 430 | "vscode:prepublish": "npm run compile", 431 | "compile": "tsc -p ./ && npm run copy-static", 432 | "watch": "tsc -watch -p ./", 433 | "lint": "eslint src --ext ts", 434 | "test": "mocha --config .mocharc.yml", 435 | "test-ci": "mocha --config .mocharc.yml --reporter xunit --reporter-option output=junit.xml", 436 | "coverage": "nyc --nycrc-path='.nycrc.json' npm run test", 437 | "copy-static": "copyfiles -u 1 src/**/*.{js,css} out" 438 | }, 439 | "devDependencies": { 440 | "@istanbuljs/nyc-config-typescript": "1.0.2", 441 | "@types/chai": "4.3.18", 442 | "@types/mocha": "10.0.10", 443 | "@types/node": "22.10.2", 444 | "@types/node-fetch": "3.0.2", 445 | "@types/vscode": "1.92.0", 446 | "@typescript-eslint/eslint-plugin": "8.3.0", 447 | "@typescript-eslint/parser": "8.3.0", 448 | "@vscode/vsce": "3.2.2", 449 | "chai": "^4.4.1", 450 | "copyfiles": "^2.4.1", 451 | "eslint": "8.57.0", 452 | "eslint-config-prettier": "8.2.0", 453 | "eslint-import-resolver-typescript": "3.6.3", 454 | "eslint-plugin-import": "2.29.1", 455 | "eslint-plugin-jsdoc": "50.2.2", 456 | "eslint-plugin-prettier": "5.2.1", 457 | "license-checker": "25.0.1", 458 | "mocha": "^10.8.2", 459 | "nyc": "15.1.0", 460 | "prettier": "3.3.3", 461 | "source-map-support": "0.5.21", 462 | "ts-node": "10.9.2", 463 | "typescript": "5.5.4" 464 | }, 465 | "dependencies": { 466 | "@tektronix/keithley_instrument_libraries": "0.19.0-1", 467 | "@tektronix/web-help-documents": "0.19.1-0", 468 | "@types/cheerio": "0.22.35", 469 | "cheerio": "1.0.0", 470 | "class-transformer": "0.5.1", 471 | "json-rpc-2.0": "1.7.0", 472 | "path-browserify": "1.0.1", 473 | "portfinder": "1.0.32", 474 | "sax-ts": "1.2.13", 475 | "xml-js": "1.6.11", 476 | "@vscode/codicons": "0.0.36" 477 | }, 478 | "optionalDependencies": { 479 | "@tektronix/kic-cli-darwin-arm64": "0.19.8-1", 480 | "@tektronix/kic-cli-linux-x64": "0.19.8-1", 481 | "@tektronix/kic-cli-win32-x64": "0.19.8-1" 482 | }, 483 | "extensionDependencies": [ 484 | "sumneko.lua" 485 | ] 486 | } 487 | -------------------------------------------------------------------------------- /resources/DemoVideoThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/DemoVideoThumbnail.png -------------------------------------------------------------------------------- /resources/TSP_Toolkit_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/TSP_Toolkit_128x128.png -------------------------------------------------------------------------------- /resources/dark/connect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/dark/dependency.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/dark/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/dark/tsp-terminal-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/light/connect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/light/dependency.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/tsp-terminal-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/tsp-toolkit-view-container-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/walkthrough/ConnectToInstrument.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/walkthrough/ConnectToInstrument.png -------------------------------------------------------------------------------- /resources/walkthrough/FetchConfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/walkthrough/FetchConfig.png -------------------------------------------------------------------------------- /resources/walkthrough/InstrumentExplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/walkthrough/InstrumentExplorer.png -------------------------------------------------------------------------------- /resources/walkthrough/OpenFolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/walkthrough/OpenFolder.png -------------------------------------------------------------------------------- /resources/walkthrough/RunTspScript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/walkthrough/RunTspScript.png -------------------------------------------------------------------------------- /resources/walkthrough/TSPExampleRepo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/walkthrough/TSPExampleRepo.png -------------------------------------------------------------------------------- /resources/walkthrough/TSPResources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/walkthrough/TSPResources.png -------------------------------------------------------------------------------- /resources/walkthrough/TerminalUsage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tektronix/tsp-toolkit/9fa03cb795929a98c4a440a1ad89aa5656e5c246/resources/walkthrough/TerminalUsage.png -------------------------------------------------------------------------------- /resources/walkthrough/WindowsDeps.md: -------------------------------------------------------------------------------- 1 | # Dependencies on Microsoft Windows 2 | - Microsoft Visual C++ Redistributable 3 | -------------------------------------------------------------------------------- /resources/walkthrough/connect_instrument.md: -------------------------------------------------------------------------------- 1 | ![Right-click Connect to Instrument](ConnectToInstrument.png) 2 | -------------------------------------------------------------------------------- /scripts/link-dev-ki-comms.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { env, exit } = require("process") 4 | const { execSync } = require("child_process") 5 | const fs = require("fs") 6 | 7 | const LOCAL_DEV_DEPS = ["@trebuchet/ki-comms"] 8 | 9 | function is_project_linked(project_name) { 10 | let link_data = {} 11 | try { 12 | const npm_output = execSync( 13 | `npm list --global --json --link ${project_name}` 14 | ) 15 | link_data = JSON.parse(npm_output.toString()) 16 | } catch (_) { 17 | // Output from npm list command also returned errors, so it probably didn't exist. 18 | return false 19 | } 20 | 21 | const recursive_check = (obj) => { 22 | for (const [key, value] of Object.entries(obj)) { 23 | if (typeof value === "object") { 24 | if (key === project_name) { 25 | return true 26 | } 27 | return recursive_check(value) 28 | } 29 | } 30 | return false 31 | } 32 | return recursive_check(link_data) 33 | } 34 | 35 | if (!env.hasOwnProperty("CI")) { 36 | for (const dep of LOCAL_DEV_DEPS) { 37 | //If the version is a file, then we know the link was already performed. 38 | if (!is_project_linked(dep)) { 39 | //prompt user for ki-comms location 40 | const readline = require("readline") 41 | const path = require("path") 42 | 43 | const rl = readline.createInterface({ 44 | input: process.stdin, 45 | output: process.stdout, 46 | }) 47 | 48 | rl.question( 49 | `What is the absolute or relative path to your ${dep} folder\n(leave blank to install from registry)\n > `, 50 | (comms_path) => { 51 | rl.close() 52 | if (comms_path.trim().length == 0) { 53 | return 54 | } 55 | const norm_path = path.normalize(comms_path) 56 | try { 57 | execSync( 58 | `npm link --force --package-lock false ${norm_path}` 59 | ) 60 | } catch (e) { 61 | console.error( 62 | `Unable to link path ${comms_path} (resolved to ${norm_path}).\n${e}` 63 | ) 64 | process.exit(1) 65 | } 66 | console.log(`Linked directory ${norm_path} successfully.`) 67 | } 68 | ) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ConifgWebView.ts: -------------------------------------------------------------------------------- 1 | import { join } from "node:path" 2 | import * as vscode from "vscode" 3 | import { Uri, Webview, WebviewView, WebviewViewProvider } from "vscode" 4 | import { 5 | NO_OPEN_WORKSPACE_MESSAGE, 6 | Node, 7 | SUPPORTED_MODELS_DETAILS, 8 | SystemInfo, 9 | } from "./resourceManager" 10 | import { updateLuaLibraryConfigurations } from "./workspaceManager" 11 | 12 | export class ConfigWebView implements WebviewViewProvider { 13 | public static readonly viewType = "systemConfigurations" 14 | private _webviewView!: vscode.WebviewView 15 | constructor(private readonly _extensionUri: Uri) { 16 | // Register a callback for configuration changes 17 | vscode.workspace.onDidChangeConfiguration(async (event) => { 18 | if (event.affectsConfiguration("tsp.tspLinkSystemConfigurations")) { 19 | await this.getSystemName() 20 | await updateLuaLibraryConfigurations() 21 | } 22 | }) 23 | } 24 | resolveWebviewView( 25 | webviewView: vscode.WebviewView, 26 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 27 | _context: vscode.WebviewViewResolveContext, 28 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 29 | _token: vscode.CancellationToken, 30 | ) { 31 | this._webviewView = webviewView 32 | // Allow scripts in the webview 33 | webviewView.webview.options = { 34 | // Enable JavaScript in the webview 35 | enableScripts: true, 36 | // Restrict the webview to only load resources from the `out` directory 37 | localResourceRoots: [Uri.joinPath(this._extensionUri)], 38 | } 39 | 40 | // Set the HTML content that will fill the webview view 41 | webviewView.webview.html = this._getWebviewContent(webviewView.webview) 42 | 43 | // Sets up an event listener to listen for messages passed from the webview view context 44 | // and executes code based on the message that is recieved 45 | this._setWebviewMessageListener(webviewView) 46 | } 47 | private _getWebviewContent(webview: Webview) { 48 | const webviewScriptUri = this.getUri(webview, this._extensionUri, [ 49 | "out", 50 | "systemConfig.js", 51 | ]) 52 | const stylesUri = this.getUri(webview, this._extensionUri, [ 53 | "out", 54 | "styles.css", 55 | ]) 56 | const codiconsUri = this.getUri(webview, this._extensionUri, [ 57 | "node_modules", 58 | "@vscode/codicons", 59 | "dist", 60 | "codicon.css", 61 | ]) 62 | const nonce = this.getNonce() 63 | return /*html*/ ` 64 | 65 | 66 | 67 | 68 | 69 | 71 | 72 | 73 | System Configuration 74 | 75 | 76 | 77 |
78 | 79 | 80 | 81 | 82 | ` 83 | } 84 | 85 | public async deprecateOldSystemConfigurations() { 86 | if (vscode.workspace.workspaceFolders) { 87 | const configFilePath = join( 88 | vscode.workspace.workspaceFolders[0].uri.fsPath, 89 | ".vscode/tspConfig/config.tsp.json", 90 | ) 91 | if ( 92 | await vscode.workspace.fs 93 | .stat(vscode.Uri.file(configFilePath)) 94 | .then( 95 | () => true, 96 | () => false, 97 | ) 98 | ) { 99 | // if configurations is present 100 | const systemInfo = 101 | await this.getOldConfiguration(configFilePath) 102 | if (systemInfo) { 103 | const option = await vscode.window.showWarningMessage( 104 | "An old system configuration was found. Do you want to export it to the new structure and delete the old configuration file and folder?", 105 | "Yes", 106 | "No", 107 | ) 108 | 109 | if (option === "Yes") { 110 | const nodes: Node[] = [] 111 | for (const key in systemInfo) { 112 | if (key.includes("node")) { 113 | nodes.push({ 114 | nodeId: key, 115 | mainframe: systemInfo[key], 116 | }) 117 | } 118 | } 119 | const name = await this.getNewSystemName() 120 | if (!name) { 121 | vscode.window.showWarningMessage( 122 | "Unable to get the new system name", 123 | ) 124 | return 125 | } 126 | 127 | const newItem: SystemInfo = { 128 | name: name, 129 | localNode: systemInfo.self, 130 | isActive: false, 131 | nodes: nodes, 132 | } 133 | 134 | const originalSystemInfo: SystemInfo[] = 135 | vscode.workspace 136 | .getConfiguration("tsp") 137 | .get("tspLinkSystemConfigurations") ?? [] 138 | 139 | const updatedSystemInfos = [ 140 | ...originalSystemInfo, 141 | newItem, 142 | ] 143 | 144 | await vscode.workspace 145 | .getConfiguration("tsp") 146 | .update( 147 | "tspLinkSystemConfigurations", 148 | updatedSystemInfos, 149 | false, 150 | ) 151 | await this.activateSystem(name) 152 | vscode.commands.executeCommand( 153 | "systemConfigurations.focus", 154 | ) 155 | const configFolder = join( 156 | vscode.workspace.workspaceFolders[0].uri.fsPath, 157 | ".vscode/tspConfig/", 158 | ) 159 | await this.deleteOldSystemConfigurations(configFolder) 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | private async getOldConfiguration( 167 | configFilePath: string, 168 | ): Promise<{ [key: string]: string } | null> { 169 | try { 170 | const fileUri = vscode.Uri.file(configFilePath) 171 | const fileData = await vscode.workspace.fs.readFile(fileUri) 172 | const jsonStr = Buffer.from(fileData).toString("utf8") 173 | interface OldConfig { 174 | nodes?: { [key: string]: { model?: string } } 175 | self?: string 176 | [key: string]: unknown 177 | } 178 | 179 | const config = JSON.parse(jsonStr) as OldConfig 180 | 181 | const result: { [key: string]: string } = {} 182 | if ( 183 | config.self && 184 | config.self.trim() !== "" && 185 | typeof config.nodes === "object" 186 | ) { 187 | result["self"] = config.self 188 | for (const nodeKey of Object.keys(config.nodes)) { 189 | const node = config.nodes[nodeKey] 190 | if (node && typeof node.model === "string") { 191 | const nodeNumber = nodeKey.match(/\d+/)?.[0] ?? nodeKey 192 | const nodeId = `node[${nodeNumber}]` 193 | if ( 194 | Object.prototype.hasOwnProperty.call(result, nodeId) 195 | ) { 196 | return null 197 | } 198 | result[nodeId] = node.model 199 | } 200 | } 201 | //check if model in configuration are in supported model list 202 | const supportedModels = Object.keys(SUPPORTED_MODELS_DETAILS) 203 | for (const key in result) { 204 | const model = result[key] 205 | if (!supportedModels.includes(model)) { 206 | return null 207 | } 208 | } 209 | return result 210 | } 211 | return null 212 | } catch { 213 | return null 214 | } 215 | } 216 | 217 | private async deleteOldSystemConfigurations(folderPath: string) { 218 | // delete this folder 219 | try { 220 | await vscode.workspace.fs.delete(vscode.Uri.file(folderPath), { 221 | recursive: true, 222 | useTrash: false, 223 | }) 224 | } catch (error) { 225 | vscode.window.showWarningMessage( 226 | "Failed to delete old configuration folder: " + 227 | (error instanceof Error ? error.message : String(error)), 228 | ) 229 | } 230 | } 231 | 232 | public addSystem() { 233 | if (!vscode.workspace.workspaceFolders) { 234 | vscode.window.showInformationMessage(`${NO_OPEN_WORKSPACE_MESSAGE}`) 235 | return 236 | } 237 | const savedSystems: SystemInfo[] = 238 | vscode.workspace 239 | .getConfiguration("tsp") 240 | .get("tspLinkSystemConfigurations") ?? [] 241 | this._webviewView.webview.postMessage({ 242 | command: "supportedModels", 243 | payload: JSON.stringify({ 244 | systemInfo: savedSystems, 245 | supportedModels: SUPPORTED_MODELS_DETAILS, 246 | }), 247 | }) 248 | } 249 | 250 | private _setWebviewMessageListener(webviewView: WebviewView) { 251 | webviewView.webview.onDidReceiveMessage(async (message) => { 252 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 253 | switch (message.command) { 254 | case "openFolder": { 255 | const uri = await vscode.window.showOpenDialog({ 256 | canSelectFolders: true, 257 | canSelectFiles: false, 258 | canSelectMany: true, 259 | openLabel: "Open Folder", 260 | }) 261 | if (uri && uri.length > 0) { 262 | vscode.commands.executeCommand( 263 | "vscode.openFolder", 264 | uri[0], 265 | ) 266 | } 267 | break 268 | } 269 | case "getInitialSystems": { 270 | if (!vscode.workspace.workspaceFolders) { 271 | webviewView.webview.postMessage({ 272 | command: "openWorkspaceNotFound", 273 | }) 274 | break 275 | } 276 | 277 | this.reloadUi(webviewView) 278 | break 279 | } 280 | case "add": { 281 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 282 | const newSystemDetails = message.data as SystemInfo 283 | const originalSystemInfo: SystemInfo[] = 284 | vscode.workspace 285 | .getConfiguration("tsp") 286 | .get("tspLinkSystemConfigurations") ?? [] 287 | 288 | const newItem: SystemInfo = { 289 | name: newSystemDetails.name, 290 | localNode: newSystemDetails.localNode, 291 | isActive: newSystemDetails.isActive, 292 | slots: newSystemDetails.slots, 293 | nodes: newSystemDetails.nodes, 294 | } 295 | 296 | const updatedSystemInfos = [...originalSystemInfo, newItem] 297 | 298 | await vscode.workspace 299 | .getConfiguration("tsp") 300 | .update( 301 | "tspLinkSystemConfigurations", 302 | updatedSystemInfos, 303 | false, 304 | ) 305 | const systemInfo: SystemInfo[] = 306 | vscode.workspace 307 | .getConfiguration("tsp") 308 | .get("tspLinkSystemConfigurations") ?? [] 309 | 310 | webviewView.webview.postMessage({ 311 | command: "systems", 312 | payload: JSON.stringify({ 313 | systemInfo: systemInfo, 314 | supportedModels: SUPPORTED_MODELS_DETAILS, 315 | selectedSystem: newSystemDetails.name, 316 | }), 317 | }) 318 | await this.activateSystem(newSystemDetails.name) 319 | break 320 | } 321 | case "delete": { 322 | const originalSystemInfo: SystemInfo[] = 323 | vscode.workspace 324 | .getConfiguration("tsp") 325 | .get("tspLinkSystemConfigurations") ?? [] 326 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access 327 | const item = message.data 328 | const updatedSystemInfos = originalSystemInfo.filter( 329 | (sys) => sys.name !== item, 330 | ) 331 | await vscode.workspace 332 | .getConfiguration("tsp") 333 | .update( 334 | "tspLinkSystemConfigurations", 335 | updatedSystemInfos, 336 | false, 337 | ) 338 | const systemInfo: SystemInfo[] = 339 | vscode.workspace 340 | .getConfiguration("tsp") 341 | .get("tspLinkSystemConfigurations") ?? [] 342 | 343 | webviewView.webview.postMessage({ 344 | command: "systems", 345 | payload: JSON.stringify({ 346 | systemInfo: systemInfo, 347 | supportedModels: SUPPORTED_MODELS_DETAILS, 348 | selectedSystem: systemInfo[0] 349 | ? systemInfo[0].name 350 | : "", 351 | }), 352 | }) 353 | await this.activateSystem(systemInfo[0].name) 354 | break 355 | } 356 | case "activate": { 357 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 358 | const system_name = message.data as string 359 | await this.activateSystem(system_name) 360 | break 361 | } 362 | case "update": { 363 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 364 | const newSystemDetails = message.data as SystemInfo 365 | const originalSystemInfo: SystemInfo[] = 366 | vscode.workspace 367 | .getConfiguration("tsp") 368 | .get("tspLinkSystemConfigurations") ?? [] 369 | 370 | const updatedSystemInfos = originalSystemInfo.map( 371 | (system) => { 372 | if (system.isActive) { 373 | return { 374 | ...system, 375 | name: newSystemDetails.name, 376 | localNode: newSystemDetails.localNode, 377 | slots: newSystemDetails.slots, 378 | nodes: newSystemDetails.nodes, 379 | } 380 | } 381 | return system 382 | }, 383 | ) 384 | await vscode.workspace 385 | .getConfiguration("tsp") 386 | .update( 387 | "tspLinkSystemConfigurations", 388 | updatedSystemInfos, 389 | false, 390 | ) 391 | 392 | const systemInfo: SystemInfo[] = 393 | vscode.workspace 394 | .getConfiguration("tsp") 395 | .get("tspLinkSystemConfigurations") ?? [] 396 | 397 | webviewView.webview.postMessage({ 398 | command: "systemUpdated", 399 | payload: JSON.stringify({ 400 | systemInfo: systemInfo, 401 | }), 402 | }) 403 | 404 | break 405 | } 406 | } 407 | }) 408 | } 409 | 410 | private reloadUi(webviewView: vscode.WebviewView) { 411 | const savedSystems: SystemInfo[] = 412 | vscode.workspace 413 | .getConfiguration("tsp") 414 | .get("tspLinkSystemConfigurations") ?? [] 415 | 416 | const activeSystem = savedSystems.find((system) => system.isActive) 417 | webviewView.webview.postMessage({ 418 | command: "systems", 419 | payload: JSON.stringify({ 420 | systemInfo: savedSystems, 421 | supportedModels: SUPPORTED_MODELS_DETAILS, 422 | selectedSystem: activeSystem ? activeSystem.name : null, 423 | }), 424 | }) 425 | } 426 | 427 | private async activateSystem(systemName: string) { 428 | const originalSystemInfo: SystemInfo[] = 429 | vscode.workspace 430 | .getConfiguration("tsp") 431 | .get("tspLinkSystemConfigurations") ?? [] 432 | 433 | const item = systemName 434 | const updatedSystemInfos = originalSystemInfo.map((sys) => { 435 | if (sys.name === item) { 436 | return { ...sys, isActive: true } 437 | } else { 438 | return { ...sys, isActive: false } 439 | } 440 | }) 441 | await vscode.workspace 442 | .getConfiguration("tsp") 443 | .update("tspLinkSystemConfigurations", updatedSystemInfos, false) 444 | vscode.window.showInformationMessage( 445 | `The system configuration "${item}" has been activated.`, 446 | ) 447 | } 448 | 449 | private render_new_system(system_name: string) { 450 | const savedSystems: SystemInfo[] = 451 | vscode.workspace 452 | .getConfiguration("tsp") 453 | .get("tspLinkSystemConfigurations") ?? [] 454 | 455 | this._webviewView.webview.postMessage({ 456 | command: "systems", 457 | payload: JSON.stringify({ 458 | systemInfo: savedSystems, 459 | supportedModels: SUPPORTED_MODELS_DETAILS, 460 | selectedSystem: system_name, 461 | }), 462 | }) 463 | } 464 | private async getSystemName() { 465 | const existingSystems: SystemInfo[] = 466 | vscode.workspace 467 | .getConfiguration("tsp") 468 | .get("tspLinkSystemConfigurations") ?? [] 469 | 470 | const systemWithEmptyName = existingSystems.find( 471 | (system) => !system.name, 472 | ) 473 | if (systemWithEmptyName) { 474 | const name = await this.getNewSystemName() 475 | if (name) { 476 | systemWithEmptyName.name = name 477 | await vscode.workspace 478 | .getConfiguration("tsp") 479 | .update( 480 | "tspLinkSystemConfigurations", 481 | existingSystems, 482 | false, 483 | ) 484 | this.render_new_system(systemWithEmptyName.name) 485 | await this.activateSystem(systemWithEmptyName.name) 486 | } else { 487 | const updatedSystems = existingSystems.filter( 488 | (system) => system !== systemWithEmptyName, 489 | ) 490 | await vscode.workspace 491 | .getConfiguration("tsp") 492 | .update( 493 | "tspLinkSystemConfigurations", 494 | updatedSystems, 495 | false, 496 | ) 497 | } 498 | } 499 | } 500 | 501 | private async getNewSystemName() { 502 | const existingSystems: SystemInfo[] = 503 | vscode.workspace 504 | .getConfiguration("tsp") 505 | .get("tspLinkSystemConfigurations") ?? [] 506 | 507 | const options: vscode.InputBoxOptions = { 508 | prompt: "Enter new system name", 509 | validateInput: (value) => { 510 | const trimmedValue = value.trim() 511 | if (!trimmedValue) { 512 | return "System name cannot be empty" 513 | } 514 | const isDuplicate = existingSystems.some( 515 | (system: { name: string }) => system.name === trimmedValue, 516 | ) 517 | if (isDuplicate) { 518 | return "Duplicate system name not allowed" 519 | } 520 | return null 521 | }, 522 | } 523 | const name = await vscode.window.showInputBox(options) 524 | return name 525 | } 526 | 527 | private getNonce() { 528 | let text = "" 529 | const possible = 530 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 531 | for (let i = 0; i < 32; i++) { 532 | text += possible.charAt(Math.floor(Math.random() * possible.length)) 533 | } 534 | return text 535 | } 536 | 537 | private getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { 538 | return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)) 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | 3 | import { EXECUTABLE } from "./kic-cli" 4 | import { Instrument } from "./instrument" 5 | import { HelpDocumentWebView } from "./helpDocumentWebView" 6 | import { 7 | ConnectionDetails, 8 | ConnectionHelper, 9 | NO_OPEN_WORKSPACE_MESSAGE, 10 | } from "./resourceManager" 11 | import { configure_initial_workspace_configurations } from "./workspaceManager" 12 | import { Log, SourceLocation } from "./logging" 13 | import { InstrumentsExplorer } from "./instrumentExplorer" 14 | import { Connection } from "./connection" 15 | import { InstrumentProvider } from "./instrumentProvider" 16 | import { ConfigWebView } from "./ConifgWebView" 17 | 18 | let _instrExplorer: InstrumentsExplorer 19 | 20 | /** 21 | * Function will create terminal with given connection details 22 | * @param connection_string connection string example 'tsPop@127.0.0.1' 23 | * @param model_serial model serial number 24 | * @param command_text command text that needs to send to terminal 25 | * @returns None 26 | */ 27 | export async function createTerminal( 28 | connection: Connection | string, 29 | ): Promise { 30 | const LOGLOC: SourceLocation = { 31 | file: "extension.ts", 32 | func: "createTerminal()", 33 | } 34 | let name = "" 35 | //'example@5e6:2461@2' OR 'example@127.0.0.1' 36 | // Always create a Connection. 37 | if (typeof connection === "string") { 38 | const connection_details = 39 | ConnectionHelper.parseConnectionString(connection) 40 | 41 | if (!connection_details) { 42 | return Promise.reject( 43 | new Error("Unable to parse connection string"), 44 | ) 45 | } 46 | Log.debug( 47 | `Connection type was determined to be ${connection_details.type.toUpperCase()}`, 48 | LOGLOC, 49 | ) 50 | const existing = 51 | InstrumentProvider.instance.getConnection(connection_details) 52 | 53 | if (existing) { 54 | connection = existing 55 | } else { 56 | connection = new Connection( 57 | connection_details.type, 58 | connection_details.addr, 59 | ) 60 | } 61 | name = connection_details.name 62 | } 63 | 64 | const conn: Connection = connection 65 | 66 | await conn.connect(name) 67 | 68 | //LAN 69 | return Promise.resolve(conn) 70 | } 71 | 72 | function registerCommands( 73 | context: vscode.ExtensionContext, 74 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 75 | commands: { name: string; cb: (...args: any[]) => any; thisArgs?: any }[], 76 | ) { 77 | for (const c of commands) { 78 | registerCommand(context, c.name, c.cb, c.thisArgs) 79 | } 80 | } 81 | 82 | function registerCommand( 83 | context: vscode.ExtensionContext, 84 | name: string, 85 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 86 | cb: (...args: any[]) => any, 87 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 88 | thisArgs?: any, 89 | ) { 90 | const LOGLOC: SourceLocation = { 91 | file: "extension.ts", 92 | func: "registerCommand()", 93 | } 94 | Log.debug(`Registering '${name}' command`, LOGLOC) 95 | 96 | context.subscriptions.push( 97 | vscode.commands.registerCommand(name, cb, thisArgs), 98 | ) 99 | } 100 | 101 | // Called when the extension is activated. 102 | export function activate(context: vscode.ExtensionContext) { 103 | const LOGLOC: SourceLocation = { file: "extension.ts", func: "activate()" } 104 | Log.info("TSP Toolkit activating", LOGLOC) 105 | 106 | Log.debug("Updating extension settings", LOGLOC) 107 | updateExtensionSettings() 108 | 109 | Log.debug("Creating new InstrumentExplorer", LOGLOC) 110 | _instrExplorer = new InstrumentsExplorer(context) 111 | 112 | // The command has been defined in the package.json file 113 | // Now provide the implementation of the command with registerCommand 114 | // The commandId parameter must match the command field in package.json 115 | registerCommands(context, [ 116 | { name: "tsp.openTerminal", cb: pickConnection }, 117 | { name: "tsp.openTerminalIP", cb: connectCmd }, 118 | { 119 | name: "InstrumentsExplorer.connect", 120 | cb: async () => { 121 | await pickConnection("New Connection") 122 | }, 123 | }, 124 | { 125 | name: "InstrumentsExplorer.showTerm", 126 | cb: (conn: Connection) => { 127 | conn.showTerminal() 128 | }, 129 | }, 130 | { 131 | name: "InstrumentsExplorer.rename", 132 | cb: async (e: Instrument) => { 133 | await startRename(e) 134 | }, 135 | }, 136 | { 137 | name: "InstrumentsExplorer.reset", 138 | cb: async (e: Connection) => { 139 | await startReset(e) 140 | vscode.window.showInformationMessage("Reset complete") 141 | }, 142 | }, 143 | { 144 | name: "InstrumentsExplorer.upgradeFirmware", 145 | cb: async (e: Instrument) => { 146 | await e.upgrade() 147 | }, 148 | }, 149 | { 150 | name: "tsp.sendFileToAllInstr", 151 | cb: async (e: vscode.Uri) => { 152 | await InstrumentProvider.instance.sendToAllActiveTerminals( 153 | e.fsPath, 154 | ) 155 | }, 156 | }, 157 | { 158 | name: "tsp.sendFile", 159 | cb: async (e: vscode.Uri) => { 160 | const term = vscode.window.activeTerminal 161 | if ( 162 | (term?.creationOptions as vscode.TerminalOptions) 163 | ?.shellPath === EXECUTABLE 164 | ) { 165 | let connection: Connection | undefined = undefined 166 | for (const i of InstrumentProvider.instance.instruments) { 167 | connection = i.connections.find( 168 | (c) => c.terminal?.processId === term?.processId, 169 | ) 170 | if (connection) { 171 | break 172 | } 173 | } 174 | 175 | if (connection) { 176 | await connection.sendScript(e.fsPath) 177 | } 178 | } else { 179 | const conn = await pickConnection() 180 | await conn?.sendScript(e.fsPath) 181 | } 182 | }, 183 | }, 184 | { 185 | name: "systemConfigurations.fetchConnectionNodes", 186 | cb: async () => { 187 | if (!vscode.workspace.workspaceFolders) { 188 | vscode.window.showInformationMessage( 189 | `${NO_OPEN_WORKSPACE_MESSAGE}`, 190 | ) 191 | return 192 | } 193 | 194 | const term = vscode.window.activeTerminal 195 | if ( 196 | (term?.creationOptions as vscode.TerminalOptions) 197 | ?.shellPath === EXECUTABLE 198 | ) { 199 | let connection: Connection | undefined = undefined 200 | for (const i of InstrumentProvider.instance.instruments) { 201 | connection = i.connections.find( 202 | (c) => c.terminal?.processId === term?.processId, 203 | ) 204 | if (connection) { 205 | break 206 | } 207 | } 208 | 209 | if (connection) { 210 | await connection.getNodes( 211 | vscode.workspace.workspaceFolders[0].uri.fsPath, 212 | ) 213 | } 214 | } else { 215 | const conn = await pickConnection() 216 | await conn?.getNodes( 217 | vscode.workspace.workspaceFolders[0].uri.fsPath, 218 | ) 219 | } 220 | }, 221 | }, 222 | ]) 223 | 224 | Log.debug("Setting up HelpDocumentWebView", LOGLOC) 225 | HelpDocumentWebView.createOrShow(context) 226 | // Instantiate a new instance of the ViewProvider class 227 | const systemConfigWebViewprovider = new ConfigWebView(context.extensionUri) 228 | 229 | registerCommand( 230 | context, 231 | "systemConfigurations.addSystem", 232 | systemConfigWebViewprovider.addSystem.bind(systemConfigWebViewprovider), 233 | ) 234 | // Register the provider for a Webview View 235 | const systemConfigViewDisposable = 236 | vscode.window.registerWebviewViewProvider( 237 | ConfigWebView.viewType, 238 | systemConfigWebViewprovider, 239 | ) 240 | void systemConfigWebViewprovider.deprecateOldSystemConfigurations() 241 | 242 | context.subscriptions.push(systemConfigViewDisposable) 243 | 244 | Log.debug( 245 | "Checking to see if workspace folder contains `*.tsp` files", 246 | LOGLOC, 247 | ) 248 | 249 | Log.debug("Update local and global configuration for TSP", LOGLOC) 250 | void configure_initial_workspace_configurations() 251 | Log.debug( 252 | "Subscribing to TSP configuration changes in all workspace folders", 253 | LOGLOC, 254 | ) 255 | 256 | Log.info("TSP Toolkit activation complete", LOGLOC) 257 | 258 | return base_api 259 | } 260 | 261 | // Called when the extension is deactivated. 262 | export function deactivate() { 263 | const LOGLOC = { file: "extensions.ts", func: "deactivate()" } 264 | Log.info("Deactivating TSP Toolkit", LOGLOC) 265 | _instrExplorer.dispose() 266 | Log.info("Deactivation complete", LOGLOC) 267 | } 268 | 269 | //Request the instrument to be reset 270 | function startReset(def: Connection): Promise { 271 | return Promise.resolve(def.reset()) 272 | } 273 | 274 | function updateExtensionSettings() { 275 | const LOGLOC: SourceLocation = { 276 | file: "extension.ts", 277 | func: "updateExtensionSettings()", 278 | } 279 | const settingsList = ["connectionList", "savedInstrumentList"] 280 | settingsList.forEach((setting) => { 281 | if (vscode.workspace.getConfiguration("tsp").get(setting)) { 282 | Log.warn(`Found deprecated setting: \`${setting}\``, LOGLOC) 283 | void vscode.window 284 | .showInformationMessage( 285 | setting + 286 | ' is deprecated. Select "Remove" to remove it from settings.json. If you wish to leave it, select "Ignore"', 287 | ...["Remove", "Ignore"], 288 | ) 289 | .then((selection) => { 290 | if (selection === "Remove") { 291 | Log.info( 292 | `User chose to remove \`${setting}\`. Removing.`, 293 | LOGLOC, 294 | ) 295 | void vscode.workspace 296 | .getConfiguration("tsp") 297 | .update( 298 | setting, 299 | undefined, 300 | vscode.ConfigurationTarget.Global, 301 | ) 302 | .then(() => { 303 | Log.info( 304 | `Setting \`${setting}\` removed from Global settings`, 305 | LOGLOC, 306 | ) 307 | void vscode.window.showInformationMessage( 308 | "Removed deprecated setting: " + setting, 309 | ) 310 | }) 311 | void vscode.workspace 312 | .getConfiguration("tsp") 313 | .update( 314 | setting, 315 | undefined, 316 | vscode.ConfigurationTarget.Workspace, 317 | ) 318 | .then(() => { 319 | Log.info( 320 | `Setting \`${setting}\` removed from workspace`, 321 | LOGLOC, 322 | ) 323 | }) 324 | void vscode.workspace 325 | .getConfiguration("tsp") 326 | .update( 327 | setting, 328 | undefined, 329 | vscode.ConfigurationTarget.WorkspaceFolder, 330 | ) 331 | .then(() => { 332 | Log.info( 333 | `Setting \`${setting}\` removed from workspace folder`, 334 | LOGLOC, 335 | ) 336 | }) 337 | } 338 | }) 339 | } 340 | }) 341 | } 342 | 343 | async function pickConnection( 344 | connection_info?: string, 345 | ): Promise { 346 | const options: vscode.QuickPickItem[] = 347 | InstrumentProvider.instance.getQuickPickOptions() 348 | 349 | if (connection_info !== undefined) { 350 | const options: vscode.InputBoxOptions = { 351 | prompt: "Enter instrument IP address or VISA resource string", 352 | validateInput: ConnectionHelper.instrConnectionStringValidator, 353 | } 354 | const address = await vscode.window.showInputBox(options) 355 | if (address === undefined) { 356 | return 357 | } 358 | await connect(address) 359 | } else { 360 | const quickPick = vscode.window.createQuickPick() 361 | quickPick.items = options 362 | quickPick.title = "Connect to an Instrument" 363 | quickPick.placeholder = 364 | "Enter instrument IP address or VISA resource string" 365 | if (options.length > 0) { 366 | quickPick.placeholder = 367 | "Select connection from existing list or enter instrument IP address or VISA resource string" 368 | } 369 | 370 | quickPick.onDidChangeValue((value) => { 371 | if (!options.some((option) => option.label === value)) { 372 | const new_item = { label: value } 373 | if (new_item.label.length > 0) { 374 | quickPick.items = [new_item, ...options] 375 | } 376 | } 377 | }) 378 | 379 | return new Promise((resolve) => { 380 | quickPick.onDidAccept(async () => { 381 | const selectedItem = quickPick.selectedItems[0] 382 | quickPick.busy = true 383 | try { 384 | // Validate connection string 385 | const validationResult = 386 | ConnectionHelper.instrConnectionStringValidator( 387 | selectedItem.label, 388 | ) 389 | if (validationResult) { 390 | throw new Error(validationResult) 391 | } 392 | 393 | if ( 394 | options.some( 395 | (option) => option.label === selectedItem.label, 396 | ) 397 | ) { 398 | resolve(await createTerminal(selectedItem.label)) 399 | } else { 400 | const Ip = selectedItem.label 401 | if (Ip === undefined) { 402 | return 403 | } 404 | resolve(await connect(Ip)) 405 | } 406 | } catch (error) { 407 | vscode.window.showErrorMessage( 408 | `Error: ${(error as Error).message}`, 409 | ) 410 | } finally { 411 | quickPick.busy = false 412 | quickPick.hide() 413 | } 414 | }) 415 | 416 | quickPick.show() 417 | }) 418 | } 419 | } 420 | 421 | async function connect( 422 | inIp: string, 423 | shouldPrompt?: boolean, 424 | ): Promise { 425 | let Ip: string | undefined = inIp 426 | if (shouldPrompt) { 427 | const options: vscode.InputBoxOptions = { 428 | prompt: "Connect to instrument?", 429 | value: inIp, 430 | } 431 | Ip = await vscode.window.showInputBox(options) 432 | } 433 | if (Ip === undefined) { 434 | return 435 | } 436 | 437 | if (ConnectionHelper.parseConnectionString(Ip)) { 438 | return await createTerminal(Ip) 439 | } else { 440 | void vscode.window.showErrorMessage("Bad connection string") 441 | } 442 | } 443 | 444 | //function startTerminateAllConn() { 445 | // void _terminationMgr.terminateAllConn() 446 | //} 447 | 448 | async function startRename(def: Instrument): Promise { 449 | await _instrExplorer.rename(def) 450 | } 451 | 452 | function connectCmd(def: Connection) { 453 | const LOGLOC: SourceLocation = { 454 | file: "extension.ts", 455 | func: `connectCmd(${String(def)})`, 456 | } 457 | 458 | const connection_str = def.addr 459 | 460 | if (ConnectionHelper.parseConnectionString(connection_str)) { 461 | Log.debug("Connection string is valid. Creating Terminal", LOGLOC) 462 | void createTerminal(def) 463 | } else { 464 | Log.error( 465 | `Connection string "${connection_str}" is invalid. Unable to connect to instrument.`, 466 | LOGLOC, 467 | ) 468 | void vscode.window.showErrorMessage( 469 | `Unable to connect. "${connection_str}" is not a valid connection string.`, 470 | ) 471 | } 472 | } 473 | 474 | const base_api = { 475 | fetchKicTerminals(): vscode.Terminal[] { 476 | const kicTerminals = vscode.window.terminals.filter( 477 | (t) => 478 | (t.creationOptions as vscode.TerminalOptions)?.shellPath === 479 | EXECUTABLE, 480 | ) 481 | return kicTerminals 482 | }, 483 | 484 | async fetchConnDetails( 485 | term_pid: Thenable | undefined, 486 | ): Promise { 487 | const pid = await term_pid 488 | if (pid) { 489 | const connection = 490 | await InstrumentProvider.instance.getTerminalByPid(pid) 491 | if (connection) { 492 | return { 493 | name: connection.terminal?.name ?? "", 494 | addr: connection.addr, 495 | type: connection.type, 496 | } 497 | } 498 | } 499 | return undefined 500 | }, 501 | 502 | async restartConnAfterDbg(details: ConnectionDetails) { 503 | const conn = InstrumentProvider.instance.getConnection(details) 504 | await conn?.connect(details.name) 505 | }, 506 | } 507 | -------------------------------------------------------------------------------- /src/helpDocumentWebView.ts: -------------------------------------------------------------------------------- 1 | import { join } from "path" 2 | import * as fs from "fs" 3 | import * as vscode from "vscode" 4 | import * as cheerio from "cheerio" 5 | import { WEB_HELP_FILE_PATH } from "@tektronix/web-help-documents" 6 | 7 | /** 8 | * Responsibility of this class is to create the webview panel 9 | * and reflect it in vscode 10 | */ 11 | export class HelpDocumentWebView { 12 | private static currentPanel: vscode.WebviewPanel | undefined 13 | static context: vscode.ExtensionContext 14 | public static createOrShow(context: vscode.ExtensionContext) { 15 | this.context = context 16 | this.context.subscriptions.push( 17 | vscode.commands.registerCommand( 18 | "kic.viewHelpDocument", 19 | (helpfile: string) => { 20 | this.getHelpDocumentContent(helpfile) 21 | }, 22 | ), 23 | ) 24 | } 25 | /** 26 | * check for current webview pane and decide needs to create 27 | * or update existing one 28 | * @param helpfile html file name 29 | */ 30 | private static getHelpDocumentContent(helpfile: string) { 31 | if ( 32 | HelpDocumentWebView.currentPanel && 33 | HelpDocumentWebView.currentPanel.title === helpfile 34 | ) { 35 | HelpDocumentWebView.currentPanel.reveal(this.getViewColumn(), true) 36 | } else if ( 37 | HelpDocumentWebView.currentPanel && 38 | HelpDocumentWebView.currentPanel.title !== helpfile 39 | ) { 40 | HelpDocumentWebView.currentPanel.title = helpfile 41 | HelpDocumentWebView.currentPanel.webview.html = 42 | this.generateWebviewContent(helpfile) 43 | HelpDocumentWebView.currentPanel.reveal(this.getViewColumn(), true) 44 | } else { 45 | const options = { 46 | localResourceRoots: [ 47 | vscode.Uri.joinPath(vscode.Uri.file(WEB_HELP_FILE_PATH)), 48 | ], 49 | } 50 | const panel = vscode.window.createWebviewPanel( 51 | "helpFileWebView", 52 | helpfile, 53 | { viewColumn: this.getViewColumn(), preserveFocus: true }, 54 | options, 55 | ) 56 | 57 | panel.onDidDispose( 58 | () => { 59 | HelpDocumentWebView.currentPanel = undefined 60 | }, 61 | null, 62 | this.context.subscriptions, 63 | ) 64 | 65 | HelpDocumentWebView.currentPanel = panel 66 | HelpDocumentWebView.currentPanel.webview.html = 67 | this.generateWebviewContent(helpfile) 68 | } 69 | } 70 | 71 | /** 72 | * check active text editor position and decide where 73 | * to reveal webview 74 | * @returns vscode.ViewColumn number 75 | */ 76 | private static getViewColumn(): vscode.ViewColumn { 77 | if (vscode.window.activeTextEditor) { 78 | if ( 79 | vscode.window.activeTextEditor.viewColumn == 80 | vscode.ViewColumn.One 81 | ) { 82 | return vscode.ViewColumn.Two 83 | } 84 | } 85 | return vscode.ViewColumn.One 86 | } 87 | /** 88 | * Read web help file and update it so that it should 89 | * work with vscode webview 90 | * @param htmlFileName file name of web help file. 91 | * @return html string 92 | */ 93 | private static generateWebviewContent(htmlFileName: string): string { 94 | const filePath = join(WEB_HELP_FILE_PATH, htmlFileName) 95 | if (!fs.existsSync(filePath)) { 96 | return ` 97 | 98 | 99 | 100 | 101 |

Not Found doc/${htmlFileName}

102 | 103 | ` 104 | } 105 | 106 | // below style applies to image tag 107 | const style = ` 108 | ` 125 | 126 | const fileContent = fs.readFileSync(filePath, "utf-8") 127 | 128 | // Load the HTML into cheerio 129 | const document = cheerio.load(fileContent) 130 | document("head").html(style) 131 | 132 | // find the table with previous and next button and remove it 133 | document("table").remove(".relatedtopics") 134 | // Find all elements with a `bgcolor` attribute and remove it 135 | document("[bgcolor]").removeAttr("bgcolor") 136 | 137 | // Disabling Also see section for webhelp file 138 | // Find all anchor elements within paragraphs 139 | document("p") 140 | .find("a") 141 | .each((_index, element) => { 142 | // Get the text content of the anchor tag 143 | const anchorText = document(element).text() 144 | 145 | // Replace the anchor tag with plain text containing the same text 146 | document(element).replaceWith(anchorText) 147 | }) 148 | 149 | let html = document.html() 150 | 151 | document("img, script").each((index, element) => { 152 | const src = document(element).attr("src") 153 | if (src) { 154 | const uri = 155 | HelpDocumentWebView.currentPanel?.webview.asWebviewUri( 156 | vscode.Uri.file( 157 | join( 158 | WEB_HELP_FILE_PATH, 159 | htmlFileName.split("/")[0], 160 | src, 161 | ), 162 | ), 163 | ) 164 | html = html.replace(src, uri?.toString() || src) 165 | } 166 | }) 167 | return html 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/instrument.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import { idn_to_string, IIDNInfo, InstrInfo } from "./resourceManager" 3 | import { 4 | Connection, 5 | ConnectionStatus, 6 | connectionStatusIcon, 7 | contextValueStatus, 8 | } from "./connection" 9 | 10 | export const instr_map = new Map() 11 | instr_map.set("2601", "non_nimitz") 12 | instr_map.set("2602", "non_nimitz") 13 | instr_map.set("2611", "non_nimitz") 14 | instr_map.set("2612", "non_nimitz") 15 | instr_map.set("2635", "non_nimitz") 16 | instr_map.set("2636", "non_nimitz") 17 | instr_map.set("2601A", "non_nimitz") 18 | instr_map.set("2602A", "non_nimitz") 19 | instr_map.set("2611A", "non_nimitz") 20 | instr_map.set("2612A", "non_nimitz") 21 | instr_map.set("2635A", "non_nimitz") 22 | instr_map.set("2636A", "non_nimitz") 23 | instr_map.set("2651A", "non_nimitz") 24 | instr_map.set("2657A", "non_nimitz") 25 | instr_map.set("2601B", "non_nimitz") 26 | instr_map.set("2601B-PULSE", "non_nimitz") 27 | instr_map.set("2602B", "non_nimitz") 28 | instr_map.set("2606B", "non_nimitz") 29 | instr_map.set("2611B", "non_nimitz") 30 | instr_map.set("2612B", "non_nimitz") 31 | instr_map.set("2635B", "non_nimitz") 32 | instr_map.set("2636B", "non_nimitz") 33 | instr_map.set("2604B", "non_nimitz") 34 | instr_map.set("2614B", "non_nimitz") 35 | instr_map.set("2614B", "non_nimitz") 36 | instr_map.set("2634B", "non_nimitz") 37 | instr_map.set("2601B-L", "non_nimitz") 38 | instr_map.set("2602B-L", "non_nimitz") 39 | instr_map.set("2611B-L", "non_nimitz") 40 | instr_map.set("2612B-L", "non_nimitz") 41 | instr_map.set("2635B-L", "non_nimitz") 42 | instr_map.set("2636B-L", "non_nimitz") 43 | instr_map.set("2604B-L", "non_nimitz") 44 | instr_map.set("2614B-L", "non_nimitz") 45 | instr_map.set("2634B-L", "non_nimitz") 46 | instr_map.set("3706-SNFP", "non_nimitz") 47 | instr_map.set("3706-S", "non_nimitz") 48 | instr_map.set("3706-NFP", "non_nimitz") 49 | instr_map.set("3706A", "non_nimitz") 50 | instr_map.set("3706A-SNFP", "non_nimitz") 51 | instr_map.set("3706A-S", "non_nimitz") 52 | instr_map.set("3706A-NFP", "non_nimitz") 53 | instr_map.set("707B", "non_nimitz") 54 | instr_map.set("708B", "non_nimitz") 55 | instr_map.set("5880-SRU", "non_nimitz") 56 | instr_map.set("5881-SRU", "non_nimitz") 57 | instr_map.set("2450", "nimitz") 58 | instr_map.set("2470", "nimitz") 59 | instr_map.set("DMM7510", "nimitz") 60 | instr_map.set("2460", "nimitz") 61 | instr_map.set("2461", "nimitz") 62 | instr_map.set("2461-SYS", "nimitz") 63 | instr_map.set("DMM7512", "nimitz") 64 | instr_map.set("DMM6500", "nimitz") 65 | instr_map.set("DAQ6510", "nimitz") 66 | instr_map.set("VERSATEST-300", "versatest") 67 | instr_map.set("VERSATEST-600", "versatest") 68 | instr_map.set("MP5103", "versatest") 69 | instr_map.set("TSP", "versatest") 70 | instr_map.set("TSPop", "versatest") 71 | 72 | /** 73 | * A class used to indicate the start of the Inactive Instruments section of the instrument 74 | * list. This class stores no data and is simply a sentinel. 75 | */ 76 | export class InactiveInstrumentList extends vscode.TreeItem { 77 | constructor() { 78 | super("Offline Instruments", vscode.TreeItemCollapsibleState.Collapsed) 79 | } 80 | } 81 | 82 | /** 83 | * A class used to indicate the start of the Ignored Instruments section of the instrument 84 | * list. This class stores no data and is simply a sentinel. 85 | */ 86 | export class IgnoredInstruments extends vscode.TreeItem { 87 | constructor() { 88 | super("Ignored Instruments", vscode.TreeItemCollapsibleState.Collapsed) 89 | } 90 | } 91 | 92 | /** 93 | * Information describing an instrument that is either saved or discovered. 94 | */ 95 | export class Instrument extends vscode.TreeItem implements vscode.Disposable { 96 | private _name: string = "" 97 | private _connections: Connection[] = [] 98 | private _info: IIDNInfo = { 99 | vendor: "", 100 | model: "", 101 | serial_number: "", 102 | firmware_rev: "", 103 | } 104 | private _status = ConnectionStatus.Inactive 105 | private _category: string = "" 106 | 107 | private _onChanged = new vscode.EventEmitter() 108 | 109 | readonly onChanged: vscode.Event = this._onChanged.event 110 | private _saved: boolean = false 111 | 112 | static from(info: InstrInfo) { 113 | const n = new Instrument( 114 | { 115 | firmware_rev: info.firmware_revision, 116 | model: info.model, 117 | serial_number: info.serial_number, 118 | vendor: info.manufacturer, 119 | }, 120 | info.friendly_name.length === 0 ? undefined : info.friendly_name, 121 | ) 122 | n._category = info.instr_categ 123 | n.addConnection(Connection.from(info)) 124 | 125 | return n 126 | } 127 | 128 | constructor(info: IIDNInfo, name?: string) { 129 | if (!name) { 130 | name = `${info.model}#${info.serial_number}` 131 | } 132 | super(name, vscode.TreeItemCollapsibleState.Expanded) 133 | this.description = idn_to_string(info) 134 | this._info = info 135 | this._name = name 136 | this.tooltip = new vscode.MarkdownString( 137 | [ 138 | `* Manufacturer: ${info.vendor}`, 139 | `* Model: ${info.model}`, 140 | `* Serial Number: ${info.serial_number}`, 141 | `* Firmware Rev: ${info.firmware_rev}`, 142 | ].join("\n"), 143 | ) 144 | this.contextValue = "Instr" 145 | this._category = instr_map.get(this._info.model) ?? "" 146 | if (this._category === "versatest") { 147 | this.contextValue += "Versatest" 148 | } else { 149 | this.contextValue += "Reg" 150 | } 151 | this.contextValue += "Inactive" 152 | this.saved = false 153 | } 154 | dispose() { 155 | for (const c of this._connections) { 156 | c.dispose() 157 | } 158 | } 159 | 160 | get name(): string { 161 | return this._name 162 | } 163 | 164 | set name(name: string) { 165 | this._name = name 166 | this.label = this._name 167 | this._onChanged.fire() 168 | } 169 | 170 | get info(): IIDNInfo { 171 | return this._info 172 | } 173 | 174 | get category(): string { 175 | return this._category 176 | } 177 | 178 | get connections(): Connection[] { 179 | return this._connections 180 | } 181 | 182 | async sendScript(filepath: string) { 183 | const connection = this._connections.find( 184 | (c) => c.status === ConnectionStatus.Connected, 185 | ) 186 | if (!connection) { 187 | return 188 | } 189 | await connection.sendScript(filepath) 190 | } 191 | 192 | async upgrade(): Promise { 193 | let connection = this._connections.find( 194 | (c) => c.status === ConnectionStatus.Connected, 195 | ) 196 | if (!connection) { 197 | const label: string | undefined = await vscode.window.showQuickPick( 198 | this._connections.map((c) => c.label?.toString() ?? ""), 199 | { canPickMany: false, title: "Which connection?" }, 200 | ) 201 | 202 | if (!label) { 203 | return 204 | } 205 | 206 | connection = this._connections.find( 207 | (x) => (x.label?.toString() ?? "") === label, 208 | ) 209 | 210 | if (!connection) { 211 | vscode.window.showErrorMessage( 212 | "Unable to find selected connection", 213 | ) 214 | return 215 | } 216 | } 217 | 218 | let slot: number | undefined = undefined 219 | if (this._category === "versatest") { 220 | let num_slots = 0 221 | switch (this._info.model) { 222 | case "MP5103": 223 | num_slots = 3 224 | break 225 | case "TSPop": 226 | num_slots = 3 227 | break 228 | } 229 | const slot_str = await vscode.window.showQuickPick( 230 | [ 231 | "Mainframe", 232 | ...[...Array(num_slots + 1).keys()] 233 | .slice(1) 234 | .map((n) => `Slot ${n}`), 235 | ], 236 | { canPickMany: false, title: "What do you want to upgrade?" }, 237 | ) 238 | if (!slot_str) { 239 | return 240 | } 241 | 242 | if (slot_str === "Mainframe") { 243 | slot = undefined 244 | } else { 245 | slot = parseInt(slot_str.substring("Slot ".length)) 246 | } 247 | } 248 | 249 | const file = await vscode.window.showOpenDialog({ 250 | title: "Select Firmware File", 251 | filters: { 252 | "Firmware Files": ["x", "upg"], 253 | }, 254 | openLabel: "Upgrade", 255 | }) 256 | if (!file) { 257 | return 258 | } 259 | 260 | await connection.upgrade(file[0].fsPath, slot) 261 | } 262 | 263 | /** 264 | * Gets the ConnectionStatus of the instrument by calling `this.updateStatus`. 265 | * @see updateStatus() 266 | */ 267 | get status(): ConnectionStatus { 268 | return this.updateStatus() 269 | } 270 | 271 | /** 272 | * @param connection A new connection interface to add to this instrument 273 | */ 274 | addConnection(connection: Connection): boolean { 275 | const i = this._connections.findIndex( 276 | (v) => v.addr === connection.addr && v.type === connection.type, 277 | ) 278 | if (i > -1) { 279 | if ( 280 | connection.status !== undefined && 281 | this._connections[i].status !== connection.status 282 | ) { 283 | this._connections[i].status = connection.status 284 | //this._onChanged.fire() 285 | } 286 | return false 287 | } 288 | connection.onChangedStatus(() => { 289 | this.updateStatus() 290 | }, this) 291 | 292 | connection.parent = this 293 | this._connections.push(connection) 294 | 295 | this._onChanged.fire() 296 | 297 | return true 298 | } 299 | 300 | hasConnection(connection: Connection) { 301 | const i = this._connections.findIndex( 302 | (v) => v.addr === connection.addr && v.type === connection.type, 303 | ) 304 | return i > -1 305 | } 306 | 307 | /** 308 | * Check all connections to see if they are responsive. Can be run concurrently with 309 | * other instrument checks. 310 | */ 311 | async getUpdatedStatus(): Promise { 312 | for (const c of this._connections) { 313 | await c.getUpdatedStatus() 314 | } 315 | } 316 | 317 | /** 318 | * Update the status of this Instrument by going through each connection interface 319 | * and then determining the overall status of this instrument: 320 | * 321 | * @returns ConnectionStatus.Connected if any connections are Connected. 322 | * @returns ConnectionStatus.Active if no connections are Connected but at least 1 is Active. 323 | * @returns ConnectionStatus.Inactive otherwise. 324 | */ 325 | updateStatus(): ConnectionStatus { 326 | const status_before = this._status 327 | this._status = ConnectionStatus.Inactive 328 | 329 | // Sort the connections by address 330 | this._connections.sort((a, b) => a.addr.localeCompare(b.addr)) 331 | // Sort the connections by status 332 | // (we want to show higher values first, so invert the result) 333 | this._connections.sort((a, b) => { 334 | const a_status = a.status ?? ConnectionStatus.Inactive 335 | const b_status = b.status ?? ConnectionStatus.Inactive 336 | return -(a_status - b_status) 337 | }) 338 | 339 | for (const c of this._connections) { 340 | if (c.status === ConnectionStatus.Active) { 341 | this._status = ConnectionStatus.Active //If at least one connection is active, we show "Active" 342 | } else if (c.status === ConnectionStatus.Connected) { 343 | this._status = ConnectionStatus.Connected 344 | 345 | break //If any of the connections are connected, we show "Connected" 346 | } 347 | } 348 | 349 | this.iconPath = connectionStatusIcon(this._status) 350 | 351 | if (this._status !== status_before) { 352 | this.contextValue = contextValueStatus( 353 | this.contextValue ?? "Instr", 354 | this._status, 355 | ) 356 | this._onChanged.fire() 357 | } 358 | 359 | if (this._status === ConnectionStatus.Connected) { 360 | this._connections 361 | .filter((c) => c.status !== ConnectionStatus.Connected) 362 | .map((c) => c.enable(false)) 363 | } else { 364 | for (const c of this._connections) { 365 | c.enable(true) 366 | } 367 | } 368 | 369 | return this._status 370 | } 371 | 372 | get saved(): boolean { 373 | return this._saved 374 | } 375 | 376 | set saved(enabled: boolean) { 377 | this._saved = enabled 378 | const str = enabled ? "Saved" : "Discovered" 379 | if (this.contextValue?.match(/Saved|Discovered/)) { 380 | this.contextValue = this.contextValue?.replace( 381 | /Saved|Discovered/, 382 | str, 383 | ) 384 | } else { 385 | this.contextValue += str 386 | } 387 | } 388 | 389 | rename() {} 390 | } 391 | 392 | export class InfoList extends vscode.TreeItem { 393 | private _info?: IIDNInfo | undefined 394 | constructor(info: IIDNInfo) { 395 | super( 396 | "Instrument Information", 397 | vscode.TreeItemCollapsibleState.Collapsed, 398 | ) 399 | this._info = info 400 | } 401 | 402 | get info(): IIDNInfo | undefined { 403 | return this._info 404 | } 405 | } 406 | 407 | export class StringData extends vscode.TreeItem { 408 | constructor(text: string) { 409 | super(text, vscode.TreeItemCollapsibleState.None) 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /src/instrumentExplorer.ts: -------------------------------------------------------------------------------- 1 | import * as child from "child_process" 2 | import { join } from "path" 3 | import * as vscode from "vscode" 4 | import { InactiveInstrumentList, Instrument } from "./instrument" 5 | import { Connection } from "./connection" 6 | import { InstrumentProvider } from "./instrumentProvider" 7 | import { Log, SourceLocation } from "./logging" 8 | import { DISCOVER_EXECUTABLE } from "./kic-cli" 9 | import { LOG_DIR } from "./utility" 10 | 11 | const DISCOVERY_TIMEOUT = 5 12 | 13 | export class InstrumentsExplorer implements vscode.Disposable { 14 | private InstrumentsDiscoveryViewer: vscode.TreeView< 15 | Instrument | Connection | InactiveInstrumentList 16 | > 17 | private treeDataProvider?: InstrumentProvider 18 | private intervalID?: NodeJS.Timeout 19 | //private _kicProcessMgr: KicProcessMgr 20 | private _discoveryInProgress: boolean = false 21 | 22 | constructor( 23 | context: vscode.ExtensionContext, 24 | // kicProcessMgr: KicProcessMgr, 25 | ) { 26 | const LOGLOC: SourceLocation = { 27 | file: "instruments.ts", 28 | func: "InstrumentExplorer.constructor()", 29 | } 30 | // this._kicProcessMgr = kicProcessMgr 31 | 32 | Log.trace("Instantiating TDP", LOGLOC) 33 | const treeDataProvider = InstrumentProvider.instance 34 | Log.trace("Refreshing TDP", LOGLOC) 35 | treeDataProvider 36 | .refresh(async () => await this.startDiscovery()) 37 | .catch((e) => { 38 | Log.error( 39 | `Problem starting Instrument List data provider: ${e}`, 40 | LOGLOC, 41 | ) 42 | }) 43 | 44 | this.InstrumentsDiscoveryViewer = vscode.window.createTreeView< 45 | Instrument | Connection | InactiveInstrumentList 46 | >("InstrumentsExplorer", { 47 | treeDataProvider, 48 | }) 49 | 50 | this.treeDataProvider = treeDataProvider 51 | vscode.commands.registerCommand("InstrumentsExplorer.refresh", () => { 52 | this.InstrumentsDiscoveryViewer.message = 53 | "Checking saved instrument connections..." 54 | this.treeDataProvider 55 | ?.refresh(async () => { 56 | await this.startDiscovery() 57 | }) 58 | .then( 59 | () => { 60 | this.InstrumentsDiscoveryViewer.message = undefined 61 | }, 62 | (e) => { 63 | this.InstrumentsDiscoveryViewer.message = undefined 64 | Log.error(`Unable to refresh instrument explorer: ${e}`) 65 | }, 66 | ) 67 | }) 68 | vscode.commands.registerCommand( 69 | "InstrumentsExplorer.openInstrumentsDiscoveryResource", 70 | () => void 0, 71 | ) 72 | vscode.commands.registerCommand( 73 | "InstrumentsExplorer.revealResource", 74 | () => void 0, 75 | ) 76 | 77 | const saveInstrument = vscode.commands.registerCommand( 78 | "InstrumentsExplorer.save", 79 | async (e: Instrument) => { 80 | await this.saveInstrument(e) 81 | }, 82 | ) 83 | 84 | const removeInstrument = vscode.commands.registerCommand( 85 | "InstrumentsExplorer.remove", 86 | async (e: Instrument) => { 87 | await this.removeInstrument(e) 88 | }, 89 | ) 90 | 91 | context.subscriptions.push(saveInstrument) 92 | context.subscriptions.push(removeInstrument) 93 | 94 | this.InstrumentsDiscoveryViewer.message = 95 | "Checking saved instrument connections..." 96 | 97 | this.treeDataProvider 98 | ?.refresh(async () => await this.startDiscovery()) 99 | .then( 100 | () => { 101 | this.InstrumentsDiscoveryViewer.message = undefined 102 | }, 103 | (e: Error) => { 104 | this.InstrumentsDiscoveryViewer.message = undefined 105 | Log.error(`Unable to start Discovery ${e.message}`, LOGLOC) 106 | }, 107 | ) 108 | } 109 | dispose() { 110 | this.treeDataProvider?.dispose() 111 | } 112 | 113 | private startDiscovery(): Promise { 114 | const LOGLOC: SourceLocation = { 115 | file: "instruments.ts", 116 | func: "InstrumentExplorer.startDiscovery()", 117 | } 118 | return new Promise((resolve) => { 119 | if (!this._discoveryInProgress) { 120 | this.InstrumentsDiscoveryViewer.message = 121 | "Discovering new instrument connections..." 122 | 123 | this._discoveryInProgress = true 124 | const discover = child.spawn( 125 | DISCOVER_EXECUTABLE, 126 | [ 127 | "--log-file", 128 | join( 129 | LOG_DIR, 130 | `${new Date() 131 | .toISOString() 132 | .substring(0, 10)}-kic-discover.log`, 133 | ), 134 | "all", 135 | "--timeout", 136 | DISCOVERY_TIMEOUT.toString(), 137 | "--exit", 138 | ], 139 | //, 140 | // { 141 | // detached: true, 142 | // stdio: "ignore", 143 | // } 144 | ) 145 | 146 | discover.on("exit", (code) => { 147 | if (code) { 148 | Log.trace(`Discover Exit Code: ${code}`, LOGLOC) 149 | } 150 | this.InstrumentsDiscoveryViewer.message = "" 151 | clearInterval(this.intervalID) 152 | this._discoveryInProgress = false 153 | resolve() 154 | }) 155 | 156 | //subprocess.unref() 157 | 158 | //this.treeDataProvider?.clear() 159 | 160 | this.intervalID = setInterval(() => { 161 | this.treeDataProvider?.getContent().then( 162 | () => {}, 163 | () => {}, 164 | ) 165 | }, 1000) 166 | } 167 | }) 168 | } 169 | 170 | public async rename(item: Instrument) { 171 | //if (typeof item === typeof InstrDiscoveryNode) { 172 | const name = await vscode.window.showInputBox({ 173 | placeHolder: "Enter new name", 174 | }) 175 | if ( 176 | name !== null && 177 | name !== undefined && 178 | name.length > 0 && 179 | item !== undefined 180 | ) { 181 | Log.trace(`Changing name from ${item.name} to ${name}`, { 182 | file: "instruments.ts", 183 | func: "InstrumentsExplorer.rename()", 184 | }) 185 | item.name = name 186 | this.treeDataProvider?.addOrUpdateInstrument(item) 187 | this.treeDataProvider?.doWithConfigWatcherOff(() => { 188 | this.treeDataProvider?.updateSaved(item).catch(() => {}) 189 | }) 190 | } else { 191 | Log.warn("Item not defined", { 192 | file: "instruments.ts", 193 | func: "InstrumentsExplorer.rename()", 194 | }) 195 | } 196 | } 197 | 198 | public async saveWhileConnect(instrument: Instrument) { 199 | await this.treeDataProvider?.saveInstrument(instrument) 200 | } 201 | 202 | private async saveInstrument(instr: Instrument) { 203 | await this.treeDataProvider?.saveInstrument(instr) 204 | } 205 | 206 | //from connect 207 | 208 | private async removeInstrument(instr: Instrument) { 209 | instr.saved = false 210 | await this.treeDataProvider?.removeInstrument(instr) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/kic-cli.ts: -------------------------------------------------------------------------------- 1 | const platform = process.platform.toString() 2 | const arch = process.arch.toString() 3 | const kic_cli = `@tektronix/kic-cli-${platform}-${arch}` 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment 6 | const cli = require(kic_cli) 7 | 8 | const { EXECUTABLE, DISCOVER_EXECUTABLE } = cli as { 9 | EXECUTABLE: string 10 | DISCOVER_EXECUTABLE: string 11 | } 12 | 13 | export { EXECUTABLE, DISCOVER_EXECUTABLE } 14 | -------------------------------------------------------------------------------- /src/logging.ts: -------------------------------------------------------------------------------- 1 | import { appendFileSync, PathLike } from "node:fs" 2 | import path from "node:path" 3 | import { LOG_DIR } from "./utility" 4 | 5 | enum LogLevel { 6 | TRACE = 0, 7 | DEBUG = 1, 8 | INFO = 2, 9 | WARN = 3, 10 | ERROR = 4, 11 | } 12 | 13 | const LOGLEVEL_PAD = 5 14 | 15 | function logLevelToString(level: LogLevel): string { 16 | switch (level) { 17 | case LogLevel.TRACE: 18 | return "TRACE" 19 | case LogLevel.DEBUG: 20 | return "DEBUG" 21 | case LogLevel.INFO: 22 | return "INFO" 23 | case LogLevel.WARN: 24 | return "WARN" 25 | case LogLevel.ERROR: 26 | return "ERROR" 27 | } 28 | } 29 | export class Log { 30 | /** 31 | * Write a trace-level log to the log file. 32 | * 33 | * @param msg The message to write to the log file 34 | * @param location The location in which the log took place 35 | */ 36 | public static trace(msg: string, location: SourceLocation): void { 37 | Log.instance().writeln(new Date(), LogLevel.TRACE, msg, location) 38 | } 39 | 40 | /** 41 | * Write a debug-level log to the log file. 42 | * 43 | * @param msg The message to write to the log file 44 | * @param location The location in which the log took place 45 | */ 46 | public static debug(msg: string, location: SourceLocation): void { 47 | Log.instance().writeln(new Date(), LogLevel.DEBUG, msg, location) 48 | } 49 | 50 | /** 51 | * Write an info-level log to the log file. 52 | * 53 | * @param msg The message to write to the log file 54 | * @param location The location in which the log took place 55 | */ 56 | public static info(msg: string, location: SourceLocation): void { 57 | Log.instance().writeln(new Date(), LogLevel.INFO, msg, location) 58 | } 59 | 60 | /** 61 | * Write a warning-level log to the log file. 62 | * 63 | * @param msg The message to write to the log file 64 | * @param location The location in which the log took place 65 | */ 66 | public static warn(msg: string, location: SourceLocation): void { 67 | Log.instance().writeln(new Date(), LogLevel.WARN, msg, location) 68 | } 69 | 70 | /** 71 | * Write a critical-level log to the log file. 72 | * 73 | * @param msg The message to write to the log file 74 | * @param location The location in which the log took place 75 | */ 76 | public static error(msg: string, location?: SourceLocation): void { 77 | Log.instance().writeln(new Date(), LogLevel.ERROR, msg, location) 78 | } 79 | 80 | private date: Date 81 | private file: PathLike 82 | 83 | private static __instance: Log | undefined | null 84 | 85 | private static day_changed(file_date: Date): boolean { 86 | const file_year = file_date.getUTCFullYear() 87 | const file_month = file_date.getUTCMonth() 88 | const file_day = file_date.getUTCDate() 89 | 90 | const date = new Date(Date.now()) 91 | const year = date.getUTCFullYear() 92 | const month = date.getUTCMonth() 93 | const day = date.getUTCDate() 94 | 95 | return file_year !== year || file_month !== month || file_day !== day 96 | } 97 | 98 | private static get_date(): Date { 99 | const date = new Date() 100 | const year = date.getUTCFullYear() 101 | const month = date.getUTCMonth() 102 | const day = date.getUTCDate() 103 | 104 | return new Date(year, month, day) 105 | } 106 | 107 | private static get_date_string(date: Date): string { 108 | return date.toISOString().substring(0, 10) 109 | } 110 | 111 | private static get_time(): Date { 112 | return new Date() 113 | } 114 | 115 | private static get_timestamp(): string { 116 | return Log.get_time().toISOString() 117 | } 118 | 119 | private static instance(): Log { 120 | if (!Log.__instance || Log.day_changed(Log.__instance.date)) { 121 | const date = Log.get_date() 122 | const file = path.join( 123 | LOG_DIR, 124 | `${Log.get_date_string(date)}-tsp-toolkit.log`, 125 | ) 126 | 127 | Log.__instance = new Log(file, date) 128 | } 129 | 130 | return Log.__instance 131 | } 132 | 133 | private constructor(file: string, date: Date) { 134 | this.file = file 135 | this.date = date 136 | } 137 | 138 | private writeln( 139 | timestamp: Date, 140 | level: LogLevel, 141 | msg: string, 142 | location?: SourceLocation, 143 | ): void { 144 | //2024-10-15T12:34:56.789Z [INFO ] source-file.ts@Class.functionName: Some message to write to the log file 145 | const content = `${timestamp.toISOString()} [${logLevelToString(level).padEnd(LOGLEVEL_PAD, " ")}] ${toString(location)}${msg}\n` 146 | try { 147 | appendFileSync(this.file, content) 148 | console.log(content) 149 | } catch (err) { 150 | console.error(err) 151 | } 152 | } 153 | } 154 | 155 | export interface SourceLocation { 156 | file?: string 157 | func?: string 158 | } 159 | 160 | function toString(location?: SourceLocation) { 161 | if (!location) { 162 | return "" 163 | } 164 | if (location.file && location.func) { 165 | return `${location.file}@${location.func}: ` 166 | } 167 | if (location.func) { 168 | return location.func + ": " 169 | } 170 | if (location.file) { 171 | return location.file + ": " 172 | } 173 | 174 | return "" 175 | } 176 | -------------------------------------------------------------------------------- /src/resourceManager.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs" 2 | import { COMMAND_SETS } from "@tektronix/keithley_instrument_libraries" 3 | 4 | export const CONNECTION_RE = 5 | /(?:([A-Za-z0-9_\-+.]*)@)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/ 6 | //export const IPV4_ADDR_RE = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ 7 | 8 | const MPSU50_2ST = "MPSU50-2ST" 9 | const MSMU60_2 = "MSMU60-2" 10 | const EMPTY = "Empty" 11 | 12 | export const NO_OPEN_WORKSPACE_MESSAGE = 13 | "No workspace is open. Please open a workspace to continue." 14 | 15 | const TREBUCHET_SUPPORTED_MODELS_DETAILS: Record< 16 | string, 17 | { noOfSlots?: number; moduleOptions?: string[] } 18 | > = { 19 | MP5103: { 20 | noOfSlots: 3, 21 | moduleOptions: [EMPTY, MPSU50_2ST, MSMU60_2], 22 | }, 23 | } 24 | 25 | /** 26 | * An array of supported model names. 27 | * 28 | * This array is populated by reading the directories within the COMMAND_SETS directory. 29 | * Each directory name represents a supported model. 30 | * 31 | * @type {string[]} 32 | */ 33 | let models: string[] = fs 34 | .readdirSync(COMMAND_SETS) 35 | .filter((folder: string) => 36 | fs.statSync(`${COMMAND_SETS}/${folder}`).isDirectory(), 37 | ) 38 | 39 | // Remove "tsp-lua-5.0" and "nodes_definitions" from the supported models 40 | models = models.filter( 41 | (model) => 42 | model !== "tsp-lua-5.0" && 43 | model !== "nodes_definitions" && 44 | model !== MPSU50_2ST && 45 | model !== MSMU60_2, 46 | ) 47 | 48 | export const SUPPORTED_MODELS_DETAILS = models.reduce< 49 | Record 50 | >((acc, item) => { 51 | if (TREBUCHET_SUPPORTED_MODELS_DETAILS[item]) { 52 | acc[item] = TREBUCHET_SUPPORTED_MODELS_DETAILS[item] 53 | } else { 54 | acc[item] = {} 55 | } 56 | return acc 57 | }, {}) 58 | 59 | //interface for *idn? response 60 | export interface IIDNInfo { 61 | vendor: string 62 | model: string 63 | serial_number: string 64 | firmware_rev: string 65 | } 66 | 67 | export function idn_to_string(idn: IIDNInfo): string { 68 | return `Model: ${idn.model} SN: ${idn.serial_number} FW: ${idn.firmware_rev}` 69 | } 70 | 71 | export enum IoType { 72 | Lan = "Lan", 73 | Visa = "Visa", 74 | } 75 | 76 | export function toIoType(str: string): IoType { 77 | switch (str.toUpperCase().trim()) { 78 | case "LAN": 79 | return IoType.Lan 80 | case "VISA": 81 | return IoType.Visa 82 | default: 83 | throw Error(`Unknown IoType "${str.trim()}"`) 84 | } 85 | } 86 | 87 | /** 88 | * io_type - Lan, Usb etc. 89 | * instr_address - connection address of instrument 90 | * instr_categ - versatest, tti, 26xx etc. 91 | */ 92 | 93 | interface IInstrInfo { 94 | io_type: IoType 95 | instr_address: string 96 | manufacturer: string 97 | model: string 98 | serial_number: string 99 | firmware_revision: string 100 | instr_categ: string 101 | socket_port?: string 102 | } 103 | 104 | export class InstrInfo implements IInstrInfo { 105 | io_type = IoType.Lan 106 | instr_address = "" 107 | manufacturer = "" 108 | model = "" 109 | serial_number = "" 110 | firmware_revision = "" 111 | instr_categ = "" 112 | friendly_name = "" 113 | socket_port?: string | undefined 114 | } 115 | 116 | interface Slot { 117 | slotId: string 118 | module: string 119 | } 120 | 121 | export interface Node { 122 | nodeId: string 123 | mainframe: string 124 | slots?: Slot[] 125 | } 126 | 127 | interface ISystemInfo { 128 | name: string 129 | localNode: string 130 | isActive: boolean 131 | slots?: Slot[] 132 | nodes?: Node[] 133 | } 134 | 135 | export class SystemInfo implements ISystemInfo { 136 | name = "" 137 | isActive = false 138 | localNode = "" 139 | slots?: Slot[] = [] 140 | nodes?: Node[] = [] 141 | } 142 | 143 | export interface ConnectionDetails { 144 | name: string 145 | addr: string 146 | type: IoType 147 | } 148 | 149 | //Hosts multiple functions to help with creating connection with the instrument 150 | export class ConnectionHelper { 151 | public static connectionType(addr: string): IoType | undefined { 152 | if (ConnectionHelper.IPTest(addr)) { 153 | return IoType.Lan 154 | } 155 | if (ConnectionHelper.VisaResourceStringTest(addr)) { 156 | return IoType.Visa 157 | } 158 | return undefined 159 | } 160 | public static parseConnectionString( 161 | connection_string: string, 162 | ): ConnectionDetails | undefined { 163 | let name = "" 164 | let addr = connection_string 165 | if (connection_string.split("@").length > 1) { 166 | name = connection_string.split("@")[0] 167 | addr = connection_string.split("@")[1] 168 | } 169 | const type = ConnectionHelper.connectionType(addr) 170 | 171 | if (!type) { 172 | return undefined 173 | } 174 | 175 | return { name: name, type: type, addr: addr } 176 | } 177 | 178 | public static IPTest(ip: string) { 179 | return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( 180 | ip, 181 | ) 182 | } 183 | public static VisaResourceStringTest(val: string): boolean { 184 | return /^(visa:\/\/.*\/)?((TCPIP|USB|GPIB|ASRL|FIREWIRE|GPIB-VXI|PXI|VXI)\d*)::.*/.test( 185 | val, 186 | ) 187 | } 188 | public static instrConnectionStringValidator = (val: string) => { 189 | let conn_str = val 190 | if (val.split("@").length > 1) { 191 | conn_str = val.split("@")[1] 192 | } 193 | if (!this.IPTest(conn_str) && !this.VisaResourceStringTest(conn_str)) { 194 | return "Enter proper IPv4 address or VISA resource string" 195 | } 196 | return null 197 | } 198 | } 199 | 200 | /** 201 | * Generic class to contain debug related variables 202 | */ 203 | export class DebugHelper { 204 | /** 205 | * Since we need to access the active file when the user clicks 206 | * the debug button, we have created the debuggeeFilePath property 207 | * which is set to vscode active text editor file (.tsp file) 208 | * when the user clicks on debug button and the 209 | * same file is used till end of that debug session 210 | */ 211 | public static debuggeeFilePath: string | undefined 212 | } 213 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: var(--vscode-font-family); 3 | font-size: var(--vscode-font-size); 4 | color: var(--vscode-foreground); 5 | background-color: var(--vscode-sideBar-background); 6 | padding: 10px; 7 | margin-left: 10px; 8 | } 9 | 10 | label { 11 | text-align: left; 12 | color: var(--vscode-foreground); 13 | padding-bottom: 5px; 14 | padding-top: 5px 15 | } 16 | 17 | .accordion-header-region { 18 | border: 1px solid var(--vscode-editorWidget-border); 19 | border-radius: 6px; 20 | background: var(--vscode-sideBar-background); 21 | margin-top: 20px; 22 | box-shadow: 0 1px 4px 0 var(--vscode-widget-shadow,rgba(0,0,0,0.08)); 23 | min-width: 175px; /* Prevent shrinking below this width */ 24 | width: 100%; 25 | max-width: 100%; 26 | box-sizing: border-box; 27 | } 28 | 29 | .accordion { 30 | background-color: var(--vscode-sideBar-background); 31 | color: var(--vscode-foreground); 32 | cursor: pointer; 33 | padding: 10px; 34 | border: none; 35 | border-radius: 4px; 36 | width: 100%; 37 | text-align: left; 38 | transition: 0.3s; 39 | display: flex; 40 | justify-content: space-between; 41 | align-items: center; 42 | margin-bottom: 0; 43 | margin-top: 0; 44 | background: transparent; 45 | box-shadow: none; 46 | } 47 | 48 | .show { 49 | margin-top: 10px; 50 | margin-left: 5px; 51 | margin-bottom: 10px; 52 | 53 | } 54 | 55 | .accordion-left { 56 | display: flex; 57 | align-items: center; 58 | gap: 10px; 59 | } 60 | 61 | .accordion .chevron { 62 | display: inline-block; 63 | transition: transform 0.3s ease; 64 | } 65 | 66 | .accordion[aria-expanded="true"] .chevron { 67 | transform: rotate(90deg); 68 | } 69 | 70 | .accordion[aria-expanded="true"] { 71 | border-bottom: 1px solid var(--vscode-editorWidget-border); 72 | } 73 | 74 | .plus-icon { 75 | font-size: 16px; 76 | cursor: pointer; 77 | } 78 | 79 | .accordion-content { 80 | display: none; 81 | background-color: var(--vscode-sideBar-background); 82 | color: var(--vscode-foreground); 83 | border: none; 84 | background: transparent; 85 | margin-top: 0; 86 | margin-bottom: 0; 87 | padding-left: 0; 88 | padding-right: 0; 89 | max-width: 100%; 90 | box-sizing: border-box; 91 | } 92 | 93 | .accordion-content.show { 94 | display: block; 95 | } 96 | 97 | .node-subgroup, 98 | .form-group, 99 | .slot-group { 100 | display: flex; 101 | flex-direction: column; 102 | align-items: stretch; 103 | gap: 8px; 104 | margin-bottom: 8px; 105 | } 106 | 107 | .selected-system{ 108 | display: grid; 109 | grid-template-columns: 1fr 1fr; 110 | margin-bottom: 20px; 111 | margin-top: 10px; 112 | align-items: baseline; 113 | width: fit-content; 114 | } 115 | 116 | .grid-layout{ 117 | display: grid; 118 | grid-template-columns: 150px 1fr; 119 | align-items: center; 120 | /* gap: 10px; */ 121 | margin-bottom: 15px; 122 | } 123 | 124 | .delete-icon { 125 | cursor: pointer; 126 | } 127 | 128 | @media (max-width: 335px) { 129 | .grid-layout{ 130 | grid-template-columns: 1fr; 131 | } 132 | .selected-system{ 133 | display: flex; 134 | flex-direction: column; 135 | align-items: stretch; 136 | gap: 8px; 137 | margin-bottom: 8px; 138 | } 139 | } 140 | 141 | select { 142 | padding: 6px 8px; 143 | border: 1px solid var(--vscode-editorWidget-border); 144 | border-radius: 4px; 145 | background-color: var(--vscode-settings-dropdownBackground); 146 | color: var(--vscode-settings-dropdownForeground); 147 | cursor: pointer; 148 | width: fit-content; 149 | } 150 | 151 | input[type="text"] { 152 | padding: 6px 8px; 153 | border: 1px solid var(--vscode-editorWidget-border); 154 | border-radius: 4px; 155 | background-color: var(--vscode-input-background); 156 | color: var(--vscode-input-foreground); 157 | } 158 | 159 | input[type="text"].invalid { 160 | border: 1px solid var(--vscode-inputValidation-errorBorder); 161 | background-color: var(--vscode-inputValidation-errorBackground); 162 | color: var(--vscode-inputValidation-errorForeground); 163 | } 164 | 165 | input[type="text"].invalid::placeholder { 166 | color: var(--vscode-inputValidation-errorForeground); 167 | } 168 | 169 | .invalid-node-number { 170 | border: 1px solid var(--vscode-inputValidation-errorBorder); 171 | background-color: var(--vscode-inputValidation-errorBackground); 172 | color: var(--vscode-inputValidation-errorForeground); 173 | } 174 | 175 | .invalid-node-number:hover { 176 | background-color: var(--vscode-inputValidation-errorBackground); 177 | color: var(--vscode-inputValidation-errorForeground); 178 | } 179 | 180 | /* Tooltip styling (uses the native title attribute) */ 181 | .invalid-node-number[title] { 182 | cursor: help; 183 | /* Indicates that the element has additional information */ 184 | } 185 | 186 | 187 | input[type="text"]::placeholder { 188 | color: var(--vscode-input-placeholderForeground); 189 | } 190 | 191 | 192 | .vscode-style-button { 193 | /* margin-top: 10px; */ 194 | background-color: var(--vscode-button-background); 195 | color: var(--vscode-button-foreground); 196 | border: 1px solid var(--vscode-button-border); 197 | transition: background-color 0.3s; 198 | align-items: center; 199 | border: 1px solid var(--vscode-button-border, transparent); 200 | border-radius: 2px; 201 | box-sizing: border-box; 202 | cursor: pointer; 203 | display: flex; 204 | justify-content: center; 205 | line-height: 18px; 206 | padding: 4px; 207 | text-align: center; 208 | width: 100%; 209 | } 210 | 211 | .vscode-style-button:hover { 212 | background-color: var(--vscode-button-hoverBackground); 213 | } 214 | 215 | .select-with-icon { 216 | display: flex; 217 | align-items: center; 218 | border: 1px solid var(--vscode-editorWidget-border); 219 | border-radius: 4px; 220 | background-color: var(--vscode-input-background); 221 | color: var(--vscode-input-foreground); 222 | width: fit-content; 223 | } 224 | .select-with-icon select { 225 | border: none; 226 | outline: none; 227 | background: transparent; 228 | color: inherit; 229 | height: 100%; 230 | box-shadow: none; 231 | padding: 6px 8px; 232 | background-color: var(--vscode-input-background); 233 | cursor: pointer; 234 | width: fit-content; 235 | } 236 | .select-with-icon .delete-icon { 237 | margin-inline: 5px; 238 | display: flex; 239 | align-items: center; 240 | cursor: pointer; 241 | transition: background 0.2s; 242 | } 243 | .select-with-icon .delete-icon:hover { 244 | background-color: var(--vscode-toolbar-hoverBackground); 245 | color: var(--vscode-sideBarSectionHeader-foreground); 246 | } -------------------------------------------------------------------------------- /src/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | 3 | suite("Extension Test Suite", function () { 4 | test("TODO", function () { 5 | assert.equal(true, true) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /src/utility.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync, PathLike } from "node:fs" 2 | import { homedir, platform, tmpdir } from "node:os" 3 | import { basename, join } from "node:path" 4 | import { env } from "node:process" 5 | 6 | interface PathsInterface { 7 | data: PathLike 8 | config: PathLike 9 | cache: PathLike 10 | log: PathLike 11 | temp: PathLike 12 | } 13 | 14 | class Paths { 15 | readonly _data: string 16 | readonly _config: string 17 | readonly _cache: string 18 | readonly _log: string 19 | readonly _temp: string 20 | 21 | constructor(options: PathsInterface) { 22 | this._data = options.data.toString() 23 | this._config = options.config.toString() 24 | this._cache = options.cache.toString() 25 | this._log = options.log.toString() 26 | this._temp = options.temp.toString() 27 | } 28 | 29 | get data(): string { 30 | if (!existsSync(this._data)) { 31 | mkdirSync(this._data, { recursive: true }) 32 | } 33 | return this._data 34 | } 35 | 36 | get config(): string { 37 | if (!existsSync(this._config)) { 38 | mkdirSync(this._config, { recursive: true }) 39 | } 40 | return this._config 41 | } 42 | 43 | get cache(): string { 44 | if (!existsSync(this._cache)) { 45 | mkdirSync(this._cache, { recursive: true }) 46 | } 47 | return this._cache 48 | } 49 | 50 | get log(): string { 51 | if (!existsSync(this._log)) { 52 | mkdirSync(this._log, { recursive: true }) 53 | } 54 | return this._log 55 | } 56 | 57 | get temp(): string { 58 | if (!existsSync(this._temp)) { 59 | mkdirSync(this._temp, { recursive: true }) 60 | } 61 | return this._temp 62 | } 63 | } 64 | 65 | // Paths referenced from https://github.com/sindresorhus/env-paths/blob/v3.0.0/index.js 66 | // License: 67 | // MIT License 68 | // 69 | // Copyright (c) Sindre Sorhus (https://sindresorhus.com) 70 | // 71 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 72 | // 73 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 74 | // 75 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 76 | 77 | const PATHS: Paths = (function (app_name: string): Paths { 78 | switch (platform()) { 79 | case "win32": { 80 | const ad = env.APPDATA || join(homedir(), "AppData", "Roaming") 81 | const lad = env.LOCALAPPDATA || join(homedir(), "AppData", "Local") 82 | 83 | return new Paths({ 84 | data: join(lad, app_name, "Data"), 85 | config: join(ad, app_name, "Config"), 86 | cache: join(lad, app_name, "Cache"), 87 | log: join(lad, app_name, "Log"), 88 | temp: join(tmpdir(), app_name), 89 | }) 90 | } 91 | case "darwin": { 92 | const lib = join(homedir(), "Library") 93 | 94 | return new Paths({ 95 | data: join(lib, app_name, "Application Support"), 96 | config: join(lib, app_name, "Preferences"), 97 | cache: join(lib, app_name, "Caches"), 98 | log: join(lib, app_name, "Logs"), 99 | temp: join(tmpdir(), app_name), 100 | }) 101 | } 102 | case "linux": { 103 | const username = basename(homedir()) 104 | return new Paths({ 105 | data: join( 106 | env.XDG_DATA_HOME || join(homedir(), ".local", "share"), 107 | app_name, 108 | ), 109 | config: join( 110 | env.XDG_CONFIG_HOME || join(homedir(), ".config"), 111 | app_name, 112 | ), 113 | cache: join( 114 | env.XDG_CACHE_HOME || join(homedir(), ".cache"), 115 | app_name, 116 | ), 117 | log: join( 118 | env.XDG_STATE_HOME || join(homedir(), ".local", "state"), 119 | app_name, 120 | ), 121 | temp: join(tmpdir(), username, app_name), 122 | }) 123 | } 124 | default: { 125 | return new Paths({ 126 | data: join(homedir(), app_name, "data"), 127 | config: join(homedir(), app_name, "config"), 128 | cache: join(homedir(), app_name, "cache"), 129 | log: join(homedir(), app_name, "log"), 130 | temp: join(tmpdir(), app_name), 131 | }) 132 | } 133 | } 134 | })("tsp-toolkit") 135 | 136 | const log_path = PATHS.log 137 | 138 | /** 139 | * The appropriate location for user-level logs. 140 | * - **Windows**: C:\Users\USERNAME\AppData\Local\tsp-toolkit\Log 141 | * - **Linux**: ~/.local/state/tsp-toolkit 142 | * - **macOS**: ~/Library/Logs/tsp-toolkit 143 | */ 144 | export const LOG_DIR = log_path 145 | export const COMMON_PATHS = PATHS 146 | -------------------------------------------------------------------------------- /src/workspaceManager.ts: -------------------------------------------------------------------------------- 1 | import { join } from "path" 2 | import * as fs from "fs" 3 | import { COMMAND_SETS } from "@tektronix/keithley_instrument_libraries" 4 | import * as vscode from "vscode" 5 | import { SystemInfo } from "./resourceManager" 6 | import { Log } from "./logging" 7 | export async function updateConfiguration( 8 | config_name: string, 9 | value: unknown, 10 | target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Workspace, 11 | workspace_path?: vscode.WorkspaceFolder, 12 | ) { 13 | const config = workspace_path 14 | ? vscode.workspace.getConfiguration(undefined, workspace_path.uri) 15 | : vscode.workspace.getConfiguration() 16 | 17 | const currentConfiguration = config.get(config_name, {}) 18 | const updatedConfiguration = 19 | typeof currentConfiguration === "object" && 20 | !Array.isArray(currentConfiguration) 21 | ? { ...currentConfiguration, ...(value as object) } 22 | : value 23 | 24 | try { 25 | await config.update(config_name, updatedConfiguration, target) 26 | // DEBUG ONLY 27 | // if (notification) { 28 | // void vscode.window.showInformationMessage( 29 | // `${config_name} configuration updated successfully.`, 30 | // ) 31 | // } 32 | } catch { 33 | void vscode.window.showErrorMessage( 34 | `Failed to update ${config_name} configuration`, 35 | ) 36 | } 37 | } 38 | 39 | export async function configure_initial_workspace_configurations() { 40 | await updateConfiguration( 41 | "files.associations", 42 | { 43 | "*.tsp": "lua", 44 | }, 45 | vscode.ConfigurationTarget.Global, 46 | ) 47 | 48 | if (vscode.workspace.workspaceFolders) { 49 | //increasing Lua.workspace.preloadFileSize to 2000KB, 50 | //to handle the increasing file size of lua defination file 51 | await updateConfiguration( 52 | "Lua.workspace.preloadFileSize", 53 | 2000, 54 | vscode.ConfigurationTarget.Workspace, 55 | ) 56 | await updateConfiguration( 57 | "Lua.workspace.ignoreDir", 58 | [], 59 | vscode.ConfigurationTarget.Workspace, 60 | ) 61 | await updateConfiguration( 62 | "Lua.diagnostics.libraryFiles", 63 | "Disable", 64 | vscode.ConfigurationTarget.Workspace, 65 | ) 66 | await updateConfiguration( 67 | "Lua.runtime.version", 68 | "Lua 5.1", 69 | vscode.ConfigurationTarget.Workspace, 70 | ) 71 | await updateConfiguration( 72 | "Lua.runtime.builtin", 73 | { 74 | basic: "disable", 75 | bit: "disable", 76 | bit32: "disable", 77 | builtin: "disable", 78 | coroutine: "disable", 79 | debug: "disable", 80 | ffi: "disable", 81 | io: "disable", 82 | jit: "disable", 83 | "jit.profile": "disable", 84 | "jit.util": "disable", 85 | math: "disable", 86 | os: "disable", 87 | package: "disable", 88 | string: "disable", 89 | "string.buffer": "disable", 90 | table: "disable", 91 | "table.clear": "disable", 92 | "table.new": "disable", 93 | utf8: "disable", 94 | }, 95 | vscode.ConfigurationTarget.Workspace, 96 | ) 97 | await updateLuaLibraryConfigurations() 98 | } 99 | } 100 | 101 | /** 102 | * Updates the workspace Lua library configurations based on system information and active nodes. 103 | * 104 | * This function: 105 | * - Manages a folder that stores generated Lua node definitions. 106 | * - Retrieves system configuration details (local and remote nodes). 107 | * - Creates custom Lua file definitions for specific nodes. 108 | * - Dynamically updates the "Lua.workspace.library" settings with the new paths. 109 | * 110 | * @async 111 | * @returns {Promise} A promise that resolves when the configuration update is complete. 112 | */ 113 | export async function updateLuaLibraryConfigurations(): Promise { 114 | try { 115 | await updateConfiguration( 116 | "Lua.workspace.library", 117 | [], 118 | vscode.ConfigurationTarget.Workspace, 119 | ) 120 | const newLibrarySettings: string[] = [] 121 | newLibrarySettings.push(join(COMMAND_SETS, "tsp-lua-5.0")) 122 | 123 | const luaDefinitionsFolderPath = join(COMMAND_SETS, "nodes_definitions") 124 | if (fs.existsSync(luaDefinitionsFolderPath)) { 125 | fs.rmSync(luaDefinitionsFolderPath, { 126 | recursive: true, 127 | force: true, 128 | }) 129 | } 130 | fs.mkdirSync(luaDefinitionsFolderPath, { recursive: true }) 131 | 132 | const systemInfo: SystemInfo[] = 133 | vscode.workspace 134 | .getConfiguration("tsp") 135 | .get("tspLinkSystemConfigurations") ?? [] 136 | const activeSystem = systemInfo.find((item) => item.isActive) 137 | 138 | const nodeDetails: Record = {} 139 | 140 | createCommonNodeSlotTables(luaDefinitionsFolderPath, "---@meta\n") 141 | if (activeSystem?.localNode) { 142 | nodeDetails[activeSystem.localNode] = ["localNode"] 143 | } 144 | 145 | const slotDetails: Record = {} 146 | 147 | if (activeSystem?.slots) { 148 | // Collect slot IDs for the table 149 | const slotIds: string[] = [] 150 | for (const slot of activeSystem.slots) { 151 | if (!slot.module.includes("Empty")) { 152 | if (!slotDetails[slot.module]) { 153 | slotDetails[slot.module] = [] 154 | } 155 | slotDetails[slot.module].push(slot.slotId) 156 | slotIds.push( 157 | `[${slot.slotId.split("[")[1]} = ${slot.slotId}`, 158 | ) 159 | createCommonNodeSlotTables( 160 | luaDefinitionsFolderPath, 161 | `---${slot.module}\n---@type table\n${slot.slotId}={}\n`, 162 | ) 163 | } 164 | } 165 | 166 | const slotsTable = `slot = {${slotIds.join(", ")}}` 167 | createCommonNodeSlotTables(luaDefinitionsFolderPath, slotsTable) 168 | } 169 | 170 | if (activeSystem?.nodes) { 171 | const nodeIds: string[] = [] 172 | for (const node of activeSystem.nodes) { 173 | if (!nodeDetails[node.mainframe]) { 174 | nodeDetails[node.mainframe] = [] 175 | } 176 | nodeDetails[node.mainframe].push(node.nodeId) 177 | 178 | nodeIds.push(`[${node.nodeId.split("[")[1]} = ${node.nodeId}`) 179 | if (node?.slots) { 180 | // Collect slot IDs for the table 181 | const slotIds: string[] = [] 182 | for (const slot of node.slots) { 183 | if (!slot.module.includes("Empty")) { 184 | if (!slotDetails[slot.module]) { 185 | slotDetails[slot.module] = [] 186 | } 187 | slotDetails[slot.module].push( 188 | node.nodeId + slot.slotId, 189 | ) 190 | slotIds.push( 191 | `[${slot.slotId.split("[")[1]} = ${node.nodeId}.${slot.slotId}`, 192 | ) 193 | createCommonNodeSlotTables( 194 | luaDefinitionsFolderPath, 195 | `---${slot.module}\n---@type table\n${node.nodeId}.${slot.slotId}={}`, 196 | ) 197 | } 198 | } 199 | const slotsTable = `${node.nodeId}.slot = {${slotIds.join(", ")}}` 200 | createCommonNodeSlotTables( 201 | luaDefinitionsFolderPath, 202 | slotsTable, 203 | ) 204 | } 205 | } 206 | const nodeTable = `node = {${nodeIds.join(", ")}}` 207 | createCommonNodeSlotTables(luaDefinitionsFolderPath, nodeTable) 208 | } 209 | 210 | for (const [model, nodes] of Object.entries(nodeDetails)) { 211 | const libBasePath = join(COMMAND_SETS, model.toUpperCase()) 212 | newLibrarySettings.push(join(libBasePath, "Helper")) 213 | 214 | if (nodes.some((str) => str.includes("localNode"))) { 215 | newLibrarySettings.push(join(libBasePath, "AllTspCommands")) 216 | } 217 | 218 | for (const node of nodes) { 219 | if (node.includes("node")) { 220 | const nodeNum = parseInt(node.match(/\d+/)?.[0] || "", 10) 221 | createNodeCmdFile( 222 | libBasePath, 223 | nodeNum, 224 | luaDefinitionsFolderPath, 225 | model, 226 | ) 227 | } 228 | } 229 | } 230 | 231 | for (const [model, slots] of Object.entries(slotDetails)) { 232 | const libBasePath = join(COMMAND_SETS, model.toUpperCase()) 233 | newLibrarySettings.push(join(libBasePath, "Helper")) 234 | 235 | if (slots.some((str) => str.includes("localNode"))) { 236 | newLibrarySettings.push(join(libBasePath, "AllTspCommands")) 237 | } 238 | 239 | for (const slotId of slots) { 240 | if (slotId.includes("node[") && slotId.includes("slot[")) { 241 | const nodeMatch = slotId.match(/node\[(\d+)\]/) 242 | const slotMatch = slotId.match(/slot\[(\d+)\]/) 243 | const nodeNum = nodeMatch ? parseInt(nodeMatch[1], 10) : 0 244 | const slotNum = slotMatch ? parseInt(slotMatch[1], 10) : 0 245 | createNodeSlotCmdFile( 246 | libBasePath, 247 | nodeNum, 248 | slotNum, 249 | luaDefinitionsFolderPath, 250 | model, 251 | ) 252 | } else if (slotId.includes("slot[")) { 253 | const slotNum = parseInt(slotId.match(/\d+/)?.[0] || "", 10) 254 | createSlotCmdFile( 255 | libBasePath, 256 | slotNum, 257 | luaDefinitionsFolderPath, 258 | model, 259 | ) 260 | } else { 261 | /* empty */ 262 | } 263 | } 264 | } 265 | 266 | // Add luaDefinitionsFolderPath to library settings if it contains files 267 | if (fs.readdirSync(luaDefinitionsFolderPath).length !== 0) { 268 | newLibrarySettings.push(luaDefinitionsFolderPath) 269 | } 270 | 271 | // Wait for a few seconds before updating the configuration 272 | await new Promise((resolve) => setTimeout(resolve, 2000)) 273 | await updateConfiguration( 274 | "Lua.workspace.library", 275 | newLibrarySettings, 276 | vscode.ConfigurationTarget.Workspace, 277 | ) 278 | } catch (error) { 279 | Log.error( 280 | `Error updating Lua library configurations: ${String(error)}`, 281 | { 282 | file: "extension.ts", 283 | func: "updateLuaLibraryConfigurations()", 284 | }, 285 | ) 286 | } 287 | } 288 | 289 | function createNodeCmdFile( 290 | libBasePath: string, 291 | nodeNum: number, 292 | luaDefinitionsFolderPath: string, 293 | model: string, 294 | ) { 295 | const nodeCmdFilePath = join( 296 | libBasePath, 297 | "tspLinkSupportedCommands", 298 | "definitions.txt", 299 | ) 300 | const nodeCmdFileContent = fs 301 | .readFileSync(nodeCmdFilePath, "utf8") 302 | .replace(/\$node_number\$/g, nodeNum.toString()) 303 | const newNodeCmdFilePath = join( 304 | luaDefinitionsFolderPath, 305 | `${model}_node${nodeNum}.lua`, 306 | ) 307 | fs.writeFileSync(newNodeCmdFilePath, nodeCmdFileContent) 308 | } 309 | 310 | function createSlotCmdFile( 311 | libBasePath: string, 312 | slotNum: number, 313 | luaDefinitionsFolderPath: string, 314 | model: string, 315 | ) { 316 | const slotCmdFilePath = join( 317 | libBasePath, 318 | "AllTspCommands", 319 | "definitions.lua", 320 | ) 321 | const nodeCmdFileContent = fs 322 | .readFileSync(slotCmdFilePath, "utf8") 323 | .replace(/\$slot_number\$/g, slotNum.toString()) 324 | const newNodeCmdFilePath = join( 325 | luaDefinitionsFolderPath, 326 | `${model}_slot${slotNum}.lua`, 327 | ) 328 | fs.writeFileSync(newNodeCmdFilePath, nodeCmdFileContent) 329 | } 330 | 331 | function createNodeSlotCmdFile( 332 | libBasePath: string, 333 | nodeNum: number, 334 | slotNum: number, 335 | luaDefinitionsFolderPath: string, 336 | model: string, 337 | ) { 338 | const nodeSlotCmdFilePath = join( 339 | libBasePath, 340 | "tspLinkSupportedCommands", 341 | "definitions.txt", 342 | ) 343 | const nodeCmdFileContent = fs 344 | .readFileSync(nodeSlotCmdFilePath, "utf8") 345 | .replace(/\$node_number\$/g, nodeNum.toString()) 346 | .replace(/\$slot_number\$/g, slotNum.toString()) 347 | const newNodeCmdFilePath = join( 348 | luaDefinitionsFolderPath, 349 | `${model}_node${nodeNum}_slot${slotNum}.lua`, 350 | ) 351 | fs.writeFileSync(newNodeCmdFilePath, nodeCmdFileContent) 352 | } 353 | 354 | function createCommonNodeSlotTables( 355 | luaDefinitionsFolderPath: string, 356 | contains: string, 357 | ) { 358 | const newNodeCmdFilePath = join( 359 | luaDefinitionsFolderPath, 360 | "commonTables.lua", 361 | ) 362 | fs.writeFileSync(newNodeCmdFilePath, `\n${contains}`, { flag: "a" }) 363 | } 364 | -------------------------------------------------------------------------------- /third-party/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Adding Third-Party Components 2 | 3 | When adding new third-party code, place all code from that party into its own directory alongside a separate "LICENSE" and "README.md" file. 4 | 5 | The LICENSE file must be an exact copy of the third-party's applicable LICENSE file. 6 | 7 | The README file must contain a hyperlink to the source and commit as applicable. The hyperlink may be a link directly to the commit the component was copied from in that third-party's repository. 8 | 9 | For example, if you needed to add a component from Tektronix's [Syphon](https://github.com/tektronix/syphon) repo from commit [147c48e](https://github.com/tektronix/syphon/tree/147c48ecd91b00d372631b61eec9e69886e19af5) and weren't able to add it as an NPM or Cargo dependency, then you'd create the following: 10 | ```text 11 | third-party/ 12 | syphon/ 13 | component.file 14 | LICENSE 15 | README.md 16 | ``` 17 | In this example 18 | * `component.file` is whatever component you've copied from the repo, 19 | * `LICENSE` is copied exactly as it appears in said commit's [LICENSE](https://github.com/tektronix/syphon/blob/147c48ecd91b00d372631b61eec9e69886e19af5/LICENSE) file, 20 | * and contents of `README.md` are as follows 21 | ```markdown 22 | # Syphon 23 | 24 | This directory contains components copied from Tektronix's Syphon repository as it appears in commit 25 | [147c48ecd91b00d372631b61eec9e69886e19af5](https://github.com/tektronix/syphon/tree/147c48ecd91b00d372631b61eec9e69886e19af5). 26 | 27 | # License 28 | 29 | 30 | Copyright Tektronix Inc. 31 | 32 | Licensed under the [MIT](./LICENSE) license. 33 | ``` 34 | 35 | # Adding Integrated Third-Party Components 36 | 37 | Sometimes third-party components must be located alongside first-party code. There are very particular legal requirements for which licenses are acceptable in this scenario. The remainder of these instructions assume you've done your due diligence. 38 | 39 | All integrated third-party components must include the following information in a header comment: 40 | 1. a hyperlink to the source and commit as applicable, 41 | 1. notice of any alterations, 42 | 1. and the original license in full. 43 | 44 | If you perform any alterations, you may update the copyright information of the included original license if permitted by said license. 45 | 46 | The below example is the header from [Tsp.g4](https://github.com/tektronix/vscode-tsplang/blob/96bbfb247818332e8f53135e2cb2c96cab47c915/server/@antlr4-tsplang/Tsp.g4) as it appears in Tektronix's [vscode-tsplang](https://github.com/tektronix/vscode-tsplang) repository in commit [96bbfb247818332e8f53135e2cb2c96cab47c915](https://github.com/tektronix/vscode-tsplang/tree/96bbfb247818332e8f53135e2cb2c96cab47c915). 47 | 48 | ```c 49 | /* 50 | * This grammar file is adapted by Tektronix from Lua.g4 at commit 51 | * dbe02c840ffd07197e62e51926f49cb130819179 52 | * available at https://github.com/antlr/grammars-v4/tree/dbe02c840ffd07197e62e51926f49cb130819179/lua. 53 | * 54 | * Except as otherwise noted, the content of this file is licensed under the 55 | * BSD-3-Clause license. The text of the BSD-3-Clause license is reproduced 56 | * below. 57 | * 58 | * ---------------------------------------------------------------------------- 59 | * 60 | * Copyright (c) 2013, Kazunori Sakamoto 61 | * Copyright (c) 2016, Alexander Alexeev 62 | * Copyright (c) 2018, Tektronix Inc. 63 | * All rights reserved. 64 | * 65 | * Redistribution and use in source and binary forms, with or without 66 | * modification, are permitted provided that the following conditions 67 | * are met: 68 | * 69 | * 1. Redistributions of source code must retain the above copyright 70 | * notice, this list of conditions and the following disclaimer. 71 | * 2. Redistributions in binary form must reproduce the above copyright 72 | * notice, this list of conditions and the following disclaimer in the 73 | * documentation and/or other materials provided with the distribution. 74 | * 3. Neither the NAME of Rainer Schuster nor the NAMEs of its contributors 75 | * may be used to endorse or promote products derived from this software 76 | * without specific prior written permission. 77 | * 78 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 79 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 80 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 81 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 82 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 83 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 84 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 85 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 86 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 87 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 88 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 89 | */ 90 | /* 91 | * The left-recursion of prefixexp in the Lua 5.0 grammar was removed thanks 92 | * to the following post: 93 | * http://lua-users.org/lists/lua-l/2010-12/msg00699.html 94 | */ 95 | ``` 96 | -------------------------------------------------------------------------------- /third-party/README.md: -------------------------------------------------------------------------------- 1 | # Third-Party Components 2 | 3 | This directory and the files detailed below contain code and/or code alterations that are subject to third party license requirements. 4 | 5 | Third party code either resides within this directory, alongside its applicable license, or in the location listed below. 6 | 7 | 8 | # Integrated Third-Party Components 9 | 10 | The following files are subject to third party license requirements but do not reside in this directory. Each file's full license is available as a header comment. 11 | 12 | * _ 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "target": "ES2022", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES2022" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, /* enable all strict type-checking options */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press F5 to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (Ctrl+Shift+P or Cmd+Shift+P on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (Ctrl+R or Cmd+R on Mac) the VS Code window with your extension to load your changes. 23 | 24 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (Ctrl+Shift+D or Cmd+Shift+D on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press F5 to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | --------------------------------------------------------------------------------