├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── hw_unit.yml │ ├── preparePackage.js │ └── publish.yml ├── .gitignore ├── .nvmrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTE.md ├── FAQ.md ├── GET_STARTED.md ├── LICENSE.md ├── README.md ├── RELEASE_NOTES_2.22.x.md ├── RELEASE_NOTES_2.24.x.md ├── WHATS_NEW.md ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── icons.css │ ├── icons.png │ ├── icons@2x.png │ ├── main.js │ ├── search.js │ ├── style.css │ ├── widgets.png │ └── widgets@2x.png ├── classes │ └── PyMakr.html ├── index.html ├── media │ ├── contribute │ │ ├── release-flow.png │ │ └── run-extension.gif │ ├── dark │ │ ├── lightning-muted.svg │ │ └── lightning.svg │ ├── light │ │ ├── lightning-muted.svg │ │ └── lightning.svg │ ├── pycom.png │ ├── pycom.svg │ ├── pymakr2.png │ └── readme │ │ ├── connect-device-and-sync-up.gif │ │ ├── connect-device.gif │ │ ├── create-project.gif │ │ ├── design.png │ │ ├── device-file-explorer.gif │ │ ├── insights.gif │ │ ├── install-pymakr.gif │ │ ├── move-view.gif │ │ ├── multiple-connections.gif │ │ ├── open-terminal.gif │ │ ├── projects.gif │ │ ├── pycom-tab-icon.png │ │ ├── pymakr-a-z.gif │ │ ├── pymakr-add-device-to-file-explorer.gif │ │ ├── pymakr-add-device-to-project.gif │ │ ├── pymakr-connect-a-device.gif │ │ ├── pymakr-create-project.gif │ │ ├── pymakr-move-to-explorer.gif │ │ ├── pymakr-move-view.gif │ │ ├── saving-a-file.gif │ │ ├── shared-terminal.gif │ │ └── typed.gif └── modules.html ├── example └── workspace │ ├── multi │ ├── multi1 │ │ └── pymakr.conf │ └── multi2 │ │ └── pymakr.conf │ └── plain │ ├── pymakr.conf │ └── workspace.code-workspace ├── extension.js ├── media ├── contribute │ ├── release-flow.png │ └── run-extension.gif ├── dark │ ├── dev-cross-dark.svg │ ├── dev-dark.svg │ ├── lightning-muted.svg │ └── lightning.svg ├── light │ ├── dev-cross-light.svg │ ├── dev-light.svg │ ├── lightning-muted.svg │ └── lightning.svg ├── pycom.png ├── pycom.svg ├── pymakr2.png └── readme │ ├── connect-device-and-sync-up.gif │ ├── connect-device.gif │ ├── create-project.gif │ ├── design.png │ ├── dev-mode.gif │ ├── device-file-explorer.gif │ ├── insights.gif │ ├── install-pymakr.gif │ ├── move-view.gif │ ├── multiple-connections.gif │ ├── open-terminal.gif │ ├── projects.gif │ ├── pycom-tab-icon.png │ ├── pymakr-a-z.gif │ ├── pymakr-add-device-to-file-explorer.gif │ ├── pymakr-add-device-to-project.gif │ ├── pymakr-connect-a-device.gif │ ├── pymakr-create-project.gif │ ├── pymakr-move-to-explorer.gif │ ├── pymakr-move-view.gif │ ├── saving-a-file.gif │ ├── shared-terminal.gif │ └── typed.gif ├── package-lock.json ├── package.json ├── pymakr.schema.json ├── release.config.js ├── src ├── Device.js ├── Project.js ├── PyMakr.js ├── StatusBar.js ├── Watcher │ ├── DeviceManager.js │ ├── Watcher.js │ ├── _pymakr_dev │ │ └── fake_machine.py │ ├── scripts.js │ ├── spec │ │ └── utils.spec.js │ └── utils.js ├── commands │ └── index.js ├── globals.d.ts ├── providers │ ├── DevicesProvider.js │ ├── FilesystemProvider.js │ ├── HomeProvider.js │ ├── ProjectsProvider.js │ └── TextDocumentProvider.js ├── stores │ ├── devices.js │ ├── projects.js │ └── terminals.js ├── terminal │ ├── Server.js │ ├── Terminal.js │ └── bin │ │ ├── client-linux │ │ ├── client-macos │ │ ├── client-win.exe │ │ └── client.js └── utils │ ├── Notifier.js │ ├── blockingProxy.js │ ├── createLogger.js │ ├── errors.js │ ├── formatters.js │ ├── misc.js │ ├── msgs.js │ ├── readUntil.js │ ├── specs │ ├── _sampleProject │ │ └── pymakr.conf │ ├── blockingProxy.spec.mjs │ ├── formatters.test.js │ ├── misc.spec.mjs │ ├── probs.config.js │ ├── readUntil.test.js │ └── store.spec.js │ ├── storageObj.js │ ├── store.js │ ├── terminalExec.js │ └── vscodeHelpers.js ├── templates ├── empty │ ├── boot.py │ ├── main.py │ └── pymakr.conf └── led-example │ ├── boot.py │ ├── main.py │ └── pymakr.conf ├── test ├── fixtures │ ├── 1-project │ │ └── pymakr.conf │ ├── 2-projects │ │ ├── project-a │ │ │ └── pymakr.conf │ │ └── project-b │ │ │ └── pymakr.conf │ ├── empty │ │ ├── README.md │ │ └── workspace.code-workspace │ ├── large-project │ │ ├── boot.py │ │ ├── files.bak │ │ │ ├── 93 │ │ │ ├── 253 │ │ │ ├── 342 │ │ │ ├── 627 │ │ │ ├── 667 │ │ │ ├── 1024 │ │ │ ├── 1303 │ │ │ ├── 1829 │ │ │ ├── 2103 │ │ │ ├── 2133 │ │ │ ├── 2305 │ │ │ ├── 2487 │ │ │ ├── 2594 │ │ │ ├── 2686 │ │ │ ├── 3025 │ │ │ ├── 3144 │ │ │ ├── 3154 │ │ │ ├── 3378 │ │ │ ├── 3399 │ │ │ ├── 3467 │ │ │ ├── 3508 │ │ │ ├── 3622 │ │ │ ├── 3927 │ │ │ ├── 4035 │ │ │ ├── 4131 │ │ │ ├── 4146 │ │ │ ├── 4279 │ │ │ ├── 4462 │ │ │ ├── 4601 │ │ │ ├── 4755 │ │ │ ├── 4763 │ │ │ ├── 4767 │ │ │ ├── 4769 │ │ │ ├── 4865 │ │ │ ├── 5123 │ │ │ ├── 5221 │ │ │ ├── 5395 │ │ │ ├── 5454 │ │ │ ├── 5665 │ │ │ ├── 6002 │ │ │ ├── 6065 │ │ │ ├── 6085 │ │ │ ├── 6472 │ │ │ ├── 6590 │ │ │ ├── 6673 │ │ │ ├── 6836 │ │ │ ├── 7079 │ │ │ ├── 7089 │ │ │ ├── 7198 │ │ │ ├── 7233 │ │ │ ├── 7290 │ │ │ ├── 7370 │ │ │ ├── 7496 │ │ │ ├── 7539 │ │ │ ├── 7761 │ │ │ ├── 7859 │ │ │ ├── 7973 │ │ │ ├── 7992 │ │ │ ├── 8050 │ │ │ ├── 8442 │ │ │ ├── 8453 │ │ │ ├── 8637 │ │ │ ├── 8682 │ │ │ ├── 8690 │ │ │ ├── 8785 │ │ │ ├── 9074 │ │ │ ├── 9109 │ │ │ ├── 9162 │ │ │ ├── 9261 │ │ │ ├── 9382 │ │ │ ├── 9653 │ │ │ ├── 9739 │ │ │ └── 9985 │ │ ├── files │ │ │ ├── 93 │ │ │ ├── 253 │ │ │ ├── 342 │ │ │ ├── 627 │ │ │ ├── 667 │ │ │ ├── 1024 │ │ │ ├── 1303 │ │ │ ├── 1829 │ │ │ ├── 2103 │ │ │ ├── 2133 │ │ │ ├── 2305 │ │ │ ├── 2487 │ │ │ ├── 2594 │ │ │ ├── 2686 │ │ │ ├── 3025 │ │ │ ├── 3144 │ │ │ ├── 3154 │ │ │ ├── 3378 │ │ │ ├── 3399 │ │ │ ├── 3467 │ │ │ ├── 3508 │ │ │ ├── 3622 │ │ │ ├── 3927 │ │ │ ├── 4035 │ │ │ ├── 4131 │ │ │ ├── 4146 │ │ │ ├── 4279 │ │ │ ├── 4462 │ │ │ ├── 4601 │ │ │ ├── 4755 │ │ │ ├── 4763 │ │ │ ├── 4767 │ │ │ ├── 4769 │ │ │ ├── 4865 │ │ │ ├── 5123 │ │ │ ├── 5221 │ │ │ ├── 5395 │ │ │ ├── 5454 │ │ │ ├── 5665 │ │ │ ├── 6002 │ │ │ ├── 6065 │ │ │ ├── 6085 │ │ │ ├── 6472 │ │ │ ├── 6590 │ │ │ ├── 6673 │ │ │ ├── 6836 │ │ │ ├── 7079 │ │ │ ├── 7089 │ │ │ ├── 7198 │ │ │ ├── 7233 │ │ │ ├── 7290 │ │ │ ├── 7370 │ │ │ ├── 7496 │ │ │ ├── 7539 │ │ │ ├── 7761 │ │ │ ├── 7859 │ │ │ ├── 7973 │ │ │ ├── 7992 │ │ │ ├── 8050 │ │ │ ├── 8442 │ │ │ ├── 8453 │ │ │ ├── 8637 │ │ │ ├── 8682 │ │ │ ├── 8690 │ │ │ ├── 8785 │ │ │ ├── 9074 │ │ │ ├── 9109 │ │ │ ├── 9162 │ │ │ ├── 9261 │ │ │ ├── 9382 │ │ │ ├── 9653 │ │ │ ├── 9739 │ │ │ └── 9985 │ │ ├── generate.js │ │ ├── main.py │ │ └── pymakr.conf │ └── project-with-dist_dir │ │ ├── device │ │ └── script.py │ │ └── pymakr.conf ├── runTest.js ├── suite │ ├── integration │ │ ├── busy-devices.test.js │ │ ├── devmode.adv.test.js │ │ ├── devmode.basic.test.js │ │ ├── file-management │ │ │ ├── _sample │ │ │ │ ├── folder │ │ │ │ │ └── large-file.py │ │ │ │ ├── ignoreme │ │ │ │ │ ├── ignored-file-1.md │ │ │ │ │ └── ignored-file-2.md │ │ │ │ ├── includeme │ │ │ │ │ ├── includeme-file-1.md │ │ │ │ │ └── includeme-file-2.md │ │ │ │ ├── main.py │ │ │ │ ├── pymakr.conf │ │ │ │ └── sample-file-1.md │ │ │ ├── file-management.test.js │ │ │ └── probs.config.js │ │ ├── file-system-provider.test.js │ │ ├── general-devices.test.js │ │ ├── global.d.ts │ │ ├── micropython.test.js │ │ ├── probs.config.js │ │ ├── projects.test.js │ │ ├── stress.test.js │ │ └── utils.js │ └── runIntegrationTests.js └── utils │ └── index.js ├── tsconfig.json └── types ├── store.js └── typedef.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 2018, 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-const-assign": "warn", 18 | "no-this-before-super": "warn", 19 | "no-undef": "warn", 20 | "no-unreachable": "warn", 21 | "no-unused-vars": "warn", 22 | "constructor-super": "warn", 23 | "valid-typeof": "warn" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[BUG]' 5 | labels: '' 6 | assignees: jakobrosenberg 7 | --- 8 | 9 | **IMPORTANT** 10 | 11 | If possible, please consider creating the issue from within Pymakr. This will create a new issue template with relevant context information. 12 | 13 | To do so, click the device's context menu (`...`) -> `Debug` -> `Show Device Summary` -> `Create an issue on Github` 14 | 15 | If you prefer to use this form instead, you can delete the IMPORTANT section. 16 | 17 | --- 18 | 19 | **Describe the bug** 20 | A clear and concise description of what the bug is. 21 | 22 | **To Reproduce** 23 | Steps to reproduce the behavior: 24 | 25 | 1. Go to '...' 26 | 2. Click on '....' 27 | 3. Scroll down to '....' 28 | 4. See error 29 | 30 | **Expected behavior** 31 | A clear and concise description of what you expected to happen. 32 | 33 | **Screenshots** 34 | If applicable, add screenshots to help explain your problem. 35 | 36 | **Desktop (please complete the following information):** 37 | 38 | - OS: [e.g. Win 11 X64] 39 | - Device [e.g. Expansionboard v3.1] 40 | - Pymakr [e.g. v2.12.3] 41 | 42 | **Additional context** 43 | Add any other context about the problem here. 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: jakobrosenberg 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/hw_unit.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore themand run tests using node 2 | # assumes that there are sufficient 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - next-jv 11 | - next-testing 12 | pull_request: 13 | branches: 14 | - next-staging 15 | 16 | jobs: 17 | build: 18 | runs-on: [self-hosted, micropython] 19 | steps: 20 | - uses: actions/checkout@v1 21 | # - run : 22 | # - nvm install 14.19.0 23 | # - nvm use 14.9.0 24 | # ? - run: npm install prebuild-install -g 25 | - run: npm ci 26 | - run: npm run test:unit 27 | - run: npm run test:integration 28 | -------------------------------------------------------------------------------- /.github/workflows/preparePackage.js: -------------------------------------------------------------------------------- 1 | const { writeFileSync } = require("fs"); 2 | 3 | const BRANCH = process.env.BRANCH_NAME; 4 | const isMaster = BRANCH === "master" || BRANCH === "main"; 5 | 6 | const previewPkg = { 7 | name: "pymakr-preview", 8 | displayName: "Pymakr - Preview", 9 | preview: true, 10 | }; 11 | 12 | const stablePkg = { 13 | name: "pymakr", 14 | displayName: "Pymakr", 15 | preview: false, 16 | }; 17 | 18 | const pkgUpdate = isMaster ? stablePkg : previewPkg; 19 | 20 | console.log(`Branch is ${BRANCH}`); 21 | console.log(`Patching package.json with`, pkgUpdate); 22 | 23 | const updatedPkg = { 24 | ...require("../../package.json"), 25 | ...pkgUpdate, 26 | }; 27 | 28 | writeFileSync("package.json", JSON.stringify(updatedPkg, null, 2) + "\r\n"); 29 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Deploy PyMakr 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - alpha 8 | - 2.x 9 | - next 10 | 11 | jobs: 12 | deploy: 13 | environment: vsce-publish 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | persist-credentials: false 21 | 22 | - name: Install Node.js 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 16 26 | 27 | - run: npm ci 28 | 29 | # convert package to preview if branch is not master or main 30 | - name: preparePackage 31 | run: npm run prepackage 32 | env: 33 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 34 | 35 | - name: Publish 36 | # for testing, add --dry-run --debug 37 | run: npx semantic-release 38 | env: 39 | VSCE_PAT: ${{ secrets.PUBLISHER_TOKEN2 }} 40 | # OVSX_PAT: ${{ secrets.OPEN_VSX }} 41 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | .vscode-test 3 | node_modules 4 | test/workspaces/** 5 | 6 | *.vsix 7 | test/temp 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.16.0 -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension 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 Pymakr", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "${workspaceFolder}/example/workspace/multi", 14 | "--disable-extensions", 15 | "--extensionDevelopmentPath=${workspaceFolder}" 16 | ] 17 | }, 18 | { 19 | "name": "Integration Tests", 20 | "type": "extensionHost", 21 | "request": "launch", 22 | "preLaunchTask": "test:setup:createIntegrationFixture", 23 | "args": [ 24 | "${workspaceFolder}/test/temp/integration-test-empty", 25 | "--disable-extensions", 26 | "--extensionDevelopmentPath=${workspaceFolder}", 27 | "--extensionTestsPath=${workspaceFolder}/test/suite/runIntegrationTests.js" 28 | ], 29 | "env": { "fixturePath": "${workspaceFolder}/test/temp/integration-test-empty", "fixtureName": "empty" } 30 | }, 31 | { 32 | "name": "Unit Tests", 33 | "request": "launch", 34 | "runtimeArgs": ["run", "test:unit"], 35 | "runtimeExecutable": "npm", 36 | "skipFiles": ["/**"], 37 | "type": "pwa-node" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.lintTask.enable": true, 3 | "editor.formatOnSave": true 4 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "test:setup:createIntegrationFixture", 6 | "type": "process", 7 | "command": "node", 8 | "presentation": { 9 | "echo": true, 10 | "reveal": "never", 11 | "focus": false, 12 | "panel": "shared", 13 | "showReuseMessage": true, 14 | "clear": false 15 | }, 16 | "args": [ 17 | "${workspaceFolder}/test/utils/index.js", 18 | "createFixture", 19 | "empty", 20 | "${workspaceFolder}/test/temp/integration-test-empty" 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .*/** 2 | **/test/** 3 | src/test/** 4 | vsc-extension-quickstart.md 5 | **/jsconfig.json 6 | **/*.map 7 | **/.eslintrc.json 8 | docs/** 9 | types/** 10 | 11 | #avoid including earlier local vsix builds 12 | pymakr-*.vsix 13 | pymakr-*.zip 14 | 15 | # example/** 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Citizen Code of Conduct 2 | 3 | ## 1. Purpose 4 | 5 | A primary goal of Pymakr Vsc is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. 8 | 9 | We invite all those who participate in Pymakr Vsc to help us create safe and positive experiences for everyone. 10 | 11 | ## 2. Open [Source/Culture/Tech] Citizenship 12 | 13 | A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. 14 | 15 | Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 16 | 17 | If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. 18 | 19 | ## 3. Expected Behavior 20 | 21 | The following behaviors are expected and requested of all community members: 22 | 23 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 24 | * Exercise consideration and respect in your speech and actions. 25 | * Attempt collaboration before conflict. 26 | * Refrain from demeaning, discriminatory, or harassing behavior and speech. 27 | * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. 28 | * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. 29 | 30 | ## 4. Unacceptable Behavior 31 | 32 | The following behaviors are considered harassment and are unacceptable within our community: 33 | 34 | * Violence, threats of violence or violent language directed against another person. 35 | * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. 36 | * Posting or displaying sexually explicit or violent material. 37 | * Posting or threatening to post other people's personally identifying information ("doxing"). 38 | * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 39 | * Inappropriate photography or recording. 40 | * Inappropriate physical contact. You should have someone's consent before touching them. 41 | * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. 42 | * Deliberate intimidation, stalking or following (online or in person). 43 | * Advocating for, or encouraging, any of the above behavior. 44 | * Sustained disruption of community events, including talks and presentations. 45 | 46 | ## 5. Weapons Policy 47 | 48 | No weapons will be allowed at Pymakr Vsc events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter. 49 | 50 | ## 6. Consequences of Unacceptable Behavior 51 | 52 | Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. 53 | 54 | Anyone asked to stop unacceptable behavior is expected to comply immediately. 55 | 56 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). 57 | 58 | ## 7. Reporting Guidelines 59 | 60 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. support@pycom.io. 61 | 62 | 63 | 64 | Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. 65 | 66 | ## 8. Addressing Grievances 67 | 68 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify pycom with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. 69 | 70 | 71 | 72 | ## 9. Scope 73 | 74 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business. 75 | 76 | This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. 77 | 78 | ## 10. Contact info 79 | 80 | support@pycom.io 81 | 82 | ## 11. License and attribution 83 | 84 | The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). 85 | 86 | Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). 87 | 88 | _Revision 2.3. Posted 6 March 2017._ 89 | 90 | _Revision 2.2. Posted 4 February 2016._ 91 | 92 | _Revision 2.1. Posted 23 June 2014._ 93 | 94 | _Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._ -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Contributing to Pymakr VSC 2 | 3 | Thank you for reading this document. We hope it will aid you in creating one or more PRs. 4 | 5 | #### Requirements 6 | 7 | - `VSCode`, 8 | - `NodeJS` - LTS version 16.14.2, 14.19.1 or later 9 | 10 | _Hint: For `Node`, we recommend using a version manager like `volta` or `nvm`_ 11 | - [volta](https://docs.volta.sh/guide/getting-started) 12 | - [nvm for windows](https://github.com/coreybutler/nvm-windows#readme) 13 | - [nvm for linux](https://github.com/nvm-sh/nvm#readme) 14 | - [NodeJS.org](https://nodejs.org/en/download/) 15 | ## Language used in this project 16 | The plugin is written in JavaScript with Type Hints via JSDoc. 17 | 18 | Due to this it is not needed (or possible) to use a TypeScript compiler to transpile the code before running as it's already Javascript. 19 | Typescript is only used to validate the types an by typedoc when generating the API docs. 20 | In this project `tsc` should vever be run , and the outDir in tsconfig.json is specified only to avoid distracting errors from the TypeScript compiler. 21 | 22 | Refs: 23 | - https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html 24 | - https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html 25 | ## Setup 26 | 27 | To work on Pymakr VSC, we first need to clone the repo and install its dependencies. 28 | 29 | 1. Clone project 30 | 31 | ``` 32 | git clone https://github.com/pycom/pymakr-vsc --branch next 33 | ``` 34 | 35 | 2. Install dependencies 36 | ``` 37 | cd pymakr-vsc 38 | npm install 39 | ``` 40 | 41 | --- 42 | 43 | ## Running the extension 44 | 45 | To run the extension, we need to open the project in VSCode and run the source code in debug mode. 46 | 47 | 1. Open the repo in VSCode 48 | 49 | _Hint: We can do this directly from the terminal by typing `code .`_ 50 | 51 | 2. Run/Debug the extension 52 | 53 | Click `Run and Debug` _(Ctrl+Shift+D)_ -> `Run Pymakr` 54 | 55 | 56 | 57 | _Hint: We can also start the task with F5. This shortcut always starts the last run task._ 58 | 59 | --- 60 | 61 | ## Testing the extension 62 | Hardware requirements: 63 | - The integration tests expect two MicroPython devices to be connected to the computer using a USB cable / serial port connection. 64 | 65 | Tests can be run directly from the terminal by typing 66 | - `npm run test:types`, to test type safety. 67 | - `npm run test:unit`, to run the unit tests. 68 | - `npm run test:integration`, to run the integration tests. 69 | - `npm run test`, to run all tests. 70 | 71 | To develop or debug tests use the same way to run/debug the extension. Instead of clicking Run PyMakr, we click the dropdown arrow and choose `Integration Tests` or `Unit Tests`. 72 | 73 | _Hint: F5 can be used to start the last executed task._ 74 | 75 | --- 76 | 77 | ## File structure 78 | 79 | | Path | Description | 80 | | ---------------- | ----------------------------------------------------------------------------------------------------- | 81 | | **example** | Example projects (may be deleted for a templating approach) | 82 | | **media** | Media assets | 83 | | **node_modules** | _Node dependencies. Fully managed by `npm`._ | 84 | | **src** | _Source files. See [API.md](https://github.com/pycom/pymakr-vsc/blob/next/docs/index.html) for info._ | 85 | | **templates** | Templates used for erasing device / creating new projects | 86 | | **test** | Integration tests | 87 | | **types** | Types used for auto completion | 88 | 89 | --- 90 | 91 | ## Create a local package [Optional] 92 | 93 | You may want to simply package the extension for testing on multiple systems without publishing them to the store. 94 | Extensions will always be packaged into a .vsix file. 95 | Here's how: 96 | `vsce package` 97 | 98 | This will package your extension into a .vsix file and place it in the current directory. It's possible to install .vsix files into Visual Studio Code. See [Installing Extensions](https://vscode-docs.readthedocs.io/docs/extensions/install-extension.md) for more details. 99 | 100 | ref: https://vscode-docs.readthedocs.io/en/latest/tools/vscecli/ 101 | 102 | ## Submitting a PR 103 | 104 | Please submit PRs to the `next-staging` branch. 105 | 106 | We encourage commit messages to follow [Angular's Commit Message Format](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format). Eg. `fix: some bug`, `feat: some new feature` etc. 107 | 108 | ## API 109 | 110 | For API documentation, please see [Pymakr API](https://htmlpreview.github.io/?https://raw.githubusercontent.com/pycom/pymakr-vsc/next/docs/classes/PyMakr.html). 111 | 112 | ## Release flow 113 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ### Pymakr says it can't access my device 4 | 5 | Please make sure that your device is not open in another program. If you have multiple instances of VSCode open, make sure only one is accessing the device at a time. 6 | 7 | ### My device doesn't have enough memory 8 | 9 | This can be solved by updating `chunkSize` and `chunkDelay` In settings under `pymakr > devices > configs`. `chunkSize: 256` has been reported to be a good starting point for some devices. In some cases you may also need to set `chunkDelay`. 10 | 11 | ### I accidentally saved a notification choice or clicked "Don't ask again" 12 | 13 | You can undo past notification choices in settings under `pymakr > Misc > Notifications`. 14 | 15 | ### My device is unresponsive 16 | 17 | There are a few ways to recover an unresponsive device. 18 | 19 | - Ctrl + c in terminal. Breaks the current script. 20 | - Ctrl + f in terminal. Hard resets device and enters safe boot. 21 | - `Stop script` or `Safe boot device` from the device context menu 22 | - Use the physical reset button on the device 23 | - If all else fails, you can reset/erase your Pycom device with `Pycom Firmware Update`. If you have a faulty `boot.py` or `main.py` script, you can solve this by checking the box `Erase during update`. 24 | 25 | ### Do I need Node? 26 | 27 | Node shouldn't be required, but there have been reports of issues being solved after installing Node. 28 | 29 | ### What is Pymakr - Preview 30 | 31 | Pymakr Preview is the prerelease branch of Pymakr. Features and bugfixes that pass integration tests, but could require further feedback, are released to Pymakr Preview first. If there are no issues or proposals, Pymakr Preview will then be merged into Pymakr. 32 | 33 | ### Can I change my version of Pymakr 34 | 35 | Yes, to install a specific version, go to the extensions tab, click the cogwheel next to `Pymakr` and select `Install Another Version`. 36 | 37 | ### I have an issue that's not described here 38 | 39 | Please open an issue on our [Github repo](https://github.com/pycom/pymakr-vsc). We usually respond within 24 hours. 40 | 41 | To get support for a specific device, please open the context menu (...) of the device. Then click `Debug` -> `Show device summary` -> `Create an issue on Github`. 42 | -------------------------------------------------------------------------------- /GET_STARTED.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | 1. First [download and install Visual Studio Code](https://code.visualstudio.com/). 4 | 2. Install the [Pymakr VSCode Extension](https://marketplace.visualstudio.com/items?itemName=pycom.Pymakr) 5 | 6 | _(We're installing the preview, but once the project reaches "stable" we'll, be using the regular extension.)_ 7 | 8 | 9 | 10 | 3. That's it! You've installed the Pymakr Extension for VSCode 11 | 12 | ##
13 | 14 | ## Creating a project 15 | 16 | Pymakr revolves around projects that can be uploaded to your devices. To create your first project click the `+` icon and select a folder for your project: 17 | 18 | ![](./media/readme/create-project.gif) 19 | 20 | _Note: If a project is created outside the current workspace(s), its folder will be mounted as a new workspace._ 21 | 22 | ##
23 | 24 | ## Creating a script 25 | 26 | Below we add a `main.py`. Once uploaded to a device, this file will run whenever the device is reset. 27 | 28 | ![](./media/readme/saving-a-file.gif) 29 | 30 | ##
31 | 32 | ## Upload the project to a device 33 | 34 | Once the project is ready to run, it needs to be uploaded to a device. 35 | 36 | ![](./media/readme/connect-device-and-sync-up.gif) 37 | 38 | ##
39 | 40 | ## Developer mode - Experimental 41 | 42 | ![](./media/readme/dev-mode.gif) 43 | 44 | To speed up development, you can put a project in `development mode`. 45 | 46 | In development mode, Pymakr automatically propagates local file changes to connected devices and then restarts the main script. 47 | 48 | Dev mode can be configured in `pymakr.json` 49 | 50 | ```json 51 | { 52 | "onUpdate": "restartScript" | "softRestartDevice" | "hardRestartDevice" 53 | } 54 | ``` 55 | 56 | **onUpdate:** Action to be called once file changes have been propagated. 57 | 58 | - **restartScript** Clears the `main.py` module as well as any changed modules. Then imports `boot.py` and `main.py`. 59 | - **softRestartDevice** Performs ctrl + d 60 | - **hardRestartDevice** Performs `machine.reset()` 61 | 62 | #### NOTE 63 | 64 | _`machine.sleep` and `machine.deepsleep` do not work in development since they stop the USB connection._ 65 | 66 | ##
67 | 68 | ## Hint: Organizing your setup 69 | 70 | Having to switch between different tabs can be cumbersome. To solve this, you can drag your devices and projects to the file explorer view. 71 | 72 | ![](./media/readme/move-view.gif) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pymakr 2 2 | 3 | --- 4 | 5 | ## Getting Started 6 | 7 | If you have a Pycom device, you can install this extension and you're ready to go. 8 | 9 | [GET_STARTED.md](./GET_STARTED.md) 10 | 11 | 12 | --- 13 | 14 | ## What's new 15 | 16 | Pymakr 2 was written completely from scratch. We now support multiple workspaces and devices plus a host of new features. 17 | 18 | [WHATS_NEW.md](./WHATS_NEW.md) 19 | 20 | 21 | --- 22 | 23 | ## Contribute 24 | 25 | Maintainability and scalability have been key focuses for Pymakr 2. To achieve this, the codebase has been written in fully typed JS. 26 | 27 | This provides full type completion and type safety without imposing TS on contributors. 28 | 29 | [CONTRIBUTE.md](./CONTRIBUTE.md). 30 | 31 | --- 32 | 33 | ## Support 34 | 35 | We hope you won't need it, but should you come across an issue, feel free to open an issue. 36 | 37 | To help us help you, please include: 38 | - a step-by-step walkthrough of what you did. 39 | - what you expected to happen. 40 | - what actually happened. -------------------------------------------------------------------------------- /RELEASE_NOTES_2.22.x.md: -------------------------------------------------------------------------------- 1 | # Release notes for Pymakr v2.22.x 2 | 3 | It's been a long time coming and we're delighted to finally announce the next release of our Pymakr 2. We've packed this release with tons of new features and bugfixes to make Pymakr 2 even faster, easier and more reliable than ever. 4 | 5 | We aim to make your experience with Pymakr as pleasant as possible Should you have any problems with our new release, please [open an issue](https://github.com/pycom/pymakr-vsc/issues/new/choose) on Github so that we can help. 6 | 7 | #
8 | 9 | ## Feature Highlights 10 | 11 | [Get Started Guide](#get-started-guide) 12 | 13 | [Developer Mode](#developer-mode) 14 | 15 | [Device Configuration](#device-configuration) 16 | 17 | [Better Notifications](#better-notifications) 18 | 19 | [Device Hover](#device-hover) 20 | 21 | [Open File on Device](#open-file-on-device) 22 | 23 | #
24 | 25 | ## Get Started Guide 26 | 27 | We've added a small walkthrough. To open it: 28 | 29 | 1. Click `ctrl/cmd + shift + p` 30 | 2. Type `walkthrough` 31 | 3. Select `Get Started: Open Walkthrough...` will show a list of walkthroughs. 32 | 4. Click `Pymakr 2 - Getting Started`. 33 | 34 | #
35 | 36 | ## Developer Mode 37 | 38 | Dev mode keeps your connected devices running and synchronized to your project folder in realtime. 39 | 40 | In dev mode, whenever a file in a project is saved, the changes are synced to the project's devices and the devices then restarted. 41 | 42 | Clicking the upload button while in dev mode will stop the current running script and upload files that are different from those on the device. Kinda like patching. This is different from the regular upload functionality, which erases all content on the device before uploading the entire project. 43 | 44 | _Note: Dev mode is limited to to the capabilities of the connected devices. Eg. it's not possible for Pymakr to communicate with a device in `machine.deepsleep` since the device's USB is disabled in this state. (A workaround for this may be possible - see `dev.simulateDeepSleep` in `pymakr.conf`)_ 45 | 46 | #
47 | 48 | ## Device Configuration 49 | 50 | It's now possible to configure individual devices in `settings->pymakr->devices->configs`. Here you can customize settings like `device.rootPath` and `device.adapterOptions` in case your device doesn't work with the defaults / auto detected values. 51 | 52 | #
53 | 54 | ## Better Notifications 55 | 56 | We've added more notifications to help explain the different events and actions in Pymakr. 57 | Power users who are ready to kick the support wheels can click `Don't show again` or save their choices when a popup is no longer wanted. 58 | 59 | _Popup choices can be undone in `Settings->Pymakr->Misc->Notifications_ 60 | 61 | #
62 | 63 | ## Device hover 64 | 65 | Hovering over a device now shows information about the device. 66 | 67 | #
68 | 69 | ## Open File on Device 70 | 71 | Right clicking a project file and selecting `Pymakr->Open file on device` will open the corresponding file on the device. Here you can verify the content and save changes as required. 72 | -------------------------------------------------------------------------------- /RELEASE_NOTES_2.24.x.md: -------------------------------------------------------------------------------- 1 | # Release notes for Pymakr v2.24.x 2 | 3 | This version of Pymakr brings a couple of features and a range of smaller fixes. 4 | 5 | #
6 | 7 | ## Feature Highlights 8 | 9 | [New Home View](#new-home-view) 10 | 11 | [Device Summary Page](#device-summary-page) 12 | 13 | #
14 | 15 | ## New Home View 16 | 17 | - A new view has been added to the Pymakr tab. This view provides quick access to settings and documentation. 18 | - The projects tree view has been moved to the Explorer tab by default. It can still be moved into other tabs, eg. Pymakr, but we feel this setting is a better default for most. 19 | 20 | If there's anything you'd like to see in the new home view, please let us know. 21 | 22 | #
23 | 24 | ## Device Summary Page 25 | 26 | The Device Summary Page provides a quick overview of connected devices, including a history of device actions. 27 | 28 | To speed up troubleshooting, the page includes a `Create an issue on Github` which will create an issue template based on your device and system. We hope you never have to use it, but look forward to helping you, if you do. 29 | 30 | #
31 | 32 | For a complete summary of changes, please see 33 | https://github.com/pycom/pymakr-vsc/releases 34 | -------------------------------------------------------------------------------- /WHATS_NEW.md: -------------------------------------------------------------------------------- 1 | # What's new in Pymakr 2 2 | 3 | ### New design 4 | 5 | Pymakr has gotten its own extension tab. Here projects and devices can be accessed. 6 | 7 | It's possible to drag these two views to the explorer tab for better accessibility (please see [Move PyMakr to the explorer tab](./GET_STARTED.md#move-pymakr-to-the-explorer-tab)). 8 | 9 | 10 | 11 | These views can be reorganized for improved accessibility. 12 |
13 | Reorganizing views 14 | 15 |
16 | 17 | 18 | ### Multiple connected devices 19 | 20 | Multiple devices can now be connected at the same time. 21 | 22 | 23 | ### Shared terminals for the same device (experimental) 24 | 25 | If multiple terminals are open for the same device, the last terminal to receive input will receive the device output. This is useful when handling large amounts of output. 26 | 27 | 28 | ### Projects 29 | 30 | Project management is finally here and among the highlights are: 31 | 32 | - Multiple workspaces support 33 | - Multiple projects in one workspace 34 | - Auto detection of projects 35 | - Multiple devices per project 36 | 37 | 38 | 39 | ### Device File explorer 40 | 41 | Mount your device inside VSCode and access it like a USB storage device. You can even save files directly to the device. 42 | 43 | 44 | ### Realtime updates 45 | It is now possible to see if a script is currently running on your device. 46 | 47 | 48 | ### Codebase (for contributors) 49 | Pymakr 2 was written completely from scratch and the codebase is now fully typed JS. On top of that we now have unit and integration tests. 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #0000FF; 3 | --dark-hl-0: #569CD6; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #001080; 7 | --dark-hl-2: #9CDCFE; 8 | --light-hl-3: #795E26; 9 | --dark-hl-3: #DCDCAA; 10 | --light-code-background: #F5F5F5; 11 | --dark-code-background: #1E1E1E; 12 | } 13 | 14 | @media (prefers-color-scheme: light) { :root { 15 | --hl-0: var(--light-hl-0); 16 | --hl-1: var(--light-hl-1); 17 | --hl-2: var(--light-hl-2); 18 | --hl-3: var(--light-hl-3); 19 | --code-background: var(--light-code-background); 20 | } } 21 | 22 | @media (prefers-color-scheme: dark) { :root { 23 | --hl-0: var(--dark-hl-0); 24 | --hl-1: var(--dark-hl-1); 25 | --hl-2: var(--dark-hl-2); 26 | --hl-3: var(--dark-hl-3); 27 | --code-background: var(--dark-code-background); 28 | } } 29 | 30 | body.light { 31 | --hl-0: var(--light-hl-0); 32 | --hl-1: var(--light-hl-1); 33 | --hl-2: var(--light-hl-2); 34 | --hl-3: var(--light-hl-3); 35 | --code-background: var(--light-code-background); 36 | } 37 | 38 | body.dark { 39 | --hl-0: var(--dark-hl-0); 40 | --hl-1: var(--dark-hl-1); 41 | --hl-2: var(--dark-hl-2); 42 | --hl-3: var(--dark-hl-3); 43 | --code-background: var(--dark-code-background); 44 | } 45 | 46 | .hl-0 { color: var(--hl-0); } 47 | .hl-1 { color: var(--hl-1); } 48 | .hl-2 { color: var(--hl-2); } 49 | .hl-3 { color: var(--hl-3); } 50 | pre, code { background: var(--code-background); } 51 | -------------------------------------------------------------------------------- /docs/assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/assets/icons.png -------------------------------------------------------------------------------- /docs/assets/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/assets/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/assets/widgets.png -------------------------------------------------------------------------------- /docs/assets/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/assets/widgets@2x.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | pymakr-preview
Options
All
  • Public
  • Public/Protected
  • All
Menu

pymakr-preview

2 | 3 |

Pymakr 2

4 |
5 |
6 | 7 | 8 |

Getting Started

9 |
10 |

If you have a Pycom device, you can install this extension and you're ready to go.

11 |

GET_STARTED.md

12 |
13 | 14 | 15 |

What's new

16 |
17 |

Pymakr 2 was written completely from scratch. We now support multiple workspaces and devices plus a host of new features.

18 |

WHATS_NEW.md

19 |
20 | 21 | 22 |

Contribute

23 |
24 |

Maintainability and scalability have been key focuses for Pymakr 2. To achieve this, the codebase has been written in fully typed JS.

25 |

This provides full type completion and type safety without imposing TS on contributors.

26 |

CONTRIBUTE.md.

27 |
28 | 29 | 30 |

Support

31 |
32 |

We hope you won't need it, but should you come across an issue, feel free to open an issue.

33 |

To help us help you, please include:

34 |
    35 |
  • a step-by-step walkthrough of what you did.
  • 36 |
  • what you expected to happen.
  • 37 |
  • what actually happened.
  • 38 |
39 |

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/media/contribute/release-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/contribute/release-flow.png -------------------------------------------------------------------------------- /docs/media/contribute/run-extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/contribute/run-extension.gif -------------------------------------------------------------------------------- /docs/media/dark/lightning-muted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/media/dark/lightning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/media/light/lightning-muted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/media/light/lightning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/media/pycom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/pycom.png -------------------------------------------------------------------------------- /docs/media/pycom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/media/pymakr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/pymakr2.png -------------------------------------------------------------------------------- /docs/media/readme/connect-device-and-sync-up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/connect-device-and-sync-up.gif -------------------------------------------------------------------------------- /docs/media/readme/connect-device.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/connect-device.gif -------------------------------------------------------------------------------- /docs/media/readme/create-project.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/create-project.gif -------------------------------------------------------------------------------- /docs/media/readme/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/design.png -------------------------------------------------------------------------------- /docs/media/readme/device-file-explorer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/device-file-explorer.gif -------------------------------------------------------------------------------- /docs/media/readme/insights.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/insights.gif -------------------------------------------------------------------------------- /docs/media/readme/install-pymakr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/install-pymakr.gif -------------------------------------------------------------------------------- /docs/media/readme/move-view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/move-view.gif -------------------------------------------------------------------------------- /docs/media/readme/multiple-connections.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/multiple-connections.gif -------------------------------------------------------------------------------- /docs/media/readme/open-terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/open-terminal.gif -------------------------------------------------------------------------------- /docs/media/readme/projects.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/projects.gif -------------------------------------------------------------------------------- /docs/media/readme/pycom-tab-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/pycom-tab-icon.png -------------------------------------------------------------------------------- /docs/media/readme/pymakr-a-z.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/pymakr-a-z.gif -------------------------------------------------------------------------------- /docs/media/readme/pymakr-add-device-to-file-explorer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/pymakr-add-device-to-file-explorer.gif -------------------------------------------------------------------------------- /docs/media/readme/pymakr-add-device-to-project.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/pymakr-add-device-to-project.gif -------------------------------------------------------------------------------- /docs/media/readme/pymakr-connect-a-device.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/pymakr-connect-a-device.gif -------------------------------------------------------------------------------- /docs/media/readme/pymakr-create-project.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/pymakr-create-project.gif -------------------------------------------------------------------------------- /docs/media/readme/pymakr-move-to-explorer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/pymakr-move-to-explorer.gif -------------------------------------------------------------------------------- /docs/media/readme/pymakr-move-view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/pymakr-move-view.gif -------------------------------------------------------------------------------- /docs/media/readme/saving-a-file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/saving-a-file.gif -------------------------------------------------------------------------------- /docs/media/readme/shared-terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/shared-terminal.gif -------------------------------------------------------------------------------- /docs/media/readme/typed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/docs/media/readme/typed.gif -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | pymakr-preview
Options
All
  • Public
  • Public/Protected
  • All
Menu

pymakr-preview

Generated using TypeDoc

-------------------------------------------------------------------------------- /example/workspace/multi/multi1/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name":"My multi project 1", 3 | "address": "192.168.4.1", 4 | "username": "micro", 5 | "password": "python", 6 | "sync_folder": "", 7 | "open_on_start": true, 8 | "safe_boot_on_upload": false, 9 | "py_ignore": [ 10 | ".vscode", 11 | ".gitignore", 12 | ".git", 13 | "env", 14 | "venv" 15 | ], 16 | "fast_upload": false 17 | } -------------------------------------------------------------------------------- /example/workspace/multi/multi2/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name":"My multi project 2", 3 | "address": "192.168.4.1", 4 | "username": "micro", 5 | "password": "python", 6 | "sync_folder": "", 7 | "open_on_start": true, 8 | "safe_boot_on_upload": false, 9 | "py_ignore": [ 10 | ".vscode", 11 | ".gitignore", 12 | ".git", 13 | "env", 14 | "venv" 15 | ], 16 | "fast_upload": false 17 | } -------------------------------------------------------------------------------- /example/workspace/plain/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "address": "192.168.4.1", 3 | "username": "micro", 4 | "password": "python", 5 | "sync_folder": "", 6 | "open_on_start": true, 7 | "safe_boot_on_upload": false, 8 | "py_ignore": [ 9 | ".vscode", 10 | ".gitignore", 11 | ".git", 12 | "env", 13 | "venv" 14 | ], 15 | "fast_upload": false 16 | } -------------------------------------------------------------------------------- /example/workspace/plain/workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "..\\multi" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | const { PyMakr } = require("./src/PyMakr"); 2 | 3 | /** @type {PyMakr} */ 4 | let pymakr; 5 | 6 | // this method is called when your extension is activated 7 | // your extension is activated the very first time the command is executed 8 | 9 | /** 10 | * @param {import('vscode').ExtensionContext} context 11 | */ 12 | function activate(context) { 13 | console.log('--- Starting Pymakr ---') 14 | pymakr = new PyMakr(context); 15 | } 16 | 17 | // this method is called when your extension is deactivated 18 | function deactivate() {} 19 | 20 | module.exports = { 21 | activate, 22 | deactivate, 23 | }; 24 | -------------------------------------------------------------------------------- /media/contribute/release-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/contribute/release-flow.png -------------------------------------------------------------------------------- /media/contribute/run-extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/contribute/run-extension.gif -------------------------------------------------------------------------------- /media/dark/dev-cross-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /media/dark/dev-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /media/dark/lightning-muted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /media/dark/lightning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /media/light/dev-cross-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /media/light/dev-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /media/light/lightning-muted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /media/light/lightning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /media/pycom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/pycom.png -------------------------------------------------------------------------------- /media/pycom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/pymakr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/pymakr2.png -------------------------------------------------------------------------------- /media/readme/connect-device-and-sync-up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/connect-device-and-sync-up.gif -------------------------------------------------------------------------------- /media/readme/connect-device.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/connect-device.gif -------------------------------------------------------------------------------- /media/readme/create-project.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/create-project.gif -------------------------------------------------------------------------------- /media/readme/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/design.png -------------------------------------------------------------------------------- /media/readme/dev-mode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/dev-mode.gif -------------------------------------------------------------------------------- /media/readme/device-file-explorer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/device-file-explorer.gif -------------------------------------------------------------------------------- /media/readme/insights.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/insights.gif -------------------------------------------------------------------------------- /media/readme/install-pymakr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/install-pymakr.gif -------------------------------------------------------------------------------- /media/readme/move-view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/move-view.gif -------------------------------------------------------------------------------- /media/readme/multiple-connections.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/multiple-connections.gif -------------------------------------------------------------------------------- /media/readme/open-terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/open-terminal.gif -------------------------------------------------------------------------------- /media/readme/projects.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/projects.gif -------------------------------------------------------------------------------- /media/readme/pycom-tab-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/pycom-tab-icon.png -------------------------------------------------------------------------------- /media/readme/pymakr-a-z.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/pymakr-a-z.gif -------------------------------------------------------------------------------- /media/readme/pymakr-add-device-to-file-explorer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/pymakr-add-device-to-file-explorer.gif -------------------------------------------------------------------------------- /media/readme/pymakr-add-device-to-project.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/pymakr-add-device-to-project.gif -------------------------------------------------------------------------------- /media/readme/pymakr-connect-a-device.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/pymakr-connect-a-device.gif -------------------------------------------------------------------------------- /media/readme/pymakr-create-project.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/pymakr-create-project.gif -------------------------------------------------------------------------------- /media/readme/pymakr-move-to-explorer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/pymakr-move-to-explorer.gif -------------------------------------------------------------------------------- /media/readme/pymakr-move-view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/pymakr-move-view.gif -------------------------------------------------------------------------------- /media/readme/saving-a-file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/saving-a-file.gif -------------------------------------------------------------------------------- /media/readme/shared-terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/shared-terminal.gif -------------------------------------------------------------------------------- /media/readme/typed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/media/readme/typed.gif -------------------------------------------------------------------------------- /pymakr.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "properties": { 6 | "dist_dir": { 7 | "description": "Folder to synchronize do device. Defaults to \".\"", 8 | "type": "string", 9 | "default": "." 10 | }, 11 | "name": { 12 | "description": "Name of the project. If empty, the project will be named by it's folder name.", 13 | "type": "string", 14 | "default": "My Pyboard Project" 15 | }, 16 | "ctrl_c_on_connect": { 17 | "description": "If true, executes a ctrl-c on connect to stop running programs", 18 | "type": "boolean" 19 | }, 20 | "py_ignore": { 21 | "description": "Comma separated list of files and folders to ignore when uploading (no wildcard or regular expressions supported", 22 | "type": "array" 23 | }, 24 | "safe_boot_on_upload": { 25 | "description": "Safe-boot before upload, Only works with firmware v1.16.0.b1 and up. Safe boots the board before uploading to prevent running out of memory while uploading. Especially useful on older boards with less memory, but adds about 2 seconds to the upload procedure'", 26 | "type": "boolean" 27 | }, 28 | "reboot_after_upload": { 29 | "description": "Reboots your pycom board after any upload or download action", 30 | "type": "boolean" 31 | }, 32 | "username": { 33 | "description": "Board username, only for telnet", 34 | "type": "string", 35 | "default": "micro", 36 | "deprecated": true, 37 | "deprecationMessage": "Deprecated. Board credentials are stored in a persistent VSCode session." 38 | }, 39 | "password": { 40 | "description": "Board password, only for telnet", 41 | "type": "string", 42 | "default": "python", 43 | "deprecated": true, 44 | "deprecationMessage": "Deprecated. Board credentials are stored in a persistent VSCode session." 45 | }, 46 | "address": { 47 | "description": "IP address or comport for your device", 48 | "type": "string", 49 | "deprecated": true, 50 | "deprecationMessage": "Deprecated. Board address is stored in a persistent VSCode session." 51 | }, 52 | "sync_folder": { 53 | "description": "Folder to synchronize. Empty to sync projects main folder", 54 | "type": "string", 55 | "deprecated": true, 56 | "deprecationMessage": "Deprecated. Please use the explorer context menu for upload/download of specific folders and files." 57 | }, 58 | "open_on_start": { 59 | "description": "Weather to open the terminal and connect to the board when starting Code", 60 | "type": "boolean", 61 | "deprecated": true, 62 | "deprecationMessage": "Deprecated. VSCode will automatically attempt to restore any terminals left open in your last session." 63 | }, 64 | "fast_upload": { 65 | "description": "Fast upload (experimental), Uses bigger batches and compresses larger (>4kb) files to make uploading faster. Only works on newer devices with 4mb of ram and firmware version >=1.19.x", 66 | "type": "boolean", 67 | "deprecated": true, 68 | "deprecationMessage": "Deprecated. This may or may not be reimplemented." 69 | }, 70 | "dev": { 71 | "description": "Options related to development mode in Pymakr", 72 | "type": "object", 73 | "properties": { 74 | "simulateDeepSleep": { 75 | "description": "Replaces deepsleep with\r\ntime.sleep(x)\nmachine.reset()", 76 | "type": "boolean", 77 | "default": false 78 | }, 79 | "uploadOnDevStart": { 80 | "description": "Uploads project to device when dev mode is started.", 81 | "type": "string", 82 | "default": "outOfSync", 83 | "anyOf": [ 84 | { 85 | "const": "always", 86 | "description": "Will upload the project to all connected devices." 87 | }, 88 | { 89 | "const": "never", 90 | "description": "Will not upload project to any devices." 91 | }, 92 | { 93 | "const": "outOfSync", 94 | "description": "Will upload the project to any connected device that is detected to be out of sync." 95 | } 96 | ] 97 | }, 98 | "onUpdate": { 99 | "description": "Action to run after file changes have been propagates", 100 | "type": "string", 101 | "default": "restartScript", 102 | "anyOf": [ 103 | { 104 | "const": "restartScript", 105 | "description": "Restarts boot.py (if changed) and main.py" 106 | }, 107 | { 108 | "const": "softRestartDevice", 109 | "description": "Sends ctrl + d to device" 110 | }, 111 | { 112 | "const": "hardRestartDevice", 113 | "description": "Runs machine.reset()" 114 | } 115 | ] 116 | } 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | const BRANCH = process.env.BRANCH_NAME; 2 | const isMaster = BRANCH === "master" || BRANCH === "main"; 3 | 4 | module.exports = { 5 | plugins: [ 6 | "@semantic-release/commit-analyzer", 7 | "@semantic-release/release-notes-generator", 8 | "@semantic-release/changelog", 9 | [ 10 | "semantic-release-vsce", 11 | { 12 | packageVsix: true, 13 | }, 14 | ], 15 | [ 16 | "@semantic-release/github", 17 | { 18 | assets: [ 19 | { 20 | path: "*.vsix", 21 | label: "Extension File", 22 | }, 23 | ], 24 | }, 25 | ], 26 | isMaster && "@semantic-release/git", 27 | ].filter(Boolean), 28 | }; 29 | -------------------------------------------------------------------------------- /src/Project.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const { dirname, basename, resolve } = require("path"); 3 | const { createStateObject } = require("./utils/storageObj"); 4 | const { Watcher } = require("./Watcher/Watcher"); 5 | 6 | const cfgDefaults = { dist_dir: "." }; 7 | 8 | class Project { 9 | /** 10 | * A project is any folder that contains a `pymakr.conf` file. 11 | * @param {import('vscode').Uri} configFile pymakr.conf location 12 | * @param {PyMakr} pymakr 13 | **/ 14 | constructor(configFile, pymakr) { 15 | this.pymakr = pymakr; 16 | this.configFile = configFile; 17 | this.folder = dirname(configFile.fsPath); 18 | this.watcher = new Watcher(this); 19 | 20 | this.deviceIds = createStateObject(pymakr.context.globalState, `project.${this.folder}.deviceIds`, []); 21 | this.updatedAt = createStateObject(pymakr.context.globalState, `project.${this.folder}.updatedAt`); 22 | 23 | this.log = pymakr.log.createChild("project: " + this.name); 24 | this.refresh(); 25 | } 26 | 27 | refresh() { 28 | this.name = this.config.name || basename(this.folder); 29 | } 30 | 31 | destroy() { 32 | this.watcher.destroy(); 33 | } 34 | 35 | get absoluteDistDir() { 36 | return resolve(this.folder, this.config.dist_dir); 37 | } 38 | 39 | /** @type {Partial} */ 40 | get config() { 41 | try { 42 | return { ...cfgDefaults, ...JSON.parse(readFileSync(this.configFile.fsPath, "utf-8")) }; 43 | } catch (err) { 44 | this.err = `Could not parse config: ${this.configFile.fsPath}`; 45 | this.pymakr.log.error("could not parse config:", this.configFile.fsPath); 46 | this.pymakr.notifier.notifications.couldNotParsePymakrConfig(this); 47 | return { ...cfgDefaults }; 48 | } 49 | } 50 | 51 | get devices() { 52 | return this.pymakr.devicesStore.getAllById(this.deviceIds.get()); 53 | } 54 | 55 | /** 56 | * Attaches devices to project 57 | * @param {import('./Device').Device[]} devices 58 | */ 59 | setDevices(devices) { 60 | const deviceIds = devices.map(({ id }) => id); 61 | this.deviceIds.set(deviceIds); 62 | this.pymakr.projectsProvider.refresh(); 63 | } 64 | } 65 | 66 | module.exports = { Project }; 67 | -------------------------------------------------------------------------------- /src/StatusBar.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { coerceDisposable } = require("./utils/misc"); 3 | 4 | class StatusBar { 5 | /** 6 | * @param {PyMakr} pymakr 7 | */ 8 | constructor(pymakr) { 9 | this.pymakr = pymakr; 10 | 11 | this.advancedMode = vscode.window.createStatusBarItem("advancedMode", 1, 7); 12 | 13 | this.subscriptions = [pymakr.config.subscribe(() => this.refresh())]; 14 | 15 | this.registerDisposables(); 16 | } 17 | 18 | registerDisposables() { 19 | this.pymakr.context.subscriptions.push(this.advancedMode, ...this.subscriptions.map(coerceDisposable)); 20 | } 21 | 22 | refresh() { 23 | this.advancedMode.text = vscode.workspace.getConfiguration("pymakr").get("advancedMode") ? "advanced" : "basic"; 24 | this.advancedMode.command = "pymakr.toggleAdvancedMode"; 25 | this.advancedMode.show(); 26 | } 27 | } 28 | 29 | module.exports = { StatusBar }; 30 | -------------------------------------------------------------------------------- /src/Watcher/DeviceManager.js: -------------------------------------------------------------------------------- 1 | const { scripts } = require("./scripts"); 2 | const { removeOverlappingInstructions, fakeDeepSleep } = require("./utils"); 3 | 4 | /** 5 | * @typedef {'change'|'create'|'delete'} FileAction 6 | * @typedef {{action: FileAction, file: string}} FileInstruction 7 | */ 8 | 9 | /** 10 | * Device Manager updates and restarts devices whenever the push method is called 11 | */ 12 | class DeviceManager { 13 | /** 14 | * @param {import('./Watcher').Watcher} watcher 15 | * @param {Device} device 16 | */ 17 | constructor(watcher, device) { 18 | this.watcher = watcher; 19 | this.device = device; 20 | this.project = this.watcher.project; 21 | this.pymakr = this.project.pymakr; 22 | this.log = watcher.log.createChild(device.name); 23 | this.bootPyIsOutdated = true; 24 | 25 | /** @type {FileInstruction[]} */ 26 | this.fileInstructions = []; 27 | 28 | this.isRunning = false; 29 | } 30 | 31 | get outOfSync() { 32 | return this.device.state.devUploadedAt.get() !== this.project.updatedAt.get(); 33 | } 34 | 35 | get shouldUploadOnDev() { 36 | const uploadWhen = this.project.config.dev?.uploadOnDevStart || "outOfSync"; 37 | return uploadWhen === "always" || (uploadWhen === "outOfSync" && this.outOfSync); 38 | } 39 | 40 | get transform() { 41 | return this.project.config.dev?.simulateDeepSleep && fakeDeepSleep; 42 | } 43 | 44 | async ensureBootPy() { 45 | const prependStr = [ 46 | "# EDIT BY PYMAKR DEV", 47 | "# The below script is used by Pymakr in dev mode", 48 | "# To remove it, disable dev mode and reupload the project.", 49 | "", 50 | scripts.importFakeMachine(), 51 | "", 52 | "# END OF EDIT BY PYMAKR DEV", 53 | "", 54 | ].join("\n"); 55 | 56 | // todo pseudo code 57 | const files = await this.device.adapter.listFiles(); 58 | if (!files.map((file) => file.filename).includes("boot.py")) 59 | await this.device.adapter.putFile("boot.py", Buffer.from(prependStr)); 60 | else { 61 | const content = (await this.device.adapter.getFile("boot.py")).toString(); 62 | 63 | if (!content.match(prependStr)) { 64 | this.device.adapter.putFile("boot.py", Buffer.from(prependStr + content)); 65 | } 66 | } 67 | } 68 | 69 | async uploadProjectIfNeeded() { 70 | if (!this.device.adapter.__proxyMeta.target.isConnected()) return; 71 | 72 | const answer = await this.device.pymakr.notifier.notifications.deviceIsOutOfSync(this); 73 | 74 | if (this.shouldUploadOnDev || answer === "upload") 75 | await this.device.pymakr.commands.uploadProject({ device: this.device, project: this.project }); 76 | } 77 | 78 | async uploadPymakrDev() { 79 | console.log("uploading pymakr dev"); 80 | return this.pymakr.commands.upload({ fsPath: __dirname + "/_pymakr_dev" }, this.device, "_pymakr_dev"); 81 | } 82 | 83 | /** 84 | * Send a change/create/delete file instruction to the device 85 | * @param {FileInstruction} fileInstruction 86 | */ 87 | push(fileInstruction) { 88 | this.fileInstructions.push(fileInstruction); 89 | return this.handleNewInstructions(); 90 | } 91 | 92 | async handleNewInstructions() { 93 | if (this.isRunning) { 94 | this.log.debug("already updating and restarting"); 95 | return; 96 | } 97 | this.isRunning = true; 98 | await this.updateAndRestart(); 99 | this.isRunning = false; 100 | this.log.debug("device/script restart completed"); 101 | 102 | // If new instructions were added while we restarted the device/script, let's rerun. 103 | if (this.fileInstructions.length) await this.handleNewInstructions(); 104 | } 105 | 106 | async installDevTools() { 107 | await this.device.runScript('print("[dev] uploading Pymakr devtools")'); 108 | await this.uploadPymakrDev(); 109 | await this.device.runScript('print("[dev] patching boot.dev")'); 110 | await this.ensureBootPy(); 111 | this.bootPyIsOutdated = false; 112 | } 113 | 114 | shouldInstallDevTools() { 115 | return this.project.config.dev?.simulateDeepSleep && this.bootPyIsOutdated; 116 | } 117 | 118 | /** 119 | * Stops the current running script, performs file changes and restarts the device or main script 120 | */ 121 | async updateAndRestart() { 122 | const modulesToDelete = ["main.py"]; 123 | 124 | this.log.debug("stop script"); 125 | await this.device.pymakr.commands.stopScript({ device: this.device }, 0); 126 | 127 | this.log.debug("run instructions"); 128 | // Loop to make sure we get all instructions before we reset. 129 | // New instructions could have been added while we executed previous ones 130 | while (this.fileInstructions.length) { 131 | const instructions = removeOverlappingInstructions([...this.fileInstructions]); 132 | this.fileInstructions.length = 0; 133 | for (const instruction of instructions) modulesToDelete.push(await this.runInstruction(instruction)); 134 | } 135 | 136 | const installDevTools = this.shouldInstallDevTools(); 137 | 138 | /** @type {'restartScript'|'softRestartDevice'|'hardRestartDevice'|'installDevToolsAndRestart'} */ 139 | const restartScript = installDevTools ? "hardRestartDevice" : this.project.config.dev?.onUpdate || "restartScript"; 140 | 141 | if (installDevTools) await this.installDevTools(); 142 | await this[restartScript](modulesToDelete); 143 | 144 | await new Promise((resolve) => setTimeout(resolve, 500)); 145 | } 146 | 147 | async hardRestartDevice(modulesToDelete) { 148 | this.log.log("hard restart device"); 149 | await this.device.runScript(`\rprint("[dev] \'${modulesToDelete[0]}\' changed. Restarting... ")\r`); 150 | await this.device.reset(); 151 | } 152 | 153 | async softRestartDevice(modulesToDelete) { 154 | this.log.log("soft restart device (ctrl+d)"); 155 | await this.device.runScript(`\rprint("[dev] \'${modulesToDelete[0]}\' changed. Restarting... ")\r`); 156 | this.device.adapter.sendData("\x04"); 157 | } 158 | 159 | restartScript(modulesToDelete) { 160 | this.log.log("restart script"); 161 | this.device.runUserScript(scripts.restart(modulesToDelete), { resolveBeforeResult: false }); 162 | } 163 | 164 | /** 165 | * @param {FileInstruction} fileInstruction 166 | */ 167 | async runInstruction({ file, action }) { 168 | this.log.debug("run instruction", { file, action }); 169 | const target = require("path").relative(this.project.absoluteDistDir, file).replace(/\\/g, "/"); 170 | if (target === "boot.py") { 171 | this.bootPyIsOutdated = true; 172 | } 173 | 174 | if (action === "delete") await this.device.remove(target); 175 | else await this.pymakr.commands.upload({ fsPath: file }, this.device, target, this.transform); 176 | 177 | return target; 178 | } 179 | } 180 | 181 | module.exports = { DeviceManager }; 182 | -------------------------------------------------------------------------------- /src/Watcher/Watcher.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { DeviceManager } = require("./DeviceManager"); 3 | 4 | class Watcher { 5 | /** 6 | * @param {Project} project 7 | */ 8 | constructor(project) { 9 | this.project = project; 10 | this.log = this.project.pymakr.log.createChild("watch-mode"); 11 | /** @type {DeviceManager[]} */ 12 | this.deviceManagers = []; 13 | /** @type {vscode.Disposable[]} */ 14 | this.disposables = []; 15 | this.active = false; 16 | this.registerFileWatchers(); 17 | } 18 | 19 | /** 20 | * @param {Device} device 21 | */ 22 | addDevice(device) { 23 | if (!this.deviceManagers.map((dm) => dm.device).includes(device)) 24 | this.deviceManagers.push(new DeviceManager(this, device)); 25 | 26 | if (this.deviceManagers.length) this.active = true; 27 | this.deviceManagers.find((dm) => dm.device === device)?.uploadProjectIfNeeded(); 28 | } 29 | 30 | removeDevice(device) { 31 | this.deviceManagers = [...this.deviceManagers.filter((dm) => dm.device !== device)]; 32 | if (!this.deviceManagers.length) this.active = false; 33 | } 34 | 35 | registerFileWatchers() { 36 | // this.deviceManagers = this.devices.map((device) => new DeviceManager(this, device)); 37 | this.watcher = vscode.workspace.createFileSystemWatcher(this.project.absoluteDistDir + "/**"); 38 | this.disposables = [ 39 | this.watcher, 40 | this.watcher.onDidChange(this.handleFileChange("change")), 41 | this.watcher.onDidCreate(this.handleFileChange("create")), 42 | this.watcher.onDidDelete(this.handleFileChange("delete")), 43 | ]; 44 | } 45 | 46 | destroy() { 47 | this.disposables.forEach((d) => d.dispose()); 48 | } 49 | 50 | /** 51 | * @param {'create'|'change'|'delete'} action 52 | * @returns {(file: vscode.Uri)=>void} 53 | */ 54 | handleFileChange(action) { 55 | return (file) => { 56 | const timestamp = new Date(); 57 | this.project.updatedAt.set(timestamp); 58 | if (this.active) 59 | this.deviceManagers 60 | .filter((dm) => dm.device.adapter.__proxyMeta.target.isConnected()) 61 | .forEach(async (manager) => { 62 | await manager.push({ file: file.fsPath, action }); 63 | manager.device.state.devUploadedAt.set(timestamp); 64 | }); 65 | }; 66 | } 67 | } 68 | 69 | module.exports = { Watcher }; 70 | -------------------------------------------------------------------------------- /src/Watcher/_pymakr_dev/fake_machine.py: -------------------------------------------------------------------------------- 1 | import time 2 | import machine 3 | 4 | 5 | def sleep(msTime): 6 | print('fake_machine.sleep start') 7 | time.sleep(msTime/1000) 8 | print('fake_machine.sleep end') 9 | # send an exit rawRepl command 10 | print('\x04\x04>') 11 | time.sleep(0.1) 12 | machine.reset() 13 | 14 | 15 | def deepSleep(msTime): 16 | print('fake_machine.deepSleep start') 17 | time.sleep(msTime/1000) 18 | print('fake_machine.deepSleep end') 19 | # send an exit rawRepl command 20 | print('\x04\x04>') 21 | time.sleep(0.1) 22 | machine.reset() 23 | -------------------------------------------------------------------------------- /src/Watcher/scripts.js: -------------------------------------------------------------------------------- 1 | const rmDoubleSlash = (str) => str.replace(/\/+/, "/"); 2 | 3 | const scripts = { 4 | importFakeMachine: () => 5 | [ 6 | 'print("[dev] Importing fake_machine")', 7 | "import sys", 8 | 'sys.path.append("/flash/_pymakr_dev")', 9 | "import fake_machine", 10 | ].join("\r\n"), 11 | checkForSysPath: (rootPath) => 12 | ["import sys", ` sys.path.index('${rmDoubleSlash(rootPath + "/_pymakr_dev")}')`].join("\r\n"), 13 | restart: (modulesToDelete) => 14 | [ 15 | "print('')", 16 | modulesToDelete[1] && `print("[dev] \'${modulesToDelete[1]}\' changed. Restarting... ")`, 17 | "for name in sys.modules:", 18 | ' if(hasattr(sys.modules[name], "__file__")):', 19 | ` if sys.modules[name].__file__ in ${JSON.stringify(modulesToDelete)}:`, 20 | ' print("[dev] Clear module: " + sys.modules[name].__file__)', 21 | " del sys.modules[name]", 22 | 23 | "try:", 24 | " print('[dev] Import boot.py')", 25 | " import boot", 26 | "except KeyboardInterrupt: pass", 27 | "except Exception as e:", 28 | " if(str(e) == \"no module named 'boot'\"):", 29 | " print('[dev] No boot.py found. Skipped.')", 30 | " else:", 31 | " print('[dev] Could not import boot.py.')", 32 | " raise e", 33 | 34 | "try:", 35 | " print('[dev] Import main.py')", 36 | " import main", 37 | "except KeyboardInterrupt: pass", 38 | "except Exception as e:", 39 | " if(str(e) == \"no module named 'main'\"):", 40 | " print('[dev] No main.py found. Skipped.')", 41 | " else:", 42 | " print('[dev] Could not import main.py.')", 43 | " raise e", 44 | "", 45 | ] 46 | .filter(Boolean) 47 | .join("\r\n"), 48 | }; 49 | 50 | module.exports = { scripts }; 51 | -------------------------------------------------------------------------------- /src/Watcher/spec/utils.spec.js: -------------------------------------------------------------------------------- 1 | const { removeOverlappingInstructions } = require("../utils"); 2 | 3 | test("removeOverlappingInstructions", () => { 4 | /** @type {{action: 'change'|'create'|'delete', file: string}[]} fileInstructions */ 5 | const instructions = [ 6 | { file: "src", action: "create" }, 7 | { file: "src/main.js", action: "create" }, 8 | { file: "src/main.js", action: "change" }, 9 | { file: "src/utils.js", action: "create" }, 10 | { file: "src/test", action: "create" }, 11 | { file: "src/test/test.js", action: "create" }, 12 | { file: "src/test/test.js", action: "change" }, 13 | { file: "src/main.js", action: "change" }, 14 | { file: "src/test", action: "delete" }, 15 | { file: "src/utils.js", action: "change" }, 16 | ]; 17 | const newInstructions = removeOverlappingInstructions(instructions); 18 | assert.deepEqual(newInstructions, [ 19 | { 20 | action: "create", 21 | file: "src", 22 | }, 23 | { 24 | action: "change", 25 | file: "src/main.js", 26 | }, 27 | { 28 | action: "change", 29 | file: "src/utils.js", 30 | }, 31 | ]); 32 | }); 33 | -------------------------------------------------------------------------------- /src/Watcher/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove redundant file operations. Eg. those that will be deleted or overwritten by later operations. 3 | * @param {{action: 'change'|'create'|'delete', file: string}[]} fileInstructions 4 | */ 5 | const removeOverlappingInstructions = (fileInstructions) => { 6 | let pool = [...fileInstructions]; 7 | const newInstructions = []; 8 | 9 | while (pool.length) { 10 | const lastInstruction = pool.pop(); 11 | const firstMatchingInstruction = pool.find(({ file }) => file === lastInstruction.file); 12 | const shouldRemoveSelf = lastInstruction.action === "delete" && firstMatchingInstruction?.action === "create"; 13 | pool = pool.filter(({ file }) => !file.startsWith(lastInstruction.file)); 14 | if (!shouldRemoveSelf) newInstructions.unshift(lastInstruction); 15 | } 16 | return newInstructions; 17 | }; 18 | 19 | const fakeDeepSleep = (id, str) => 20 | str 21 | .replace(/machine\.sleep/gm, "fake_machine.sleep") 22 | .replace(/machine\.deepSleep/gm, "fake_machine.sleep") 23 | .replace(/import machine/gm, "import fake_machine") 24 | .replace(/from machine import sleep/gm, "from fake_machine import sleep") 25 | .replace(/from machine import deepSleep/gm, "from fake_machine import deepSleep"); 26 | 27 | module.exports = { removeOverlappingInstructions, fakeDeepSleep }; 28 | -------------------------------------------------------------------------------- /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /src/providers/DevicesProvider.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const vscode = require("vscode"); 3 | 4 | /** @implements {vscode.TreeDataProvider} */ 5 | class DevicesProvider { 6 | /** 7 | * @param {PyMakr} PyMakr 8 | */ 9 | constructor(PyMakr) { 10 | PyMakr.devicesStore.subscribe(this.refresh.bind(this)); 11 | this.PyMakr = PyMakr; 12 | } 13 | 14 | _onDidChangeTreeData = new vscode.EventEmitter(); 15 | onDidChangeTreeData = this._onDidChangeTreeData.event; 16 | 17 | refresh() { 18 | this._onDidChangeTreeData.fire(); 19 | } 20 | 21 | getTreeItem(element) { 22 | return element; 23 | } 24 | 25 | getChildren(element) { 26 | if (element === undefined) { 27 | return this.PyMakr.devicesStore 28 | .get() 29 | .filter((device) => !device.isHidden) 30 | .map((device) => new DeviceTreeItem(device, this)); 31 | } 32 | return element.children; 33 | } 34 | } 35 | 36 | class DeviceTreeItem extends vscode.TreeItem { 37 | /** @type {DeviceTreeItem[]|undefined} */ 38 | children; 39 | 40 | /** 41 | * @param {import('../Device').Device} device 42 | * @param {DevicesProvider} tree 43 | */ 44 | constructor(device, tree) { 45 | super(device.displayName, vscode.TreeItemCollapsibleState.None); 46 | this.device = device; 47 | const state = device.connected.get() ? (device.busy.get() ? "busy" : "idle") : "offline"; 48 | this.contextValue = `${state}#none#device`; 49 | 50 | this.tooltip = device.pymakr.vscodeHelpers.deviceSummary(device); 51 | this.iconPath = device.pymakr.vscodeHelpers.deviceStateIcon(device); 52 | } 53 | } 54 | 55 | module.exports = { DevicesProvider, DeviceTreeItem }; 56 | -------------------------------------------------------------------------------- /src/providers/FilesystemProvider.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | 3 | /** @implements {vscode.FileSystemProvider} */ 4 | class FileSystemProvider { 5 | /** 6 | * 7 | * @param {PyMakr} pymakr 8 | */ 9 | constructor(pymakr) { 10 | this.pymakr = pymakr; 11 | this.log = pymakr.log.createChild("filesystemProvider"); 12 | 13 | /** @private */ 14 | this._emitter = new vscode.EventEmitter(); 15 | this.onDidChangeFile = this._emitter.event; 16 | } 17 | 18 | createDirectory(uri) { 19 | const device = this._getDevice(uri); 20 | device.adapter.mkdir(uri.path); 21 | } 22 | 23 | async delete(uri) { 24 | const device = this._getDevice(uri); 25 | try { 26 | const { isDir } = await device.adapter.statPath(uri.path); 27 | return await device.adapter.remove(uri.path, isDir); 28 | } catch (err) { 29 | if (err.message.endsWith(" ENOENT")) throw vscode.FileSystemError.FileNotFound(`Could not find '${uri.path}'.`); 30 | else this.pymakr.notifier.notifications.uncaughtError(err); 31 | } 32 | } 33 | 34 | watch(uri, options) { 35 | // todo? 36 | return new vscode.Disposable(() => {}); 37 | } 38 | 39 | async rename(oldUri, newUri) { 40 | const device = this._getDevice(oldUri); 41 | return device.adapter.rename(oldUri.path, newUri.path); 42 | } 43 | 44 | writeFile(uri, content) { 45 | const device = this._getDevice(uri); 46 | device.adapter.putFile(uri.path, content); 47 | } 48 | 49 | /** 50 | * @param {vscode.Uri} uri 51 | */ 52 | async readFile(uri) { 53 | this.log.debug("read file", uri); 54 | const device = this._getDevice(uri); 55 | return device.adapter.getFile(uri.path); 56 | } 57 | 58 | /** 59 | * @private 60 | * @param {vscode.Uri} uri 61 | */ 62 | _getDevice(uri) { 63 | const authority = uri.authority.replace(/%2F/g, "/"); 64 | return this.pymakr.devicesStore.getByProtocolAndAddress(uri.scheme, authority); 65 | } 66 | 67 | /** 68 | * @param {vscode.Uri} uri 69 | * @returns {Promise} 70 | */ 71 | async stat(uri) { 72 | const device = this._getDevice(uri); 73 | 74 | try { 75 | this.log.debug("stat file", uri); 76 | const stat = await device.adapter.statPath(uri.path); 77 | this.log.debug("stat for", uri.path, stat); 78 | 79 | if (!stat.exists) throw vscode.FileSystemError.FileNotFound(uri); 80 | return { 81 | ctime: Date.now(), //todo (fork or pr for micropython-ctl) 82 | mtime: Date.now(), //todo (fork or pr for micropython-ctl) 83 | size: stat.size, 84 | type: stat.isDir ? vscode.FileType.Directory : vscode.FileType.File, 85 | }; 86 | } catch (err) { 87 | if (err.code === "FileNotFound") throw vscode.FileSystemError.FileNotFound(uri); 88 | else this.log.error("couldn't stat file:", uri.path, err); 89 | } 90 | } 91 | 92 | /** 93 | * @param {vscode.Uri} uri 94 | */ 95 | async readDirectory(uri) { 96 | this.log.debug("read dir", uri.path); 97 | const device = this._getDevice(uri); 98 | const path = uri.path.startsWith(device.config.rootPath) ? uri.path : device.config.rootPath; 99 | 100 | try { 101 | const files = await device.adapter.listFiles(path, { recursive: false }); 102 | this.log.debug(`found files in "${uri.path}"`, files); 103 | /** @type { [string, vscode.FileType][]} */ 104 | const content = files.map((f) => [ 105 | f.filename.replace(/^\/.+\//, ""), // get basename instead of absolute path 106 | f.isDir ? vscode.FileType.Directory : vscode.FileType.File, 107 | ]); 108 | 109 | return content; 110 | } catch (err) { 111 | this.log.error("couldn't read dir", path, err); 112 | } 113 | } 114 | } 115 | 116 | module.exports = { FileSystemProvider }; 117 | -------------------------------------------------------------------------------- /src/providers/HomeProvider.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | 3 | /** @implements {vscode.WebviewViewProvider} */ 4 | class HomeProvider { 5 | /** @param {PyMakr} pymakr */ 6 | constructor(pymakr) { 7 | this.pymakr = pymakr; 8 | } 9 | 10 | toolkitUri(webviewView) { 11 | const toolkitPath = ["node_modules", "@vscode", "webview-ui-toolkit", "dist", "toolkit.js"]; 12 | const localPath = vscode.Uri.joinPath(this.pymakr.context.extensionUri, ...toolkitPath); 13 | return webviewView.webview.asWebviewUri(localPath); 14 | } 15 | 16 | /** 17 | * @param {vscode.WebviewView} webviewView 18 | * @param {vscode.WebviewViewResolveContext} context 19 | * @param {vscode.CancellationToken} _token 20 | */ 21 | resolveWebviewView(webviewView, context, _token) { 22 | const toolkitUri = this.toolkitUri(webviewView); 23 | const releaseNotes = ["RELEASE_NOTES_2.22.x.md", "RELEASE_NOTES_2.24.x.md"].map((path) => ({ 24 | name: path, 25 | url: "command:pymakr.showMarkdownDocument?" + encodeURIComponent(JSON.stringify([path])), 26 | })); 27 | 28 | webviewView.webview.onDidReceiveMessage((message) => { 29 | vscode.commands.executeCommand(message.command, message.args); 30 | }); 31 | 32 | webviewView.webview.options = { 33 | enableScripts: true, 34 | enableCommandUris: true, 35 | }; 36 | webviewView.webview.html = ` 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 51 | 52 | 53 | 54 |

Pymakr

55 |

Interact with your Pycom devices.

56 |

57 | For project management, please see 58 | 59 | PROJECTS 60 | 61 | In the 62 | Explorer 63 | view. 64 |

65 | 66 |
67 | 68 |

Quick Access

69 |

70 | Shortcuts to the less obvious parts of Pymakr. 71 |

72 | 73 |

74 | Interactive walkthrough 75 | Settings 76 |

77 | 78 |
79 | 80 |

Docs

81 |

82 | Documentation to get you started with Pymakr. 83 |

84 | 85 |

86 | Getting Started 87 | FAQ 88 |

89 | 90 |
91 | 92 |

Support

93 |

94 | To get help, please refer to the FAQ or open an issue on 95 | 96 | Github 97 | . 98 | To get support for a specific device, please open the context menu (...) of the device. Then click 99 | Debug -> Show device summary -> Create an issue on Github. 100 |

101 | 102 |
103 | 104 |

Release Notes

105 |

106 | ${releaseNotes.map((link) => `${link.name}`).join("
")} 107 |

108 | 109 | 110 | 111 | 112 | `; 113 | } 114 | } 115 | 116 | module.exports = { HomeProvider }; 117 | -------------------------------------------------------------------------------- /src/providers/ProjectsProvider.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | /** @implements {vscode.TreeDataProvider} */ 6 | class ProjectsProvider { 7 | /** 8 | * @param {PyMakr} PyMakr 9 | */ 10 | constructor(PyMakr) { 11 | PyMakr.projectsStore.subscribe(this.refresh.bind(this)); 12 | this.PyMakr = PyMakr; 13 | } 14 | 15 | _onDidChangeTreeData = new vscode.EventEmitter(); 16 | onDidChangeTreeData = this._onDidChangeTreeData.event; 17 | 18 | refresh() { 19 | this._onDidChangeTreeData.fire(); 20 | } 21 | 22 | getTreeItem(element) { 23 | return element; 24 | } 25 | 26 | getChildren(element) { 27 | if (element === undefined) { 28 | return this.PyMakr.projectsStore.get().map((project) => new ProjectTreeItem(project, this)); 29 | } 30 | return element.children; 31 | } 32 | } 33 | 34 | class ProjectTreeItem extends vscode.TreeItem { 35 | /** 36 | * @param {import('../Project').Project} project 37 | * @param {ProjectsProvider} tree 38 | */ 39 | constructor(project, tree) { 40 | super(project.name, 2); 41 | this.project = project; 42 | const children = project.devices 43 | .filter((device) => !device.config.hidden) 44 | .map((device) => new ProjectDeviceTreeItem(device, project, tree)); 45 | 46 | this.children = children.length ? children : [new ProjectEmptyTreeItem(project)]; 47 | 48 | if (project.watcher.active) 49 | this.iconPath = { 50 | dark: path.join(__dirname + "..", "..", "..", "media", "dark", "dev-dark.svg"), 51 | light: path.join(__dirname + "..", "..", "..", "media", "light", "dev-light.svg"), 52 | }; 53 | 54 | const hasOnlineChild = project.devices.find((device) => device.adapter.__proxyMeta.target.isConnected()); 55 | const hasOfflineChild = project.devices.find((device) => !device.adapter.__proxyMeta.target.isConnected()); 56 | const onlineStatus = 57 | hasOnlineChild && hasOfflineChild ? "mixed" : hasOnlineChild ? "online" : hasOfflineChild ? "offline" : "no"; 58 | 59 | const hasBusyChild = project.devices.find((device) => device.busy.get()); 60 | const hasIdleChild = project.devices.find((device) => !device.busy.get()); 61 | const busyStatus = hasBusyChild && hasIdleChild ? "mixed" : hasBusyChild ? "busy" : hasIdleChild ? "idle" : "no"; 62 | 63 | const dev = project.watcher.active ? "dev" : "no-dev"; 64 | 65 | this.contextValue = `${busyStatus}#${onlineStatus}Children#${dev}#project`; 66 | } 67 | } 68 | 69 | class ProjectEmptyTreeItem extends vscode.TreeItem { 70 | /** 71 | * @param {Project} project 72 | */ 73 | constructor(project) { 74 | super("ADD DEVICES", vscode.TreeItemCollapsibleState.None); 75 | this.command = { 76 | title: "Select Devices", 77 | tooltip: "Add devices to your project", 78 | command: "pymakr.selectDevicesForProjectPrompt", 79 | arguments: [project], 80 | }; 81 | } 82 | } 83 | 84 | class ProjectDeviceTreeItem extends vscode.TreeItem { 85 | /** 86 | * 87 | * @param {import('../Device').Device} device 88 | * @param {import('../Project').Project} project 89 | * @param {ProjectsProvider} tree 90 | */ 91 | constructor(device, project, tree) { 92 | super(device.displayName, vscode.TreeItemCollapsibleState.None); 93 | this.project = project; 94 | this.device = device; 95 | 96 | const watched = project.watcher.deviceManagers.find((dm) => dm.device === device); 97 | const isDev = watched && device.adapter.__proxyMeta.target.isConnected(); 98 | const devState = isDev ? "dev" : "no-dev"; 99 | 100 | if (isDev) this.label = "[DEV] " + device.displayName; 101 | 102 | const state = device.connected.get() ? (device.busy.get() ? "busy" : "idle") : "offline"; 103 | this.contextValue = `${devState}#${state}#project#device`; 104 | 105 | this.tooltip = device.pymakr.vscodeHelpers.deviceSummary(device); 106 | this.iconPath = device.pymakr.vscodeHelpers.deviceStateIcon(device); 107 | } 108 | } 109 | 110 | module.exports = { ProjectsProvider, ProjectTreeItem, ProjectDeviceTreeItem }; 111 | -------------------------------------------------------------------------------- /src/providers/TextDocumentProvider.js: -------------------------------------------------------------------------------- 1 | const { SerialPort } = require("serialport"); 2 | const vscode = require("vscode"); 3 | const { timestamp } = require("../utils/createLogger"); 4 | const { arraysToMarkdownTable, adapterHistoryTable } = require("../utils/formatters"); 5 | const { serializeKeyValuePairs } = require("../utils/misc"); 6 | const os = require("os"); 7 | 8 | /** 9 | * @typedef {Object} subProvider 10 | * @prop {string|RegExp} match 11 | * @prop {(...matches:string[]) => Promise|string} body 12 | */ 13 | 14 | /** 15 | * @implements {vscode.TextDocumentContentProvider} 16 | */ 17 | class TextDocumentProvider { 18 | /** 19 | * @param {PyMakr} pymakr 20 | */ 21 | constructor(pymakr) { 22 | this.pymakr = pymakr; 23 | } 24 | 25 | provideTextDocumentContent(uri) { 26 | const path = uri.path || uri; 27 | for (const subProvider of this.subProviders) { 28 | const matches = path.match(subProvider.match); 29 | if (matches) return subProvider.body(...matches); 30 | } 31 | 32 | throw new Error("could not find pymakr document: " + uri.toString()); 33 | } 34 | 35 | onDidChangeEmitter = new vscode.EventEmitter(); 36 | onDidChange = this.onDidChangeEmitter.event; 37 | 38 | /** 39 | * @type {subProvider[]} 40 | */ 41 | subProviders = [ 42 | { 43 | match: "available devices", 44 | body: async () => { 45 | const devices = await SerialPort.list(); 46 | const devicesStr = devices.map((e) => serializeKeyValuePairs(e)).join("\r\n\r\n"); 47 | return devicesStr; 48 | }, 49 | }, 50 | { 51 | match: /device summary - (.+)/, 52 | body: (_all, path) => { 53 | const GithubMaxLengthUri = 8170; //8182 to be exact 54 | const device = this.pymakr.devicesStore.get().find((device) => device.raw.path === path); 55 | const config = { ...device.config, password: "***", username: "***" }; 56 | const configTable = arraysToMarkdownTable([["Config", ""], ...Object.entries(config || {})]); 57 | const deviceTable = arraysToMarkdownTable([["Device", ""], ...Object.entries(device.raw || {})]); 58 | const systemTable = arraysToMarkdownTable([["System", ""], ...Object.entries(device.info || {})]); 59 | const hostTable = arraysToMarkdownTable([ 60 | ["Host", ""], 61 | ...Object.entries({ 62 | OS: process.platform + " - " + os.arch(), 63 | Pymakr: vscode.extensions.getExtension("Pycom.pymakr")?.packageJSON.version, 64 | "Pymakr-Preview": vscode.extensions.getExtension("Pycom.pymakr-preview")?.packageJSON.version, 65 | VSCode: vscode.version, 66 | }), 67 | ]); 68 | 69 | const historyTable = arraysToMarkdownTable(adapterHistoryTable(device)); 70 | const body = [ 71 | configTable, 72 | deviceTable, 73 | systemTable, 74 | hostTable, 75 | `## Device History at ${timestamp(new Date())}`, 76 | historyTable, 77 | ].join("\r\n\r\n"); 78 | 79 | const intro = encodeURI( 80 | "What I did: ***\n\nWhat I expected to happen: ***\n\nWhat actually happened: ***\n\nAdditional info: ***\n\n\n\n" 81 | ); 82 | const truncateMsg = encodeURIComponent("\n\n\n#### History was truncated"); 83 | let url = `https://github.com/pycom/pymakr-vsc/issues/new?body=${intro + encodeURIComponent(body)}`; 84 | if (url.length > GithubMaxLengthUri) url = url.slice(0, GithubMaxLengthUri - truncateMsg.length) + truncateMsg; 85 | 86 | const createIssueButton = [ 87 | "# Create issue on Github", 88 | `### !WARNING ABOUT SENSITIVE DATA!`, 89 | `**Your device history may contain credentials or other sensitive data.**`, 90 | `After clicking the link below, please review the data before submitting it.`, 91 | `Pycom is not liable for credentials or other sensitive data posted to Github!`, 92 | `### [I understand - Create an issue on Github ](${url})`, 93 | ].join("\r\n\r\n"); 94 | 95 | return [body, "#
", createIssueButton].join("\r\n\r\n"); 96 | }, 97 | }, 98 | ]; 99 | } 100 | 101 | module.exports = { TextDocumentProvider }; 102 | -------------------------------------------------------------------------------- /src/stores/devices.js: -------------------------------------------------------------------------------- 1 | const { Device } = require("../Device"); 2 | const { coerceArray } = require("../utils/misc"); 3 | const { writable } = require("../utils/store"); 4 | const { SerialPort } = require("serialport"); 5 | 6 | /** 7 | * converts serial.PortInfo to { address, name, protocol, raw } 8 | * @param {DeviceInputRaw} raw 9 | * @returns {DeviceInput} 10 | */ 11 | const rawSerialToDeviceInput = (raw) => ({ 12 | address: raw.path, 13 | name: raw.friendlyName || raw.path.replace(/^\/dev\//, ""), 14 | protocol: "serial", 15 | id: raw.serialNumber, 16 | raw, 17 | }); 18 | 19 | /** 20 | * @param {DeviceInput} device 21 | * @returns {string} 22 | */ 23 | const createId = (device) => `${device.protocol}://${device.address}`; 24 | 25 | /** 26 | * @param {PyMakr} pymakr 27 | */ 28 | const createDevicesStore = (pymakr) => { 29 | /** @type {Writable} */ 30 | const store = writable([]); 31 | 32 | /** 33 | * @param {DeviceInput|DeviceInput[]} deviceInput 34 | */ 35 | const upsert = (deviceInput) => { 36 | const deviceInputs = coerceArray(deviceInput); 37 | const newDeviceInputs = deviceInputs.filter( 38 | (input) => !store.get().find((device) => createId(device) === createId(input)) 39 | ); 40 | const newDevices = newDeviceInputs.map((input) => new Device(pymakr, input)); 41 | if (newDevices.length) store.update((devices) => [...devices, ...newDevices]); 42 | }; 43 | 44 | /** 45 | * @param {Device} device 46 | */ 47 | const remove = (device) => { 48 | store.update((devices) => devices.filter((_device) => _device !== device)); 49 | }; 50 | 51 | /** 52 | * @param {string} protocol 53 | * @param {string} address 54 | */ 55 | const getByProtocolAndAddress = (protocol, address) => 56 | store 57 | .get() 58 | .find( 59 | (_device) => _device.protocol === protocol && _device.address.toLowerCase() === address.toLocaleLowerCase() 60 | ); 61 | 62 | const registerUSBDevices = async () => { 63 | pymakr.log.trace("register USB devices"); 64 | const rawSerials = await SerialPort.list(); 65 | const deviceInputs = rawSerials.map(rawSerialToDeviceInput); 66 | const inputIds = deviceInputs.map((deviceInput) => deviceInput.id); 67 | upsert(deviceInputs); 68 | 69 | store.get().forEach((device) => { 70 | const _lastOnlineState = device.online.get(); 71 | 72 | // update online status 73 | device.online.set(inputIds.includes(device.id)); 74 | 75 | // if status has changed, update connection 76 | if (device.online.get() && !device.config.hidden && !_lastOnlineState) device.refreshConnection(); 77 | }); 78 | }; 79 | 80 | /** @type {NodeJS.Timer} */ 81 | let watchIntervalHandle; 82 | const watchUSBDevices = () => { 83 | watchIntervalHandle = setInterval(registerUSBDevices, 500); 84 | return () => clearInterval(watchIntervalHandle); 85 | }; 86 | 87 | /** 88 | * @param {string|string[]} ids 89 | * @returns {Device[]} 90 | */ 91 | const getAllById = (ids) => store.get().filter((_device) => coerceArray(ids).includes(_device.id)); 92 | 93 | return { 94 | ...store, 95 | getByProtocolAndAddress, 96 | getAllById, 97 | upsert, 98 | remove, 99 | registerUSBDevices, 100 | watchUSBDevices, 101 | stopWatchingUSBDevices: () => clearInterval(watchIntervalHandle), 102 | }; 103 | }; 104 | 105 | module.exports = { createDevicesStore }; 106 | -------------------------------------------------------------------------------- /src/stores/projects.js: -------------------------------------------------------------------------------- 1 | const { workspace } = require("vscode"); 2 | const { Project } = require("../Project"); 3 | const { writable } = require("../utils/store"); 4 | 5 | /** 6 | * @param {import('../PyMakr').PyMakr} pymakr 7 | * @returns {(configFile: import('vscode').Uri)=>Project} 8 | */ 9 | const convertToProject = (pymakr) => (configFile) => new Project(configFile, pymakr); 10 | 11 | /** @param {Project[]} projects */ 12 | const isNotIn = (projects) => (configFile) => !projects.find((p) => p.configFile.path === configFile.path); 13 | 14 | /** 15 | * @param {Project} p1 16 | * @param {Project} p2 17 | */ 18 | const byName = (p1, p2) => (p1.name < p2.name ? 1 : p1.name > p2.name ? -1 : 0); 19 | 20 | /** 21 | * @param {import('vscode').Uri[]} configFiles 22 | * @returns {(project:Project) => Boolean} 23 | */ 24 | const hasConfigFile = (configFiles) => (project) => configFiles.map((cf) => cf.path).includes(project.configFile.path); 25 | 26 | /** 27 | * @param {import('vscode').Uri[]} configFiles 28 | * @returns {(project:Project) => Boolean} 29 | */ 30 | const hasNoConfigFile = (configFiles) => (project) => 31 | !configFiles.map((cfg) => cfg.path).includes(project.configFile.path); 32 | 33 | /** @param {Project} project */ 34 | const destroy = (project) => project.destroy(); 35 | 36 | /** 37 | * @param {PyMakr} pymakr 38 | */ 39 | const createProjectsStore = (pymakr) => { 40 | /** @type {Writable} */ 41 | const store = writable([]); 42 | /** Rescan for projects in workspace. */ 43 | const refresh = async () => { 44 | pymakr.log.debug("Refreshing projects store..."); 45 | const configFiles = await workspace.findFiles("**/pymakr.conf"); 46 | store.get().filter(hasNoConfigFile(configFiles)).forEach(destroy); 47 | store.update((oldProjects) => 48 | [ 49 | ...oldProjects.filter(hasConfigFile(configFiles)), 50 | ...configFiles.filter(isNotIn(oldProjects)).map(convertToProject(pymakr)), 51 | ].sort(byName) 52 | ); 53 | store.get().forEach((p) => p.refresh()); 54 | pymakr.log.debug("Refreshing projects store. Complete!"); 55 | }; 56 | 57 | const watcher = workspace.createFileSystemWatcher("**/pymakr.conf"); 58 | // Vscode doesn't detect when nested a pymaker.conf is deleted, 59 | // so we trigger a refresh on every file/folder deletion 60 | // if ever needed, we can throttle the function 61 | const watchAll = workspace.createFileSystemWatcher("**"); 62 | 63 | const disposables = [ 64 | watcher.onDidChange(refresh), 65 | watcher.onDidCreate(refresh), 66 | watchAll.onDidDelete(refresh), 67 | workspace.onDidChangeWorkspaceFolders(refresh), 68 | ]; 69 | 70 | pymakr.context.subscriptions.push(...disposables); 71 | 72 | return { ...store, refresh }; 73 | }; 74 | 75 | module.exports = { createProjectsStore }; 76 | -------------------------------------------------------------------------------- /src/stores/terminals.js: -------------------------------------------------------------------------------- 1 | const { Terminal } = require("../terminal/Terminal"); 2 | const { writable } = require("../utils/store"); 3 | const vscode = require("vscode"); 4 | 5 | /** 6 | * @param {PyMakr} pymakr 7 | */ 8 | const createTerminalsStore = (pymakr) => { 9 | /** @type {Writable} */ 10 | const store = writable([]); 11 | 12 | /** 13 | * Creates a new terminal for the provided device 14 | * @param {Device} device 15 | */ 16 | const create = (device) => store.update((terms) => [...terms, new Terminal(pymakr, device)]); 17 | /** 18 | * Removes the provided terminal 19 | * @param {Terminal} term 20 | */ 21 | const remove = (term) => store.update((terms) => terms.filter((_term) => _term !== term)); 22 | 23 | const disposable = vscode.window.onDidCloseTerminal((term) => { 24 | store.update((terminals) => { 25 | const terminal = terminals.find((terminal) => terminal.term === term); 26 | return terminals.filter((_terminal) => _terminal !== terminal); 27 | }); 28 | }); 29 | pymakr.context.subscriptions.push(disposable); 30 | 31 | return { ...store, create, remove }; 32 | }; 33 | 34 | module.exports = { createTerminalsStore }; 35 | -------------------------------------------------------------------------------- /src/terminal/Server.js: -------------------------------------------------------------------------------- 1 | const { createServer } = require("net"); 2 | const vscode = require("vscode"); 3 | 4 | /** 5 | * This is the terminal server. It's mounted as a singleton at `pymakr.server`. 6 | * It handles all client terminal connections. 7 | */ 8 | class Server { 9 | /** 10 | * @param {PyMakr} pymakr 11 | */ 12 | constructor(pymakr) { 13 | this.pymakr = pymakr; 14 | this.log = pymakr.log.createChild("terminal server"); 15 | // creates a new server for each new client 16 | this.server = createServer((socket) => { 17 | /** @type {ProtocolAndAddress[]} */ 18 | const availableDevices = pymakr.devicesStore 19 | .get() 20 | .map((device) => ({ protocol: device.protocol, address: device.address })); 21 | 22 | //send available devices to client 23 | socket.write(JSON.stringify(availableDevices)); 24 | 25 | //listen for client to return protocol and address 26 | socket.once("data", (data) => { 27 | const { protocol, address } = JSON.parse(data.toString()); 28 | 29 | const device = this.pymakr.devicesStore 30 | .get() 31 | .find((_device) => _device.protocol === protocol && _device.address === address); 32 | 33 | // rename terminal name for this instance 34 | vscode.commands.executeCommand("workbench.action.terminal.renameWithArg", { name: device.displayName }); 35 | 36 | // listen to keystrokes from client 37 | socket.on("data", (data) => { 38 | this.log.debug("received", data.toString(), data[0]); 39 | device.adapter.sendData(data); 40 | // make sure device data is sent to the last active terminal 41 | device.__onTerminalDataExclusive = (data) => socket.write(data); 42 | }); 43 | 44 | socket.on("error", (err) => { 45 | // @ts-ignore err does have .code prop 46 | if (err.code !== "ECONNRESET") throw err; 47 | else this.log.debug("client disconnected"); 48 | }); 49 | }); 50 | }).listen(this.pymakr.terminalPort); 51 | this.log.debug("Listening on port: ", this.pymakr.terminalPort); 52 | } 53 | } 54 | 55 | module.exports = { Server }; 56 | -------------------------------------------------------------------------------- /src/terminal/Terminal.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("path"); 2 | const vscode = require("vscode"); 3 | 4 | /** 5 | * Runs the client terminal, eg. `node client.js serial COM6` 6 | * Each new terminal is an instance of this class 7 | * We need this because VSCode terminals have to be external scripts 8 | */ 9 | class Terminal { 10 | /** 11 | * @param {PyMakr} pymakr 12 | * @param {import('../Device').Device} device 13 | */ 14 | constructor(pymakr, device) { 15 | this.pymakr = pymakr; 16 | this.device = device; 17 | this.log = this.pymakr.log.createChild("Terminal"); 18 | 19 | this.term = vscode.window.createTerminal(this.pymakr.getTerminalProfile(device.protocol, device.address)); 20 | this.term.show(); 21 | 22 | // dispose of the terminal when closing VSCode 23 | this.pymakr.context.subscriptions.push(this.term) 24 | } 25 | } 26 | 27 | module.exports = { Terminal }; 28 | -------------------------------------------------------------------------------- /src/terminal/bin/client-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/src/terminal/bin/client-linux -------------------------------------------------------------------------------- /src/terminal/bin/client-macos: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/src/terminal/bin/client-macos -------------------------------------------------------------------------------- /src/terminal/bin/client-win.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/src/terminal/bin/client-win.exe -------------------------------------------------------------------------------- /src/terminal/bin/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is the CLI that connects to the terminal server 3 | * It is executed in the shell by Terminal.js 4 | */ 5 | 6 | const net = require("net"); 7 | const prompts = require("prompts"); 8 | const readline = require("readline"); 9 | const host = "127.0.0.1"; 10 | 11 | createConnection(); 12 | 13 | function createConnection() { 14 | const socket = new net.Socket(); 15 | 16 | const args = process.argv[1].endsWith("client.js") ? process.argv.slice(2) : process.argv.slice(1); 17 | 18 | let [_port, protocol, address] = args; 19 | if (!_port) { 20 | console.log("got", { _port, protocol, address }, "from", process.argv); 21 | throw new Error('No port specified. Example: "client.js [protocol] [address]"'); 22 | } 23 | const port = parseInt(_port); 24 | 25 | socket.connect(port, host, async () => { 26 | // first message contains available devices 27 | socket.once("data", async (data) => { 28 | const availableDevices = JSON.parse(data.toString()); 29 | // if no device is specified, prompt the user to select one and then connect 30 | if (!protocol || !address) ({ protocol, address } = await prompt(availableDevices)); 31 | startClient(protocol, address); 32 | }); 33 | }); 34 | 35 | return socket; 36 | 37 | async function startClient(protocol, address) { 38 | // let the server know which device we want to listen to 39 | socket.write(JSON.stringify({ address, protocol })); 40 | process.stdin.setRawMode(true); 41 | process.stdin.resume(); // prompt stops input 42 | console.clear(); // clear prompt message 43 | 44 | // send stdin to keypress events 45 | readline.emitKeypressEvents(process.stdin); 46 | process.stdin.on("keypress", async (str, key) => { 47 | socket.write(Buffer.from(key.sequence)); 48 | if (key.name === "k" && key.ctrl) process.exit(0); 49 | if (key.name === "x" && key.ctrl) process.exit(0); 50 | }); 51 | 52 | // send Ctrl+B for friendly REPL 53 | socket.write("\x02"); 54 | 55 | // proxy data from the device to the terminal 56 | socket.on("data", (data) => process.stdout.write(data)); 57 | 58 | // on errors, try to reconnect every second 59 | socket.on("error", (err) => { 60 | const reconnectInterval = setInterval(() => { 61 | try { 62 | const _socket = createConnection(); 63 | _socket.once("data", async () => { 64 | clearInterval(reconnectInterval); 65 | }); 66 | } catch (err) { 67 | console.log(err); 68 | } 69 | }, 1000); 70 | }); 71 | } 72 | 73 | /** 74 | * Prompts the user to select a device 75 | * @param {ProtocolAndAddress[]} availableDevices 76 | */ 77 | async function prompt(availableDevices) { 78 | const { connection } = await prompts.prompt({ 79 | type: "select", 80 | name: "connection", 81 | message: "pick a connection", 82 | choices: availableDevices.map((ad) => ({ title: `${ad.protocol}://${ad.address}`, value: ad })), 83 | }); 84 | return connection; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/utils/createLogger.js: -------------------------------------------------------------------------------- 1 | const { createLogger: _createLogger } = require("consolite"); 2 | const vscode = require("vscode"); 3 | const util = require("util"); 4 | 5 | /** @param {Date} d */ 6 | const timestamp = (d = new Date()) => 7 | [d.getHours(), d.getMinutes(), d.getSeconds()].map((d) => d.toString().padStart(2, "0")).join(":") + 8 | `.${d.getMilliseconds().toString().padEnd(3, "0")}`; 9 | 10 | /** 11 | * turn log data into a printable string. 12 | * @param {any} data 13 | * @returns {string} 14 | */ 15 | function data2string(data) { 16 | if (!data) return JSON.stringify(data); 17 | if (data instanceof Error) { 18 | return data.message || data.stack || util.inspect(data); 19 | } 20 | if (data.success === false && data.message) { 21 | return data.message; 22 | } 23 | if (data instanceof Array) { 24 | return data.map(data2string).join(" "); 25 | } 26 | // replace passwords in json with **** 27 | return util.format(data).replace(/password: '.*?'/g, "password: '****'"); 28 | } 29 | 30 | /** 31 | * Creates Consolite instance to handle all logging 32 | * @param {string} name 33 | */ 34 | const createLogger = (name) => { 35 | const outputChannel = vscode.window.createOutputChannel("PyMakr", "log"); 36 | // todo: add test to check writing to vscode output channel 37 | // todo: avoid logging passwords in the console log 38 | const log = _createLogger( 39 | { 40 | methods: { 41 | traceShort: console.log, 42 | info: (ts, ...data) => { 43 | console.log(ts, "info:", ...data); // in case we need a copy/paste of the console 44 | outputChannel.appendLine(ts + "info: " + data2string(data)); 45 | }, 46 | warn: (ts, ...data) => { 47 | console.warn(ts, "warning:", ...data); // in case we need a copy/paste of the console 48 | outputChannel.appendLine(ts + "warning: " + data2string(data)); 49 | }, 50 | error: (ts, ...data) => { 51 | console.log(ts, "error:", ...data); // in case we need a copy/paste of the console 52 | outputChannel.appendLine(ts + "error: " + data2string(data)); 53 | }, 54 | debugShort: (ts, ...data) => { 55 | console.log(ts, "debug:", ...data); // in case we need a copy/paste of the console 56 | outputChannel.appendLine(ts + "debug: " + data2string(data)); 57 | }, 58 | debug: (ts, ...data) => { 59 | console.log(ts, "debug:", ...data); // in case we need a copy/paste of the console 60 | outputChannel.appendLine(ts + "debug: " + data2string(data)); 61 | }, 62 | }, 63 | }, 64 | () => timestamp(), 65 | name 66 | ); 67 | 68 | log.levels = { 69 | fatal: 0, 70 | error: 1, 71 | warn: 2, 72 | info: 3, 73 | log: 3, 74 | default: 3, 75 | debugShort: 4, 76 | debug: 5, 77 | traceShort: 6, 78 | trace: 7, 79 | }; 80 | log.delimiter = ">"; 81 | 82 | return log; 83 | }; 84 | 85 | module.exports = { createLogger, timestamp }; 86 | -------------------------------------------------------------------------------- /src/utils/errors.js: -------------------------------------------------------------------------------- 1 | class MissingProjectError extends Error { 2 | /** 3 | * @param {string=} message 4 | */ 5 | constructor(message) { 6 | super(message || "Project not found"); 7 | this.name = "MissingProjectError"; 8 | } 9 | } 10 | 11 | class DeviceOfflineError extends Error { 12 | /** 13 | * @param {string=} message 14 | */ 15 | constructor(message) { 16 | super(message || "cannot safeboot offline device"); 17 | this.name = "DeviceOffline"; 18 | } 19 | } 20 | 21 | module.exports = { 22 | MissingProjectError, 23 | DeviceOfflineError 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/formatters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} TableOptions 3 | * @prop {(value: (string | number | boolean)) => (string | number | boolean)} tableWrap 4 | * @prop {(value: (string | number | boolean), row) => (string | number | boolean)} rowWrap 5 | * @prop {(value: (string | number | boolean), row, col) => (string | number | boolean)} colWrap 6 | * @prop {string} colDelim 7 | * @prop {string} rowDelim 8 | */ 9 | 10 | const { timestamp } = require("./createLogger"); 11 | 12 | const sanitize = (str) => 13 | (typeof str === "string" ? str : typeof str === "undefined" ? "undefined" : JSON.stringify(str, null, 2)) 14 | .replace(/\|/gm, "|") 15 | .replace(/\r?\n/gm, "
") 16 | .replace(/"username": ".+?"/, '"username": "***"') 17 | .replace(/"password": ".+?"/, '"password": "***"'); 18 | const sanitizeTableValues = (table) => table.map((columns) => columns.map(sanitize)); 19 | 20 | const arraysToHTMLTable = (arrays, hasHeader) => 21 | arraysToTable(arrays, { 22 | tableWrap: (value) => `\n${value}\n
`, 23 | rowWrap: (value, row) => (hasHeader && !row ? `${value}` : `${value}`), 24 | colWrap: (value, row, col) => `${value}`, 25 | colDelim: " ", 26 | rowDelim: "\n", 27 | }); 28 | 29 | /** @param {(string | number | boolean)[][]} rows */ 30 | const arraysToMarkdownTable = ([...rows]) => { 31 | rows.splice(1, 0, Array(rows[0].length).fill("-")); 32 | 33 | return arraysToTable(rows, { 34 | tableWrap: (value) => value, 35 | rowWrap: (value, row) => `|${value}|`, 36 | colWrap: (value, row, col) => value, 37 | colDelim: "|", 38 | rowDelim: "\n", 39 | }); 40 | }; 41 | 42 | /** @param {(string | number | boolean)[][]} rows */ 43 | const arraysToTerminalTable = (rows) => { 44 | // determine max length of each column 45 | const colLenghts = Array(rows[0].length).fill(0); 46 | rows.forEach((row) => 47 | row.forEach((col, index) => (colLenghts[index] = Math.max(col.toString().length, colLenghts[index]))) 48 | ); 49 | 50 | return arraysToTable(rows, { 51 | tableWrap: (value) => value, 52 | rowWrap: (value, row) => value, 53 | colWrap: (value, row, col) => value.toString().padEnd(colLenghts[col]), 54 | colDelim: "|", 55 | rowDelim: "\n", 56 | }); 57 | }; 58 | 59 | /** 60 | * 61 | * @param {(string | number | boolean)[][]} arrays 62 | * @param {TableOptions} options 63 | */ 64 | const arraysToTable = (arrays, options) => { 65 | arrays = sanitizeTableValues(arrays); 66 | const { tableWrap, rowWrap, colWrap, rowDelim, colDelim } = options; 67 | const formattedColumns = arrays.map((array, row) => 68 | array.map((value, column) => colWrap(value, row, column)).join(colDelim) 69 | ); 70 | const formattedRows = formattedColumns.map((columnStr, row) => rowWrap(columnStr, row)).join(rowDelim); 71 | 72 | return tableWrap(formattedRows); 73 | }; 74 | 75 | const unBuffer = (str) => 76 | str instanceof Buffer ? str.toString() : typeof str === "string" ? str : JSON.stringify(str, null, 2); 77 | 78 | /** 79 | * @param {Device} device 80 | */ 81 | const adapterHistoryTable = (device) => { 82 | const fields = ["function", "args", "time", "result", "queued at", "finished at", "failed"]; 83 | const rows = device.adapter.__proxyMeta.history.map((entry) => [ 84 | entry.field.toString(), 85 | entry.args.length ? `
${unBuffer(entry.args)}
` : " ", 86 | entry.runDuration, 87 | `
${entry.error || unBuffer(entry.result)}
`, 88 | entry.queuedAt && timestamp(entry.queuedAt), 89 | entry.finishedAt && timestamp(entry.finishedAt), 90 | !!entry.error, 91 | ]); 92 | return [fields, ...rows.reverse()]; 93 | }; 94 | 95 | module.exports = { arraysToHTMLTable, arraysToMarkdownTable, arraysToTerminalTable, adapterHistoryTable }; 96 | -------------------------------------------------------------------------------- /src/utils/msgs.js: -------------------------------------------------------------------------------- 1 | const { friendlyProxyQueueItem } = require("./blockingProxy"); 2 | 3 | const msgs = { 4 | download: (filesAndDirs) => ["download", filesAndDirs.map((f) => f.filename)], 5 | /** @param {import('./blockingProxy').BlockingProxy} adapter */ 6 | boardInfoTimedOutErr: (adapter) => 7 | `Timed out while getting board info. Call history \r\n${adapter.__proxyMeta.history 8 | .map(friendlyProxyQueueItem) 9 | .join("\r\n")}` + 10 | "\r\n Next item in queue: " + 11 | (adapter.__proxyMeta.queue[0] ? friendlyProxyQueueItem(adapter.__proxyMeta.queue[0]) : "none"), 12 | }; 13 | 14 | module.exports = { msgs }; 15 | -------------------------------------------------------------------------------- /src/utils/readUntil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Listener 3 | * @prop {string|RegExp} match 4 | * @prop {(matches:RegExpMatchArray)=>void} cb 5 | * @prop {Object} options 6 | * @prop {Boolean=} options.callOnFalse 7 | * @prop {Number=} options.length 8 | */ 9 | 10 | /** 11 | * Creates a readUntil function that calls a provided callback whenever a provided string matches the current stream 12 | */ 13 | const createReadUntil = () => { 14 | /** @type {Listener[]} */ 15 | const listeners = []; 16 | let concatenatedData = ""; 17 | 18 | let wantedDataLength = 1; 19 | 20 | /** @param {Listener} listener */ 21 | const getLengthFromListener = (listener) => listener.options.length || listener.match.toString().length; 22 | const getMaxWantedDataLength = (listeners) => Math.max(...listeners.map(getLengthFromListener)); 23 | const getLastLineLength = (str) => str.length - Math.max(str.lastIndexOf("\n"), 0); 24 | 25 | /** 26 | * calls the callback whenever the match matches the end of the current stream 27 | * @param {Listener['match']} match 28 | * @param {Listener['cb']} cb 29 | * @param {Listener['options']} options 30 | */ 31 | const readUntil = (match, cb, options = {}) => { 32 | const listener = { match, cb, options }; 33 | listeners.push(listener); 34 | wantedDataLength = getMaxWantedDataLength(listeners); 35 | const unsub = () => { 36 | listeners.splice(listeners.indexOf(listener), 1); 37 | wantedDataLength = getMaxWantedDataLength(listeners); 38 | }; 39 | return unsub; 40 | }; 41 | 42 | /** 43 | * push data to the stream 44 | * @param {string} data 45 | */ 46 | readUntil.push = (data) => { 47 | concatenatedData += data; 48 | const chuckSize = Math.max(getLastLineLength(concatenatedData), wantedDataLength); 49 | concatenatedData = concatenatedData.slice(-chuckSize); 50 | listeners.forEach((listener) => { 51 | const result = concatenatedData.match(listener.match); 52 | if (result || listener.options.callOnFalse) listener.cb(result); 53 | }); 54 | }; 55 | 56 | Object.defineProperties(readUntil, { 57 | concatenatedData: { get: () => concatenatedData }, 58 | wantedDataLength: { get: () => wantedDataLength }, 59 | listeners: { value: listeners }, 60 | getMaxWantedDataLength: { value: getMaxWantedDataLength }, 61 | }); 62 | 63 | return readUntil; 64 | }; 65 | 66 | module.exports = { createReadUntil }; 67 | -------------------------------------------------------------------------------- /src/utils/specs/_sampleProject/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "py_ignore": [ 3 | "conf", 4 | ".vscode", 5 | ".gitignore", 6 | ".git", 7 | "env", 8 | "venv" 9 | ], 10 | "name": "sample-project" 11 | } -------------------------------------------------------------------------------- /src/utils/specs/blockingProxy.spec.mjs: -------------------------------------------------------------------------------- 1 | import { createBlockingProxy } from "../blockingProxy.js"; 2 | 3 | const wait = (time) => new Promise((resolve) => setTimeout(resolve, time)); 4 | 5 | const createTestObj = () => { 6 | const obj = { 7 | events: [], 8 | runCallback: (callback, delay) => 9 | new Promise((resolve) => 10 | setTimeout(() => { 11 | callback(); 12 | resolve(); 13 | }, delay) 14 | ), 15 | async20: () => 16 | new Promise((resolve) => 17 | setTimeout(() => { 18 | obj.events.push("async20"); 19 | resolve(); 20 | }, 20) 21 | ), 22 | async10: () => 23 | new Promise((resolve) => 24 | setTimeout(() => { 25 | obj.events.push("async10"); 26 | resolve(); 27 | }, 20) 28 | ), 29 | }; 30 | return obj; 31 | }; 32 | 33 | // this doesn't test blockingProxy, just the test object 34 | test("unblocked object runs methods in parallel", async () => { 35 | const obj = createTestObj(); 36 | const startStamp = Date.now(); 37 | 38 | const promises = [obj.async20(), obj.async20(), obj.async20(), obj.async20(), obj.async20()]; 39 | await Promise.all(promises); 40 | 41 | const duration = Date.now() - startStamp; 42 | 43 | assert(duration < 60); 44 | }); 45 | 46 | test("blocked object runs methods in sequence", async () => { 47 | const obj = createTestObj(); 48 | const startStamp = Date.now(); 49 | let finishedLastTestStamp = 0; 50 | let isReadyTestStamp = 0; 51 | 52 | const proxied = createBlockingProxy(obj); 53 | const promises = [proxied.async20(), proxied.async20(), proxied.async20(), proxied.async20(), proxied.async20()]; 54 | Promise.all(promises).then(() => (finishedLastTestStamp = Date.now())); 55 | 56 | // start proxy 57 | proxied.__proxyMeta.run(); 58 | proxied.__proxyMeta.idle.then(() => (isReadyTestStamp = Date.now())); 59 | await Promise.all([...promises, proxied.__proxyMeta.idle]); 60 | 61 | assert.ok(finishedLastTestStamp && isReadyTestStamp, "timestamps should be updated"); 62 | assert.ok(finishedLastTestStamp - startStamp > 100, "methods should take at least 100ms (5 x 20ms)"); 63 | assert.ok(finishedLastTestStamp - isReadyTestStamp < 10, "methods should finish at the same time as ready"); 64 | }); 65 | 66 | test("blocked object can have beforeEachCall hook and exceptions", async () => { 67 | const obj = createTestObj(); 68 | const startStamp = Date.now(); 69 | let finishedLastTestStamp = 0; 70 | let isReadyTestStamp = 0; 71 | 72 | const proxied = createBlockingProxy(obj, { exceptions: ["async20"] }); 73 | 74 | proxied.__proxyMeta.beforeEachCall(async ({ item }) => { 75 | await new Promise((resolve) => setTimeout(resolve, 30)); 76 | item.target.events.push("before " + item.field.toString()); 77 | }); 78 | 79 | const promises = [ 80 | proxied.async20(), // skip, exception 81 | proxied.async10(), // run queued 82 | proxied.async20(), // skip, exception 83 | proxied.async10(), // and queued 84 | proxied.async20(), // skip, exception 85 | ]; 86 | 87 | Promise.all(promises).then(() => (finishedLastTestStamp = Date.now())); 88 | 89 | // start proxy 90 | proxied.__proxyMeta.run(); 91 | proxied.__proxyMeta.idle.then(() => (isReadyTestStamp = Date.now())); 92 | 93 | await Promise.all([...promises, proxied.__proxyMeta.idle]); 94 | 95 | assert.ok(finishedLastTestStamp && isReadyTestStamp, "timestamps should be updated"); 96 | assert.ok( 97 | finishedLastTestStamp - startStamp < 200, 98 | "methods should take less than 200ms (30ms + 10 ms + 30ms + 10 ms)" 99 | ); 100 | assert.ok( 101 | finishedLastTestStamp - startStamp > 80, 102 | "methods should take more than 80ms (30ms + 10 ms + 30ms + 10 ms)" 103 | ); 104 | assert.ok(finishedLastTestStamp - isReadyTestStamp < 10, "methods should finish at the same time as ready"); 105 | assert.deepEqual(obj.events, [ 106 | "async20", 107 | "async20", 108 | "async20", 109 | "before async10", 110 | "async10", 111 | "before async10", 112 | "async10", 113 | ]); 114 | }); 115 | 116 | test("can clear queue", async () => { 117 | const obj = createTestObj(); 118 | const proxied = createBlockingProxy(obj); 119 | 120 | const messages = []; 121 | 122 | proxied.runCallback(() => messages.push("hello"), 20); 123 | proxied.runCallback(() => messages.push("world"), 20); 124 | proxied.runCallback(() => messages.push("how are you"), 20); 125 | 126 | proxied.__proxyMeta.run(); 127 | 128 | // clear queue in the middle of the second call 129 | await wait(30); 130 | proxied.__proxyMeta.clearQueue(); 131 | await proxied.__proxyMeta.idle; 132 | 133 | assert.deepEqual(messages, ["hello", "world"]); 134 | }); 135 | 136 | test("can skip current call", async () => { 137 | const obj = createTestObj(); 138 | const proxied = createBlockingProxy(obj); 139 | 140 | const messages = []; 141 | 142 | proxied.runCallback(() => messages.push("hello"), 60); 143 | proxied.runCallback(() => messages.push("world"), 20); 144 | proxied.runCallback(() => messages.push("how"), 20); 145 | proxied.runCallback(() => messages.push("are"), 20); 146 | proxied.runCallback(() => messages.push("you"), 20); 147 | 148 | proxied.__proxyMeta.run(); 149 | proxied.__proxyMeta.skipCurrent(); 150 | await proxied.__proxyMeta.idle; 151 | assert.deepEqual(messages, ["world", "how", "hello", "are", "you"]); 152 | }); 153 | 154 | test("a skipped call doesnt keep the queue active", async () => { 155 | const obj = createTestObj(); 156 | const proxied = createBlockingProxy(obj); 157 | 158 | const messages = []; 159 | 160 | proxied.runCallback(() => messages.push("hello"), 20); 161 | 162 | proxied.__proxyMeta.run(); 163 | proxied.__proxyMeta.skipCurrent(); 164 | await proxied.__proxyMeta.idle; 165 | assert.deepEqual(messages, []); 166 | 167 | await wait(20); 168 | assert.deepEqual(messages, ["hello"]); 169 | }); 170 | -------------------------------------------------------------------------------- /src/utils/specs/formatters.test.js: -------------------------------------------------------------------------------- 1 | const { arraysToHTMLTable, arraysToMarkdownTable, arraysToTerminalTable } = require("../formatters"); 2 | 3 | test("can create tables from arrays", () => { 4 | const data = [ 5 | ["header1", "header2", "header3"], 6 | ["cola1", "cola2", "cola3"], 7 | ["colb1", "colb2", "colb3"], 8 | ]; 9 | 10 | test("can create HTML table", () => { 11 | const output = arraysToHTMLTable(data); 12 | assert.equal( 13 | output, 14 | ` 15 | 16 | 17 | 18 |
header1 header2 header3
cola1 cola2 cola3
colb1 colb2 colb3
` 19 | ); 20 | }); 21 | test("can create HTML table with header", () => { 22 | const output = arraysToHTMLTable(data, true); 23 | assert.equal( 24 | output, 25 | ` 26 | 27 | 28 | 29 |
header1 header2 header3
cola1 cola2 cola3
colb1 colb2 colb3
` 30 | ); 31 | }); 32 | test("can create markdown table", () => { 33 | const output = arraysToMarkdownTable(data); 34 | assert.equal( 35 | output, 36 | `|header1|header2|header3| 37 | |-|-|-| 38 | |cola1|cola2|cola3| 39 | |colb1|colb2|colb3|` 40 | ); 41 | }); 42 | test("can create terminal table", () => { 43 | const output = arraysToTerminalTable(data); 44 | console.log(output); 45 | assert.equal( 46 | output, 47 | `header1|header2|header3 48 | cola1 |cola2 |cola3 49 | colb1 |colb2 |colb3 ` 50 | ); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/utils/specs/probs.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | timeout: 1000 3 | } -------------------------------------------------------------------------------- /src/utils/specs/readUntil.test.js: -------------------------------------------------------------------------------- 1 | const { createReadUntil } = require("../readUntil"); 2 | 3 | test("readUntil", () => { 4 | const readUntil = createReadUntil(); 5 | test("can match an exact string", () => { 6 | const results = []; 7 | readUntil("foo", () => results.push("foo")); 8 | readUntil.push("foo"); 9 | assert.deepEqual(results, ["foo"]); 10 | }); 11 | 12 | test("can match end of string", () => { 13 | const results = []; 14 | readUntil("bar", () => results.push("bar")); 15 | readUntil.push("bar"); 16 | assert.deepEqual(results, ["bar"]); 17 | }); 18 | 19 | test("can match concatenated characters", () => { 20 | let isMatch; 21 | readUntil(/\n>>> [^\n]*$/, (_isMatch) => (isMatch = _isMatch), { callOnFalse: true }); 22 | 23 | readUntil.push("\n"); 24 | assert(!isMatch); 25 | readUntil.push(">>> "); 26 | assert(isMatch); 27 | readUntil.push("foo"); 28 | assert(isMatch); 29 | readUntil.push("\nbar"); // linebreak breaks the match 30 | assert(!isMatch); 31 | }); 32 | 33 | test("can match concatenated characters", () => { 34 | let isMatch; 35 | readUntil(/\n>>> [^\n]*$/, (_isMatch) => (isMatch = _isMatch), { callOnFalse: true }); 36 | 37 | readUntil.push("\n"); 38 | assert(!isMatch); 39 | readUntil.push(">"); 40 | assert(!isMatch); 41 | readUntil.push(">"); 42 | assert(!isMatch); 43 | readUntil.push(">"); 44 | assert(!isMatch); 45 | readUntil.push(" "); 46 | assert(isMatch); 47 | readUntil.push("foo"); 48 | assert(isMatch); 49 | readUntil.push("\nbar"); // linebreak breaks the match 50 | assert(!isMatch); 51 | }); 52 | 53 | test("can unsub", () => { 54 | let counter = 0; 55 | const unsub = readUntil("foo", () => { 56 | counter++; 57 | if (counter >= 2) unsub(); 58 | }); 59 | readUntil.push("foo"); 60 | readUntil.push("foo"); 61 | readUntil.push("foo"); 62 | readUntil.push("foo"); 63 | assert.equal(counter, 2); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/utils/specs/store.spec.js: -------------------------------------------------------------------------------- 1 | const { writable, derived, chainDerived } = require("../store.js"); 2 | 3 | test("plain store", () => { 4 | let store; 5 | let unsub; 6 | let subValue = "untouched"; 7 | test("can create store", () => { 8 | store = writable({ value: "initial" }); 9 | assert.ok(store.get); 10 | }); 11 | test("can get value", () => { 12 | assert.deepEqual(store.get(), { value: "initial" }); 13 | }); 14 | test("can update value", () => { 15 | store.set({ value: "new value" }); 16 | assert.deepEqual(store.get(), { value: "new value" }); 17 | }); 18 | test("can subscribe", () => { 19 | unsub = store.subscribe((_value) => { 20 | subValue = _value; 21 | }); 22 | store.set({ value: "subscribe" }); 23 | assert.deepEqual(subValue, { value: "subscribe" }); 24 | }); 25 | test("can unsubscribe", () => { 26 | unsub(); 27 | store.set({ value: "unsub" }); 28 | assert.deepEqual(subValue, { value: "subscribe" }); 29 | }); 30 | }); 31 | 32 | test("derived store", () => { 33 | let store; 34 | let unsub; 35 | let subValue = "untouched"; 36 | const store1 = writable("store1"); 37 | const store2 = writable("store2"); 38 | const store3 = writable("store3"); 39 | test("can create derived store", () => { 40 | store = derived([store1, store2, store3], ([store1, store2, store3]) => { 41 | return [store1, store2, store3].join("-"); 42 | }); 43 | assert(store.get); 44 | }); 45 | test("can get value", () => { 46 | assert.equal(store.get(), "store1-store2-store3"); 47 | }); 48 | test("can set value", () => { 49 | store1.set("store1.updated"); 50 | assert.equal(store.get(), "store1.updated-store2-store3"); 51 | }); 52 | test("can set multiple values", () => { 53 | store2.set("store2.updated"); 54 | store3.set("store3.updated"); 55 | assert.equal(store.get(), "store1.updated-store2.updated-store3.updated"); 56 | }); 57 | test("can subscribe", () => { 58 | unsub = store.subscribe((_value) => { 59 | subValue = _value; 60 | }); 61 | store1.set("1"); 62 | assert.equal(subValue, "1-store2.updated-store3.updated"); 63 | }); 64 | test("can unsub", () => { 65 | unsub(); 66 | store2.set("2"); 67 | assert.deepEqual(subValue, "1-store2.updated-store3.updated"); 68 | }); 69 | }); 70 | 71 | test("deriveChained", () => { 72 | const storeStr = writable("store1"); 73 | const storeFn = writable((str) => "derived-" + str); 74 | 75 | const chainedStore = chainDerived([storeStr], ([storeStr]) => { 76 | return derived([storeFn], ([storeFn]) => { 77 | return storeFn(storeStr); 78 | }); 79 | }); 80 | 81 | test("can create chained store", () => { 82 | assert(chainedStore.get()); 83 | }); 84 | 85 | test("can get value", () => { 86 | assert.equal(chainedStore.get(), "derived-store1"); 87 | }); 88 | 89 | test("can set value", () => { 90 | storeStr.set("updated"); 91 | assert.equal(chainedStore.get(), "derived-updated"); 92 | }); 93 | 94 | test("can set nested Value", () => { 95 | storeFn.set((str) => "newDerived-" + str); 96 | assert.equal(chainedStore.get(), "newDerived-updated"); 97 | }); 98 | }); 99 | 100 | test("next", () => { 101 | const subVals = []; 102 | const nextVals = []; 103 | const myStore = writable(""); 104 | myStore.subscribe((v) => subVals.push(v)); 105 | myStore.next((v) => nextVals.push(v)); 106 | myStore.set("a"); 107 | myStore.set("b"); 108 | myStore.set("c"); 109 | assert.deepEqual(subVals, ["a", "b", "c"]); 110 | assert.deepEqual(nextVals, ["a"]); 111 | }); 112 | 113 | test("next with multiple subscribers", async () => { 114 | const subVals = []; 115 | const nextVals = []; 116 | const myStore = writable(""); 117 | myStore.subscribe((v) => subVals.push(v)); 118 | myStore.next((v) => nextVals.push(v)); 119 | myStore.next((v) => nextVals.push(v)); 120 | myStore.next((v) => nextVals.push(v)); 121 | myStore.next((v) => nextVals.push(v)); 122 | myStore.set("a"); 123 | myStore.set("b"); 124 | assert.deepEqual(nextVals, ["a", "a", "a", "a"]); 125 | }); 126 | 127 | test("lazy", () => { 128 | test("non lazy always calls subs", () => { 129 | let count = 0; 130 | const myStore = writable("initial"); 131 | myStore.subscribe(() => count++); 132 | myStore.set("initial"); 133 | myStore.set("initial"); 134 | assert.equal(count, 2); 135 | }); 136 | test("non lazy always calls subs", () => { 137 | let count = 0; 138 | const myStore = writable("initial", { lazy: true }); 139 | myStore.subscribe(() => count++); 140 | myStore.set("initial"); 141 | myStore.set("initial"); 142 | assert.equal(count, 0); 143 | }); 144 | }); 145 | 146 | test("can unsub self", () => { 147 | let received = null; 148 | const myStore = writable("initial"); 149 | myStore.subscribe((val, unsub) => { 150 | received = val; 151 | unsub(); 152 | }); 153 | myStore.set("foo"); 154 | myStore.set("bar"); 155 | assert.equal(received, "foo"); 156 | }); 157 | 158 | test("when", async () => { 159 | let events = []; 160 | const myStore = writable("initial"); 161 | 162 | const promise = myStore.when("resolve-me").then((r) => events.push(r)); 163 | assert.equal(myStore["_listeners"].length, 1); 164 | assert.deepEqual(events, []); 165 | 166 | myStore.set("foobar"); 167 | assert.deepEqual(events, []); 168 | assert.equal(myStore["_listeners"].length, 1); 169 | 170 | myStore.set("resolve-me"); 171 | await promise; 172 | assert.deepEqual(events, ["resolve-me"]); 173 | assert.equal(myStore["_listeners"].length, 0); 174 | }); 175 | -------------------------------------------------------------------------------- /src/utils/storageObj.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { createQueue } = require("./misc"); 3 | 4 | /** @typedef {Boolean} Changed */ 5 | 6 | /** 7 | * @template T 8 | * @typedef {Object} GetterSetter 9 | * @prop {()=>T} get 10 | * @prop {function(T):(Changed|Promise)} set 11 | */ 12 | 13 | const updateConfigQueue = createQueue(); 14 | const updateStateQueue = createQueue(); 15 | 16 | /** 17 | * @template T 18 | * @param {vscode.Memento} stateStorage 19 | * @param {string} key 20 | * @param {T=} defaults 21 | * @returns {GetterSetter} 22 | */ 23 | const createStateObject = (stateStorage, key, defaults) => { 24 | const get = () => stateStorage.get(key) ?? defaults; 25 | const set = async (value) => { 26 | const imDone = await updateStateQueue(); 27 | const changed = JSON.stringify(get()) !== JSON.stringify(value); 28 | stateStorage.update(key, value); 29 | imDone(); 30 | return changed; 31 | }; 32 | return { get, set }; 33 | }; 34 | 35 | /** 36 | * @template T 37 | * @param {string} section configuration name, supports dotted 38 | * @param {string} key key name, supports dotted 39 | * @param {T=} defaults 40 | * @param {vscode.ConfigurationTarget=} configurationTarget 41 | * @returns {GetterSetter} 42 | */ 43 | const createConfigObject = (section, key, defaults, configurationTarget = vscode.ConfigurationTarget.Global) => { 44 | const get = () => vscode.workspace.getConfiguration(section).get(key) || defaults; 45 | const set = async (value) => { 46 | const imDone = await updateConfigQueue(); 47 | const changed = JSON.stringify(get()) !== JSON.stringify(value); 48 | await vscode.workspace.getConfiguration(section).update(key, value, configurationTarget).then(); 49 | imDone(); 50 | return changed; 51 | }; 52 | return { get, set }; 53 | }; 54 | 55 | /** 56 | * @template T 57 | * @param {string} section configuration name, supports dotted 58 | * @param {string} key key name, supports dotted 59 | * @param {string} id key name, supports dotted 60 | * @param {T=} defaults 61 | * @param {vscode.ConfigurationTarget=} configurationTarget 62 | * @returns {GetterSetter} 63 | */ 64 | const createListedConfigObject = ( 65 | section, 66 | key, 67 | id, 68 | defaults, 69 | configurationTarget = vscode.ConfigurationTarget.Global 70 | ) => { 71 | const get = () => 72 | vscode.workspace 73 | .getConfiguration(section) 74 | .get(key) 75 | .find((cfg) => cfg.id === id) || { ...defaults }; 76 | const set = async (value) => { 77 | const imDone = await updateConfigQueue(); 78 | /** @type {Array} */ 79 | const all = vscode.workspace.getConfiguration(section).get(key); 80 | const index = all.findIndex((e) => e.id === id); 81 | const old = get(); 82 | value.id = id; 83 | old.id = id; 84 | const changed = JSON.stringify(old) !== JSON.stringify(value); 85 | if (index > -1) all[index] = value; 86 | else all.push(value); 87 | await vscode.workspace.getConfiguration(section).update(key, all, configurationTarget); 88 | imDone(); 89 | return changed; 90 | }; 91 | return { get, set }; 92 | }; 93 | 94 | const createMappedConfigObject = ( 95 | section, 96 | key, 97 | id, 98 | defaults, 99 | configurationTarget = vscode.ConfigurationTarget.Global 100 | ) => { 101 | const get = () => vscode.workspace.getConfiguration(section)[key][id] || { ...defaults }; 102 | const set = async (value) => { 103 | const imDone = await updateConfigQueue(); 104 | let config = vscode.workspace.getConfiguration(section)[key] || {}; 105 | 106 | if (JSON.stringify(config[id]) === JSON.stringify(value)) { 107 | imDone(); 108 | return; 109 | } 110 | config = { ...config, [id]: value }; 111 | 112 | await vscode.workspace.getConfiguration(section).update(key, config, configurationTarget); 113 | 114 | imDone(); 115 | }; 116 | return { get, set }; 117 | }; 118 | 119 | module.exports = { createStateObject, createConfigObject, createListedConfigObject, createMappedConfigObject }; 120 | -------------------------------------------------------------------------------- /src/utils/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stores are inspired by Svelte's "writable" stores, but contain a few extra features 3 | * 4 | * Create store: 5 | * const myStore = writable('hello') 6 | * 7 | * Get value of store: 8 | * myStore.get() 9 | * 10 | * Subscribe to store 11 | * myStore.subscribe(callback) 12 | * 13 | * Set store value 14 | * myStore.set('new value') 15 | * 16 | * Update store value 17 | * myStore.update(oldValue => 'new value') 18 | * 19 | * Create a derived store 20 | * const myDerivedStore = 21 | */ 22 | 23 | const storeOptions = { 24 | onSub: (x) => void 0, 25 | onUnsub: (x) => void 0, 26 | onFirstSub: (x) => void 0, 27 | onLastUnsub: (x) => void 0, 28 | }; 29 | 30 | /** 31 | * @template T 32 | * @param {T} initialValue 33 | * @param {Partial>=} options 34 | * @returns {Writable} 35 | */ 36 | const writable = (initialValue, options) => { 37 | let _storeValue = initialValue; 38 | const listeners = []; 39 | const _options = { ...storeOptions, ...options }; 40 | 41 | const store = { 42 | _listeners: listeners, 43 | get: () => _storeValue, 44 | /** @param {T} value */ 45 | set: (value) => { 46 | if (!_options.lazy || value !== _storeValue) { 47 | _storeValue = value; 48 | 49 | for (const listener of [...listeners]) { 50 | const index = listeners.indexOf(listener); 51 | const unsub = () => listeners.splice(index, 1); 52 | listener(_storeValue, unsub); 53 | } 54 | } 55 | }, 56 | update: (callback) => store.set(callback(_storeValue)), 57 | subscribe: (listener) => { 58 | if (!listeners.length) _options.onFirstSub(store); 59 | _options.onSub(store); 60 | listeners.push(listener); 61 | 62 | const unsub = () => { 63 | const index = listeners.indexOf(listener); 64 | listeners.splice(index, 1); 65 | if (!listeners) _options.onLastUnsub(store); 66 | _options.onUnsub(store); 67 | }; 68 | return unsub; 69 | }, 70 | next: (listener) => { 71 | const unsub = store.subscribe((value) => { 72 | unsub(); 73 | listener(value); 74 | }); 75 | }, 76 | when: (expected, strict) => 77 | new Promise((resolve) => { 78 | const matchesExpected = (value) => value === expected || (!strict && value == expected); 79 | if (matchesExpected(_storeValue)) { 80 | console.log("resolved"); 81 | resolve(_storeValue); 82 | } else { 83 | const unsub = store.subscribe((newValue) => { 84 | if (matchesExpected(newValue)) { 85 | unsub(); 86 | resolve(newValue); 87 | } 88 | }); 89 | } 90 | }), 91 | }; 92 | return store; 93 | }; 94 | 95 | /** 96 | * @template {Readable[]} V 97 | * @template T 98 | * @param {[...V]} stores 99 | * @param {function(StoreValues):T} callback 100 | * @returns {Readable} 101 | */ 102 | const derived = (stores, callback) => { 103 | const unsubs = []; 104 | const storeValues = /** @type {StoreValues} */ (stores.map((store) => store.get())); 105 | const listeners = []; 106 | let value; 107 | const emit = () => { 108 | value = callback(storeValues); 109 | listeners.forEach((listener) => listener(value)); 110 | }; 111 | stores.forEach((store, index) => { 112 | // subscribe to changes 113 | const unsub = store.subscribe((_value) => { 114 | storeValues[index] = _value; 115 | emit(); 116 | }); 117 | unsubs.push(unsub); 118 | }); 119 | emit(); 120 | return { 121 | get: () => value, 122 | subscribe: (listener) => { 123 | listeners.push(listener); 124 | const unsub = () => { 125 | const index = listeners.indexOf(listener); 126 | if (index >= 0) listeners.splice(index, 1); 127 | }; 128 | return unsub; 129 | }, 130 | // todo needs test 131 | next: (listener) => { 132 | const oneTimeListener = (payload) => { 133 | listener(payload); 134 | unsub(); 135 | }; 136 | listeners.push(oneTimeListener); 137 | 138 | function unsub() { 139 | const index = listeners.indexOf(oneTimeListener); 140 | if (index >= 0) listeners.splice(index, 1); 141 | } 142 | 143 | return unsub; 144 | }, 145 | }; 146 | }; 147 | 148 | /** 149 | * Similar to derived, but callback expects a store to be returned 150 | * @template {Readable[]} V 151 | * @template {Readable} T 152 | * @param {[...V]} stores 153 | * @param {function(StoreValues):T} callback 154 | * @return {{ 155 | * subscribe: (arg0: (arg0: StoreValue) => void) => Unsubscribe 156 | * get: () => StoreValue 157 | * }} 158 | */ 159 | const chainDerived = (stores, callback) => { 160 | const nestedStore = derived(stores, callback); 161 | let unsubNested = () => void 0; 162 | const { set, subscribe: _subscribe, get } = writable(nestedStore.get().get()); 163 | 164 | const unsub = nestedStore.subscribe((value) => { 165 | unsubNested(); // unsub previous subscription 166 | set(value.get()); 167 | unsubNested = value.subscribe(set); 168 | }); 169 | 170 | const subscribe = (listener) => { 171 | const _unsub = _subscribe(listener); 172 | return () => { 173 | _unsub(); 174 | unsub(); 175 | unsubNested(); 176 | }; 177 | }; 178 | 179 | return { subscribe, get }; 180 | }; 181 | 182 | module.exports = { writable, derived, chainDerived }; 183 | -------------------------------------------------------------------------------- /src/utils/terminalExec.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const port = process.argv.length >= 3 ? Number(process.argv[2]) : 1337; 4 | const ip = "127.0.0.1"; 5 | 6 | const net = require("net"); 7 | const clients = []; 8 | const stdin = process.openStdin(); 9 | // todo not sure if this is correct 10 | // @ts-ignore 11 | stdin.setRawMode(true); 12 | const debug = true; 13 | 14 | log("Starting server..."); 15 | net 16 | .createServer(function (socket) { 17 | log("Client connected"); 18 | clients.push(socket); 19 | 20 | socket.on("data", function (data) { 21 | boardInput(data); 22 | }); 23 | 24 | socket.on("end", function () { 25 | log("Client disconnected"); 26 | clients.splice(clients.indexOf(socket), 1); 27 | }); 28 | }) 29 | .listen(port, ip); 30 | 31 | stdin.addListener("data", function (text) { 32 | userInput(text); 33 | }); 34 | 35 | // Send a message to all clients 36 | function userInput(message) { 37 | clients.forEach(function (client) { 38 | client.write(message); 39 | }); 40 | } 41 | 42 | function boardInput(message) { 43 | process.stdout.write(message); 44 | } 45 | 46 | function log(...msg) { 47 | if (debug) { 48 | console.log(...msg); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/vscodeHelpers.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const vscode = require("vscode"); 3 | const { Project } = require("../Project.js"); 4 | const { ProjectDeviceTreeItem, ProjectTreeItem } = require("../providers/ProjectsProvider.js"); 5 | const errors = require("./errors.js"); 6 | const { getNearestPymakrProjectDir, omit } = require("./misc.js"); 7 | 8 | /** 9 | * @param {pymakr} pymakr 10 | */ 11 | const createVSCodeHelpers = (pymakr) => { 12 | const helpers = { 13 | /** 14 | * @param {vscode.TreeItem|vscode.Uri|string|Project} projectRef 15 | * @returns {Project} 16 | */ 17 | coerceProject: (projectRef) => { 18 | if (projectRef instanceof Project) return projectRef; 19 | if (projectRef instanceof ProjectDeviceTreeItem) return projectRef.project; 20 | if (projectRef instanceof ProjectTreeItem) return projectRef.project; 21 | if (projectRef instanceof vscode.Uri) return helpers._getProjectByFsPath(projectRef.fsPath); 22 | if (typeof projectRef === "string") return helpers._getProjectByFsPath(projectRef); 23 | throw new Error("projectRef did not match an accepted type"); 24 | }, 25 | 26 | /** 27 | * Use coerceProject instead 28 | * @param {string} fsPath 29 | * @returns {Project} 30 | */ 31 | _getProjectByFsPath: (fsPath) => { 32 | const projectPath = getNearestPymakrProjectDir(fsPath); 33 | return pymakr.projectsStore.get().find((project) => project.folder === projectPath); 34 | }, 35 | 36 | /** 37 | * @param {vscode.Uri | import('../Project.js').Project} projectOrUri 38 | */ 39 | devicesByProject: (projectOrUri) => { 40 | if (!projectOrUri) throw new errors.MissingProjectError(); 41 | const project = helpers.coerceProject(projectOrUri); 42 | return project?.devices || []; 43 | }, 44 | 45 | /** 46 | * @param {vscode.Uri | import('../Project.js').Project} projectOrUri 47 | */ 48 | devicePickerByProject: (projectOrUri) => { 49 | return helpers.devicePicker(helpers.devicesByProject(projectOrUri)); 50 | }, 51 | 52 | /** 53 | * @param {Device[]} preselectedDevices 54 | */ 55 | devicePicker: async (preselectedDevices = []) => { 56 | const answers = await vscode.window.showQuickPick( 57 | pymakr.devicesStore.get().map((device) => ({ 58 | label: device.displayName, 59 | picked: preselectedDevices.includes(device), 60 | _device: device, 61 | })), 62 | { canPickMany: true } 63 | ); 64 | return answers.map((a) => a._device); 65 | }, 66 | 67 | showAddDeviceToFileExplorerProgressBar: () => { 68 | vscode.window.withProgress({ location: vscode.ProgressLocation.Notification }, async (token) => { 69 | token.report({ message: "Adding device to explorer..." }); 70 | }); 71 | return new Promise((resolve) => vscode.workspace.onDidChangeWorkspaceFolders(resolve)); 72 | }, 73 | 74 | /** 75 | * @param {Object} object 76 | * @returns 77 | */ 78 | objectToTable: (object) => { 79 | const mdString = new vscode.MarkdownString(); 80 | mdString.appendMarkdown("\n"); 81 | mdString.appendMarkdown("|||"); 82 | mdString.appendMarkdown("\n"); 83 | mdString.appendMarkdown("|--|--|"); 84 | mdString.appendMarkdown("\n"); 85 | Object.keys(object).forEach((key) => mdString.appendMarkdown(`|**${key}**|${object[key]}|\n`)); 86 | return mdString; 87 | }, 88 | 89 | /** 90 | * Friendly summary of project, device and system 91 | * @param {Device} device 92 | * @returns 93 | */ 94 | deviceSummary: (device) => { 95 | const staleNote = device.state.stale 96 | ? " _$(alert) Stale. Please connect to refresh. $(alert)_\n\n" 97 | : ""; 98 | const projectName = "" + device.state.pymakrConf.get().name || "unknown"; 99 | 100 | const _action = device.action.get() 101 | const getActionName = ()=> _action 102 | 103 | 104 | const mdString = new vscode.MarkdownString("", true); 105 | mdString.supportHtml = true; 106 | if(device.action.get()) 107 | mdString.appendMarkdown(`Running action: ${getActionName()}\n\n`) 108 | mdString.appendMarkdown("### Project"); 109 | mdString.appendMarkdown("\n"); 110 | mdString.appendMarkdown(staleNote); 111 | mdString.appendMarkdown(helpers.objectToTable({ name: projectName }).value); 112 | mdString.appendMarkdown("\n\n"); 113 | mdString.appendMarkdown("---"); 114 | mdString.appendMarkdown("\n\n"); 115 | mdString.appendMarkdown("### Device"); 116 | mdString.appendMarkdown(helpers.objectToTable(omit(device.raw, "vendorId", "productId")).value); 117 | mdString.appendMarkdown("---"); 118 | mdString.appendMarkdown("\n\n"); 119 | mdString.appendMarkdown("### System"); 120 | mdString.appendMarkdown("\n\n"); 121 | mdString.appendMarkdown(staleNote); 122 | if (typeof device.info === "object") { 123 | const info = { 124 | ...omit(device.info, "nodename"), 125 | "Root path": device.config?.rootPath, 126 | }; 127 | mdString.appendMarkdown(helpers.objectToTable(info).value); 128 | } 129 | // if info isn't available, show the error instead 130 | else if (typeof device.info === "string") mdString.appendMarkdown(device.info); 131 | return mdString; 132 | }, 133 | 134 | /** 135 | * @param {Device} device 136 | */ 137 | deviceStateIcon: (device) => { 138 | const icons = { 139 | offline: new vscode.ThemeIcon("debug-disconnect", { id: "disabledForeground" }), 140 | disconnected: { 141 | dark: path.join(__dirname + "..", "..", "..", "media", "dark", "lightning-muted.svg"), 142 | light: path.join(__dirname + "..", "..", "..", "media", "light", "lightning-muted.svg"), 143 | }, 144 | idle: { 145 | dark: path.join(__dirname + "..", "..", "..", "media", "dark", "lightning.svg"), 146 | light: path.join(__dirname + "..", "..", "..", "media", "light", "lightning.svg"), 147 | }, 148 | script: new vscode.ThemeIcon("github-action"), // or pulse 149 | action: new vscode.ThemeIcon("sync~spin"), 150 | }; 151 | return icons[device.state.main.get()]; 152 | }, 153 | }; 154 | return helpers; 155 | }; 156 | 157 | module.exports = { createVSCodeHelpers }; 158 | -------------------------------------------------------------------------------- /templates/empty/boot.py: -------------------------------------------------------------------------------- 1 | # boot.py -- run on boot-up 2 | -------------------------------------------------------------------------------- /templates/empty/main.py: -------------------------------------------------------------------------------- 1 | # main.py -- put your code here! -------------------------------------------------------------------------------- /templates/empty/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Empty Project" 3 | } -------------------------------------------------------------------------------- /templates/led-example/boot.py: -------------------------------------------------------------------------------- 1 | # boot.py -- run on boot-up 2 | -------------------------------------------------------------------------------- /templates/led-example/main.py: -------------------------------------------------------------------------------- 1 | import pycom 2 | import time 3 | 4 | pycom.heartbeat(False) 5 | 6 | while True: 7 | pycom.rgbled(0xFF0000) # Red 8 | time.sleep(1) 9 | pycom.rgbled(0x00FF00) # Green 10 | time.sleep(1) 11 | pycom.rgbled(0x0000FF) # Blue 12 | time.sleep(1) -------------------------------------------------------------------------------- /templates/led-example/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Led Example" 3 | } -------------------------------------------------------------------------------- /test/fixtures/1-project/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "py_ignore": [ 3 | ".vscode", 4 | ".gitignore", 5 | ".git", 6 | "env", 7 | "venv" 8 | ], 9 | "name": "my project" 10 | } -------------------------------------------------------------------------------- /test/fixtures/2-projects/project-a/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "py_ignore": [ 3 | ".vscode", 4 | ".gitignore", 5 | ".git", 6 | "env", 7 | "venv" 8 | ], 9 | "name": "my project A" 10 | } -------------------------------------------------------------------------------- /test/fixtures/2-projects/project-b/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "py_ignore": [ 3 | ".vscode", 4 | ".gitignore", 5 | ".git", 6 | "env", 7 | "venv" 8 | ], 9 | "name": "my project B" 10 | } -------------------------------------------------------------------------------- /test/fixtures/empty/README.md: -------------------------------------------------------------------------------- 1 | # Fixture for an empty project 2 | 3 | ### Don't delete this file! 4 | It's needed to make sure GIT tracks the folder. -------------------------------------------------------------------------------- /test/fixtures/empty/workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "./" 5 | }, 6 | ] 7 | } -------------------------------------------------------------------------------- /test/fixtures/large-project/boot.py: -------------------------------------------------------------------------------- 1 | # boot.py -- run on boot-up 2 | -------------------------------------------------------------------------------- /test/fixtures/large-project/files/1024: -------------------------------------------------------------------------------- 1 | 630120168872933556881334598528774722730540301737761148741249791788582620067396890648249501719112317237784395683216895831305250870521270255571985472586263868297402589252513153336854504830429079114768856473039770777651785518161894331970896939638042152701414205810006526979629768956447900468876764925225075693344682993623353033098243653821101967956751163280070681204557742080196379034820140439463789754391400064218524252219011637385607008909076828637986428279957226982506723012209707028973167935785465123128236995844177026445006228160682409653243881784194101432216177448993773625432859586960118026445877821181295415683065916295481836124857714067041585070093303796923707436704857198637866278481632025087663237043949649337744462899900600891036843031381084369592658067538176797731544137379800526489517924550481417238318134645521521628664533045580445157165535184996800909702276138752687186396206614870280483586367242742103055380394505767061869616218759844459469109775605853763337311672500960449330729922093320258943101280562204188009641324770488836645565870471489333046025149582778903238842393774103642228067746274725727220632773680009085481866908710227695789556222352456565139128477177325686258296961310167269051969580606922212051899808657236053851069724989435503630766645609244285789601989900218407688776018002285727025325662443143242708945625106813789177151924665358638393440598973475202203691207145423038337752712762234780281587874354039425715831827259511590892587355386236920577146510806953077873014182257519120007916495326480432932213532929549348948201645710922192011687969559211192042451045064628012022859776824084804854823628489222663445113918447460400178360302213466759190557194417987650600573465150447870438914331625841765796699184836958038844430337152908760377426991480535129088381362900376829650394534154802016169020867607053438868831414168006883531903774668108697709859963659550195516157564568511591760122040101275116271751601454590108519563079044771233098192326509762641374481143123766797766539774928164383336302537866696147160040644108145516236238267629538658457402417588347386276958947548199401994914394886440401072624694845911363412035863615860442917274384157443367661700409123245369486911264723688739128841081438165047489638795827537775848259776151434243014776648400331830190298845533599834138522416804465794524366773448053224789397449304170349967371003036709098342683648836860047851589001654875448434232537385604307876160400651864371404129488007511805746704970426166653471356559918503550507168053008493463271632716775402532371672465535192070141161518408465542890586126698425253027059256689094559082347429799540568464595920186726132660300433235023194065622410366922561915624038057128767708243498818529984646130146223416446837853959681926478551351185245357617305235086357942441080276208857281174737975764791656294928847345502417575342384767155717020510009766510400427272563691150688279353971693095738588455319754200008370315172193353273178399139013904538983803202483377535610573826145727950158499350850026689765476160808440103255116817506466180494413973809089007082028589077866270079850556742596244461164304594149371556421777339456012319654960165624841183104262682862542000193567592351078096501572403654729208306295975397400257014716102135327606038554842299272066523856576432285363081049507852220194270923507328573181928088793869420058654997245598501083020182180684796714865929552013419034609152139533165992497913225864786286267512754451171548254388752143724612887718309333359369317778204181856651478677879786470023850872371746053636965270005947028891281953871288516310224912579270743881894639505226549820286521227855594977212627921258517936414350118145009463296903998566660162159741421254548934027144150871002189871132170117831814915342721447802129080896224488496286162263406560745780571344802717018814224784529610938299852527434555941980316746160310822826188627725860849171782814473224414641055027090356447359477882354273093340799733559029235230318744534335397464983030827857793574468907759229267599987149865140192775110635837042294581028361046369550806474535771447022532846081491628279949568760732365662320749352343732090575120894218930734374069087578559940630391979119514632475662944593997085003336251390148708406072118986672264142851062598610314456462266742823140922555877699447688617272918294809675402584234377332236242764432669814633999091035639726549464737639009853410152925556702821601378413953282116543721897205067074904015559209486042054751539588846377145640921336978019126933785701524714953858006482231636644878187948782296950298528103321053559487244988899897652337724897254124370096677919991379987562389519267372557202779451643972204115944160745552459720591353232478171951940668837921694231929506943177791901217502927706711535615597541836223362999238379294728957021961588260308884490830835080434415091364545421150142201420426058184957540805618642857363079444345646906017690725725033528972137713333013578101301652982505450769651012637249163813898799631219293221269460278412042818008183990925677344819903852791245714568472435885652757452012566907893071737502774348667872486228896591114225893413132452168962828635818230201756488658538346112081876880953640693146780054186810328162274663551640212147428511624933194104853962987485361843254304353102413874170756028478790409170894857723104069263440449613398031275466007202141333707803804583822198256301654905839591660199961681286169699442217266608987634020257171605578852951533286747007447789153323208052079902145595837988979547755668183265363705509621766878246645104086427623756644575452692584914888593180923031465372398561762328307076396643438384135847854838568488855107731778702549581492077690948676748111007453554851739438649930555122059210598609659740491710116458173742572239126677560448929961929373072624226537830165125546679726246511886671161621033772456081797605444298587899334939449182596459928376486900678755591899729387384805666131707642150665516135150838697059978578864988024951450844719023391073897655909044904285630800482070267452662208121700878179420678092895409416715345950985034700700995198216893837515848189272897870267438012787021073292569168956028030249996754053628118204002734589686757492383299791351158503206046138873054690570519228066814693209592340899087568789147749703167180189737642250071035119747908211873650300378654288138177418593438533459346756066990848166997305345467112862325287096825114682983087334395217527062138309470802579825554672127643554555332958391660734580201094723647626626233134332992952280065676289026992755047044087573313826688930222498127903026478882701474279017930062108089525491664067082589165593603494212013570118905531494883732950063891159722861161556412963629131459483817316564116479831592204552379367241660159340897347176926336649257782098839552476753182211948895562569152828936439916162960668281359200668890232581336220720116368027702208367304672026489160489124423613393843345880384164509616574257156745337298458795403867316481158221768062307091739939520199002808630400717771666490345265063961202068645468372586719638115207900905326684454397997439136173211972301423169080502952590756187433004034418547743075946387742744593089004736572793416318203532316106260517949259884871705311733751824399060840704142969063750762129823677338321473287720699420694922001254851382212713457982744710935177143009726564147407750336827709292012503701221739099615679381632432452738459028366441145528420052796987006831651682151309712076829490600709535612218346835115644718386122795457797081147232181084786 -------------------------------------------------------------------------------- /test/fixtures/large-project/generate.js: -------------------------------------------------------------------------------- 1 | const { writeFileSync, mkdirSync, rmdir, rmdirSync, rmSync } = require("fs"); 2 | 3 | const FILES = 75; 4 | const CHARS_PER_FILE = 7500; 5 | 6 | try { 7 | rmSync(`${__dirname}/files`, {recursive: true}); 8 | } catch (_err) {} 9 | mkdirSync(`${__dirname}/files`, { recursive: true }); 10 | 11 | for (let i = 0; i < FILES; i++) { 12 | let str = ""; 13 | for (let i = 0; i < CHARS_PER_FILE; i++) { 14 | // console.log(Math.random().toString()) 15 | str += Math.random().toString()[2]; 16 | } 17 | writeFileSync(`${__dirname}/files/${Math.floor(Math.random() * 10000)}`, str); 18 | // console.log(str) 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/large-project/main.py: -------------------------------------------------------------------------------- 1 | # main.py -- put your code here! -------------------------------------------------------------------------------- /test/fixtures/large-project/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Empty Project" 3 | } -------------------------------------------------------------------------------- /test/fixtures/project-with-dist_dir/device/script.py: -------------------------------------------------------------------------------- 1 | print("hello world") 2 | -------------------------------------------------------------------------------- /test/fixtures/project-with-dist_dir/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my project", 3 | "dist_dir": "device" 4 | } -------------------------------------------------------------------------------- /test/runTest.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const { runTests } = require("@vscode/test-electron"); 4 | const { createFixture, getArg } = require("./utils"); 5 | 6 | async function main() { 7 | try { 8 | // The folder containing the Extension Manifest package.json 9 | // Passed to `--extensionDevelopmentPath` 10 | const extensionDevelopmentPath = path.resolve(__dirname, "../"); 11 | 12 | { 13 | const fixtureName = "empty"; 14 | const extensionTestsPath = path.resolve(__dirname, "./suite/runIntegrationTests.js"); 15 | const fixturePath = createFixture(fixtureName); 16 | await runTests({ 17 | extensionDevelopmentPath, 18 | extensionTestsPath, 19 | launchArgs: [`${fixturePath}/workspace.code-workspace`], 20 | extensionTestsEnv: { fixturePath, fixtureName, pattern: getArg('--pattern') }, 21 | }); 22 | } 23 | 24 | // /** 25 | // * Another test 26 | // */ 27 | // { 28 | // // The test script to run 29 | // const extensionTestsPath = path.resolve(__dirname, "./suite/runIntegrationTests"); 30 | // // The fixture to be used for the tests 31 | // const fixturePath = createFixture("empty"); 32 | // await runTests({ 33 | // extensionDevelopmentPath, 34 | // extensionTestsPath, 35 | // launchArgs: [fixturePath], 36 | // extensionTestsEnv: { fixturePath }, 37 | // }); 38 | // } 39 | } catch (err) { 40 | console.error("Failed to run tests", err); 41 | process.exit(1); 42 | } 43 | } 44 | 45 | main(); 46 | -------------------------------------------------------------------------------- /test/suite/integration/busy-devices.test.js: -------------------------------------------------------------------------------- 1 | const { disconnectAllDevices, sleep } = require("./utils"); 2 | 3 | test("busy devices", async () => { 4 | const device = pymakr.devicesStore.get()[0]; 5 | 6 | test("can disconnect a busy device", async () => { 7 | test("can connect the device", async () => { 8 | await device.connect(); 9 | assert(device.connected.get()); 10 | test("can safeboot device", async () => { 11 | await device.safeBoot(); 12 | assert(device.connected.get()); 13 | }); 14 | }); 15 | test("device is idle", async () => { 16 | if (device.busy.get()) await new Promise((resolve) => device.busy.next(resolve)); 17 | assert(!device.busy.get()); 18 | }); 19 | test("can run a temporary hanging script", async () => { 20 | assert(!device.busy.get(), "device should be idle before the script"); 21 | const promise = device.runScript("import time\r\ntime.sleep(1)"); 22 | assert(device.busy.get(), "device should be busy immediately after running the script"); 23 | await sleep(500); 24 | assert(device.busy.get(), "devise should be busy 500 ms into the 1s script"); 25 | await promise; 26 | assert(!device.busy.get(), "device should not be busy after the script has finished (1s)"); 27 | }); 28 | test("can run a hanging script", async () => { 29 | await device.runScript("import time\r\nwhile True: time.sleep(1)", { resolveBeforeResult: true }); 30 | await sleep(200); 31 | assert(device.connected.get()); 32 | assert(device.busy.get()); 33 | }); 34 | test("can disconnect the device", async () => { 35 | await device.disconnect(); 36 | assert(!device.connected.get()); 37 | }); 38 | test("can connect to a device with a running script", async () => { 39 | await device.connect(); 40 | assert(device.connected.get()); 41 | }); 42 | test("running script continues to run after connection", async () => { 43 | assert(device.busy.get()); 44 | }); 45 | test("can kill running script", async () => { 46 | await device.safeBoot(); 47 | assert(!device.busy.get()); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/suite/integration/devmode.adv.test.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const { existsSync, writeFileSync } = require("fs"); 3 | const { join } = require("path"); 4 | const vscode = require("vscode"); 5 | 6 | const projectPath1 = join(workspaceDir, "project-1"); 7 | /** @type {Project} */ 8 | let project; 9 | /** @type {Device} */ 10 | let device; 11 | 12 | const readUntil = (string) => new Promise((resolve) => device.readUntil(string, resolve)); 13 | 14 | beforeAll(async () => { 15 | // Create a project and add a device 16 | // todo should createProject resolve promise only once project is added to projectsStore? 17 | await pymakr.commands.createProject(vscode.Uri.parse(projectPath1), { 18 | name: "my project", 19 | dev: { simulateDeepSleep: true }, 20 | }); 21 | await new Promise((resolve) => pymakr.projectsStore.next(resolve)); 22 | project = pymakr.projectsStore.get()[0]; 23 | device = pymakr.devicesStore.get()[0]; 24 | project.setDevices([device]); 25 | assert(existsSync(projectPath1)); 26 | assert.equal(project.name, "my project"); 27 | assert(device); 28 | }); 29 | 30 | test("can fake deepsleep in devmode", async () => { 31 | test("can put project in dev mode", async () => { 32 | await device.connect(); 33 | // await pymakr.commands.eraseDevice({ device }, "empty"); 34 | // device.onTerminalData((data) => console.log("data", data)); 35 | await pymakr.commands.startDevMode({ project }); 36 | assert(project.watcher.active); 37 | assert.equal(project.watcher.deviceManagers.length, 1); 38 | }); 39 | test("first save in devmode install devtools and restarts", async () => { 40 | writeFileSync(projectPath1 + "/main.py", 'print("hello world")'); 41 | await readUntil("uploading Pymakr devtools"); 42 | await readUntil("patching boot.dev"); 43 | await readUntil("changed. Restarting..."); 44 | console.log("after reset"); 45 | // todo test only works when run alone 46 | test("sys.path includes _pymakr_dev", async () => { 47 | await readUntil(">>>"); 48 | await readUntil(">>>"); 49 | console.log("before sys check"); 50 | const result = await device.runScript("import sys\nprint(sys.path)"); 51 | assert(result.match(/\/_pymakr_dev/), "did not find _pymakr_dev. Found: " + result.toString()); 52 | }); 53 | 54 | test("pymakr_dev/fake_machine.py exists on device", async () => { 55 | const result = await device.runScript("print(os.listdir('_pymakr_dev'))"); 56 | assert(result.match("fake_machine.py")); 57 | }); 58 | 59 | test("can import fake_machine in devmode", async () => { 60 | device.runScript("import fake_machine\nfake_machine.sleep(1)"); 61 | await readUntil("fake_machine.sleep end"); 62 | }); 63 | 64 | test("machine.sleep gets transformed to fake_machine.sleep", async () => { 65 | test("can write main.py", async () => { 66 | writeFileSync( 67 | projectPath1 + "/main.py", 68 | ["import machine", "# machine.sleep(100)", "# machine.deepSleep(100)", 'print("booted")'].join("\n") 69 | ); 70 | await readUntil("booted"); 71 | }); 72 | test("can read main.py", async () => { 73 | const result = await device.adapter.getFile("main.py"); 74 | assert.equal( 75 | result.toString(), 76 | ["import fake_machine", "# fake_machine.sleep(100)", "# fake_machine.sleep(100)", 'print("booted")'].join( 77 | "\n" 78 | ) 79 | ); 80 | }); 81 | }); 82 | 83 | test("can stop devMode", async () => { 84 | await pymakr.commands.stopDevMode({ project }); 85 | assert(!project.watcher.active); 86 | assert.equal(project.watcher.deviceManagers.length, 0); 87 | await new Promise((resolve) => setTimeout(resolve, 100)); 88 | }); 89 | 90 | // todo can't interrupt loop. needs fix 91 | // solution could be to have fake_machine.sleep print an event indicating a 100ms window for sending ctrl+f 92 | 93 | // test("main.py with deepsleep keeps looping", async () => { 94 | // let madeItPastSleep = false; // should stay false 95 | // writeFileSync( 96 | // projectPath1 + "/main.py", 97 | // ["import machine", "", 'print("before sleep")', "machine.sleep(100)", 'print("after sleep")'].join("\n") 98 | // ); 99 | // readUntil("after sleep").then(() => (madeItPastSleep = true)); 100 | // await readUntil("before sleep"); 101 | // await readUntil("before sleep"); 102 | // await readUntil("before sleep"); 103 | // assert(!madeItPastSleep); 104 | // }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/suite/integration/devmode.basic.test.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const { existsSync, writeFileSync } = require("fs"); 3 | const { join } = require("path"); 4 | const vscode = require("vscode"); 5 | 6 | const projectPath1 = join(workspaceDir, "project-1"); 7 | /** @type {Project} */ 8 | let project; 9 | /** @type {Device} */ 10 | let device; 11 | 12 | beforeAll(async () => { 13 | // Create a project and add a device 14 | // todo should createProject resolve promise only once project is added to projectsStore? 15 | await pymakr.commands.createProject(vscode.Uri.parse(projectPath1), { name: "my project" }); 16 | await new Promise((resolve) => pymakr.projectsStore.next(resolve)); 17 | project = pymakr.projectsStore.get()[0]; 18 | device = pymakr.devicesStore.get()[0]; 19 | project.setDevices([device]); 20 | assert(existsSync(projectPath1)); 21 | assert.equal(project.name, "my project"); 22 | assert(device); 23 | }); 24 | 25 | test("can use devmode", async () => { 26 | test("can put project in devmode", async () => { 27 | await pymakr.commands.startDevMode({ project }); 28 | assert(project.watcher.active); 29 | assert.equal(project.watcher.deviceManagers.length, 1); 30 | }); 31 | test("connected out of sync devices are updated when devmode is started", async () => { 32 | await pymakr.commands.stopDevMode({ project }); 33 | await device.connect(); 34 | writeFileSync(projectPath1 + "/main.py", 'print("hello world1")\n'); 35 | pymakr.commands.startDevMode({ project }); 36 | await new Promise((resolve) => device.readUntil("hello world1", resolve)); 37 | }); 38 | test("saving a file, uploads it and restarts device", async () => { 39 | writeFileSync(projectPath1 + "/main.py", 'print("hello world2")\n'); 40 | await new Promise((resolve) => device.readUntil("hello world2", resolve)); 41 | }); 42 | test("devices without looping scripts show idle terminal", async () => { 43 | writeFileSync(projectPath1 + "/main.py", 'print("hello world3")\n'); 44 | await new Promise((resolve) => device.readUntil("hello world3", resolve)); 45 | if (device.busy.get()) await new Promise((resolve) => device.busy.subscribe((val) => !val && resolve())); 46 | }); 47 | test("devices with looping scripts show as running user scripts", async () => { 48 | writeFileSync(projectPath1 + "/main.py", 'import time\nwhile True:\n print("waiting...")\n time.sleep(0.5)\n'); 49 | await new Promise((resolve) => device.readUntil("waiting...", resolve)); 50 | await new Promise((resolve) => device.readUntil("waiting...", resolve)); 51 | assert.equal(device.state.main.get(), "script"); 52 | }); 53 | test("devices with looping scripts will be stopped and restarted on file changes", async () => { 54 | writeFileSync(projectPath1 + "/main.py", 'print("hello world again")'); 55 | await new Promise((resolve) => device.readUntil("hello world again", resolve)); 56 | }); 57 | test("devices with looping scripts will be stopped and restarted on file changes repeatedly", async () => { 58 | writeFileSync(projectPath1 + "/main.py", 'print("hello world again again")'); 59 | await new Promise((resolve) => device.readUntil("hello world again again", resolve)); 60 | }); 61 | test('bad code in main.py is printed correctly"', async () => { 62 | writeFileSync(projectPath1 + "/main.py", "bad code"); 63 | await new Promise((resolve) => device.readUntil("SyntaxError: invalid syntax", resolve)); 64 | }); 65 | test("can stop devMode", async () => { 66 | await pymakr.commands.stopDevMode({ project }); 67 | assert(!project.watcher.active); 68 | assert.equal(project.watcher.deviceManagers.length, 0); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/suite/integration/file-management/_sample/ignoreme/ignored-file-1.md: -------------------------------------------------------------------------------- 1 | I should not be uploaded -------------------------------------------------------------------------------- /test/suite/integration/file-management/_sample/ignoreme/ignored-file-2.md: -------------------------------------------------------------------------------- 1 | I should not be uploaded -------------------------------------------------------------------------------- /test/suite/integration/file-management/_sample/includeme/includeme-file-1.md: -------------------------------------------------------------------------------- 1 | should be uploaded -------------------------------------------------------------------------------- /test/suite/integration/file-management/_sample/includeme/includeme-file-2.md: -------------------------------------------------------------------------------- 1 | should be uploaded -------------------------------------------------------------------------------- /test/suite/integration/file-management/_sample/main.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycom/pymakr-vsc/53263961b3fc9377c92312ed4d7836d00fb86f14/test/suite/integration/file-management/_sample/main.py -------------------------------------------------------------------------------- /test/suite/integration/file-management/_sample/pymakr.conf: -------------------------------------------------------------------------------- 1 | { 2 | "py_ignore": [ 3 | "ignoreme/**" 4 | ], 5 | "name": "sample" 6 | } -------------------------------------------------------------------------------- /test/suite/integration/file-management/_sample/sample-file-1.md: -------------------------------------------------------------------------------- 1 | # Hello -------------------------------------------------------------------------------- /test/suite/integration/file-management/file-management.test.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { posix, join } = require("path"); 3 | 4 | test("file management", async () => { 5 | // FIXME: dependency on other tests that are run before this one 6 | const device = pymakr.devicesStore.get()[0]; 7 | // fixme: if (!device) return skip test ; // skip device tests if no device is connected 8 | 9 | test("can clear device", async () => { 10 | await device.connect(); 11 | await device.adapter.remove(device.config.rootPath, true); 12 | const files = await device.adapter.listFiles(device.config.rootPath, { recursive: false }); 13 | assert.deepEqual(files, []); 14 | }); 15 | 16 | test("can upload a file", async () => { 17 | const uri = vscode.Uri.file(join(__dirname, "_sample/sample-file-1.md")); 18 | await pymakr.commands.upload(uri, device, "/sample-file-1.md"); 19 | const files = await device.adapter.listFiles(device.config.rootPath, { recursive: false }); 20 | assert.equal(files.length, 1); 21 | assert.equal(files[0].filename, posix.join(device.config.rootPath, "/sample-file-1.md")); 22 | }); 23 | 24 | test("can upload a dir", async () => { 25 | const uri = vscode.Uri.file(__dirname + "/_sample"); 26 | await pymakr.commands.upload(uri, device, "/"); 27 | const files = await device.adapter.listFiles(device.config.rootPath, { recursive: true }); 28 | assert.deepEqual( 29 | files.map((f) => f.filename), 30 | [ 31 | posix.join(device.config.rootPath, ""), 32 | posix.join(device.config.rootPath, "folder"), 33 | posix.join(device.config.rootPath, "folder/large-file.py"), 34 | posix.join(device.config.rootPath, "includeme"), 35 | posix.join(device.config.rootPath, "includeme/includeme-file-1.md"), 36 | posix.join(device.config.rootPath, "includeme/includeme-file-2.md"), 37 | posix.join(device.config.rootPath, "main.py"), 38 | posix.join(device.config.rootPath, "pymakr.conf"), 39 | posix.join(device.config.rootPath, "sample-file-1.md"), 40 | ] 41 | ); 42 | }); 43 | 44 | test("can erase and provision a device", async () => { 45 | await pymakr.commands.eraseDevice({ device }, "empty"); 46 | const files = await device.adapter.listFiles(device.config.rootPath, { recursive: false }); 47 | assert.equal(files.length, 3); 48 | assert.deepEqual( 49 | files.map((f) => f.filename), 50 | [ 51 | posix.join(device.config.rootPath, "boot.py"), 52 | posix.join(device.config.rootPath, "main.py"), 53 | posix.join(device.config.rootPath, "pymakr.conf"), 54 | ] 55 | ); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/suite/integration/file-management/probs.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../probs.config') -------------------------------------------------------------------------------- /test/suite/integration/file-system-provider.test.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const vscode = require("vscode"); 3 | 4 | const createUri = (device) => (path) => 5 | vscode.Uri.from({ scheme: "serial", authority: device.address, path: `/flash${path}` }); 6 | 7 | test("file system provider", async () => { 8 | assert.equal(vscode.workspace.workspaceFolders.length, 1, "windows has one workspace folder before mount"); 9 | const device = pymakr.devicesStore.get()[0]; 10 | const uri = createUri(device); 11 | 12 | await device.connect(); 13 | 14 | test("can mount device", async () => { 15 | await pymakr.commands.addDeviceToFileExplorer({ device }); 16 | assert.equal(vscode.workspace.workspaceFolders.length, 2); 17 | assert.equal(vscode.workspace.workspaceFolders[1].uri.scheme, "serial"); 18 | }); 19 | 20 | test("can create dir", async () => { 21 | pymakr.fileSystemProvider.createDirectory(uri("/foo")); 22 | const file = await pymakr.fileSystemProvider.stat(uri("/foo")); 23 | assert.equal(file.type, vscode.FileType.Directory); 24 | }); 25 | 26 | test("can create file", async () => { 27 | pymakr.fileSystemProvider.writeFile(uri("/foo/file.txt"), Buffer.from("hello world")); 28 | const content = await pymakr.fileSystemProvider.readFile(uri("/foo/file.txt")); 29 | assert.equal(content, "hello world"); 30 | }); 31 | 32 | test("can rename file", async () => { 33 | await pymakr.fileSystemProvider.rename(uri("/foo/file.txt"), uri("/foo/renamed.txt")); 34 | const filesAfterRename = await pymakr.fileSystemProvider.readDirectory(uri("/foo")); 35 | assert.equal(filesAfterRename.length, 1); 36 | const content = await pymakr.fileSystemProvider.readFile(uri("/foo/renamed.txt")); 37 | assert.equal(content, "hello world"); 38 | }); 39 | 40 | test("can delete file", async () => { 41 | const filesBeforeDelete = await pymakr.fileSystemProvider.readDirectory(uri("/foo")); 42 | assert.equal(filesBeforeDelete.length, 1); 43 | await pymakr.fileSystemProvider.delete(uri("/foo/renamed.txt")); 44 | const filesAfterDelete = await pymakr.fileSystemProvider.readDirectory(uri("/foo")); 45 | assert.equal(filesAfterDelete.length, 0); 46 | }); 47 | 48 | test("can delete contentful folder", async () => { 49 | const _files = await pymakr.fileSystemProvider.readDirectory(uri("/foo")); 50 | assert.equal(_files.length, 0, "should have no files. File: " + _files); 51 | pymakr.fileSystemProvider.writeFile(uri("/foo/file.txt"), Buffer.from("hello world")); 52 | const files = await pymakr.fileSystemProvider.readDirectory(uri("/foo")); 53 | assert.equal(files.length, 1, "should only have one file. Files: " + files.toString()); 54 | const rootFilesBefore = await pymakr.fileSystemProvider.readDirectory(uri("/")); 55 | assert.deepEqual(rootFilesBefore, [["foo", 2]]); 56 | await pymakr.fileSystemProvider.delete(uri("/foo")); 57 | const rootFilesAfter = await pymakr.fileSystemProvider.readDirectory(uri("/")); 58 | assert.deepEqual(rootFilesAfter, []); 59 | }); 60 | 61 | test("can dismount device", async () => { 62 | const didRemove = vscode.workspace.updateWorkspaceFolders(1, 1); 63 | assert(didRemove); 64 | await new Promise((resolve) => vscode.workspace.onDidChangeWorkspaceFolders(resolve)); 65 | assert.equal(vscode.workspace.workspaceFolders.length, 1); 66 | assert.equal(vscode.workspace.workspaceFolders[0].uri.scheme, "file"); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/suite/integration/general-devices.test.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const { readFileSync } = require("fs"); 3 | const vscode = require("vscode"); 4 | const { timer } = require("./utils"); 5 | 6 | test("Can find devices", async () => { 7 | assert(pymakr.devicesStore.get().length >= 1); 8 | 9 | test("device", async () => { 10 | const device = pymakr.devicesStore.get()[0]; 11 | await device.connect(); 12 | 13 | test("can connect", () => { 14 | assert(device.connected.get()); 15 | }); 16 | 17 | test("can create REPL", async () => { 18 | const welcomeMsgPromise = new Promise((resolve) => 19 | device.onTerminalData((data) => { 20 | if (data.match(/MicroPython/)) resolve(data); 21 | }) 22 | ); 23 | 24 | pymakr.terminalsStore.create(device); 25 | const terminal = [...vscode.window.terminals].pop(); 26 | 27 | const welcomeMsg = await welcomeMsgPromise; 28 | 29 | /**@type {vscode.TerminalOptions} */ 30 | const creationOptions = terminal.creationOptions; 31 | 32 | test("created REPL is last terminal", () => assert(creationOptions.shellArgs.includes("serial"))); 33 | 34 | test("created repl shows welcome msg", async () => { 35 | assert.match(welcomeMsg, /MicroPython/, "Could not detect a MicroPython device: " + welcomeMsg); 36 | assert.match(welcomeMsg, /\r\n>>>/, "No repl prompt. Received: " + welcomeMsg); 37 | // todo: low-prio - >>> test fails on esp8266 as the welcomesting is truncated 38 | }); 39 | 40 | test("can use print command", async () => { 41 | if (device.busy.get()) await new Promise(device.busy.next); 42 | terminal.sendText('print("foo")\n'); 43 | await new Promise((resolve) => device.readUntil(['print\\("foo"\\)', "foo", ">>> "].join("\r\n"), resolve)); 44 | }); 45 | 46 | test("can run large script", async () => { 47 | pymakr.commands.runScript( 48 | readFileSync(__dirname + "/file-management/_sample/folder/large-file.py", "utf8"), 49 | device 50 | ); 51 | await new Promise((resolve) => device.readUntil("number is\r\n1000", resolve)); 52 | }); 53 | 54 | test("can configure chunking", async () => { 55 | const script = `print('Hello world. How are you today?')\n`; 56 | 57 | const withoutChunk = await timer(() => device.runScript(script)); 58 | await device._config.set({ ...device.config, adapterOptions: { chunkSize: 8, chunkDelay: 15 } }); 59 | await device.disconnect(); 60 | await device.connect(); 61 | const withChunk = await timer(() => device.runScript(script)); 62 | await device._config.set({ ...device.config, adapterOptions: { chunkSize: null, chunkDelay: null } }); 63 | await device.disconnect(); 64 | await device.connect(); 65 | 66 | assert.equal(withChunk.result, "Hello world. How are you today?"); 67 | assert.equal(withoutChunk.result, "Hello world. How are you today?"); 68 | assert( 69 | withChunk.time > withoutChunk.time * 1.5, 70 | `Chunking should be slower ${withChunk.time} > (${withoutChunk.time} * 1.5)` 71 | ); 72 | }); 73 | 74 | test("can disconnect", async () => { 75 | await device.disconnect(); 76 | assert(!device.connected.get()); 77 | }); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/suite/integration/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare const pymakr: PyMakr 6 | /** pymakr/test/workspaces/integration */ 7 | declare const workspaceDir: string 8 | 9 | declare const PROJECT_STORE_TIMEOUT: number -------------------------------------------------------------------------------- /test/suite/integration/micropython.test.js: -------------------------------------------------------------------------------- 1 | const device = pymakr.devicesStore.get()[0]; 2 | 3 | const busyChange = (device) => new Promise((resolve) => device.busy.next(resolve)); 4 | 5 | // tests pertaining to the capabilities of micropython-ctl-cont 6 | 7 | test("micropython", async () => { 8 | await device.connect(); 9 | test("x06 safeboots device", async () => { 10 | assert(!device.busy.get()); 11 | device.adapter.sendData("\x06"); 12 | await busyChange(device); 13 | assert(device.busy.get()); 14 | await busyChange(device); 15 | assert(!device.busy.get()); 16 | }); 17 | test("can use rawRepl", async () => { 18 | console.log("[TEST] RUN SCRIPT"); 19 | const rawReplPromise = device.runScript("import time\ntime.sleep(0.3)"); 20 | await rawReplPromise; 21 | }); 22 | test("rawrepl can handle safeboot (ctrl + f / 0x06)", async () => { 23 | assert(!device.busy.get()); 24 | const rawReplPromise = device.runScript("import time\nprint('hello')\ntime.sleep(100)"); 25 | await new Promise((resolve) => setTimeout(resolve, 200)); 26 | 27 | // process.env.debug = "silly"; 28 | device.adapter.sendData("\x06"); 29 | console.log("state", device.adapter.getState()); 30 | assert.equal(device.adapter.getState().receivingResponseSubState, "SCRIPT_ABORTED"); 31 | await rawReplPromise; 32 | }); 33 | test("rawrepl can handle stopScript(3)", async () => { 34 | assert(!device.busy.get()); 35 | const rawReplPromise = device.runScript("import time\nprint('hello')\ntime.sleep(100)"); 36 | await new Promise((resolve) => setTimeout(resolve, 200)); 37 | 38 | // process.env.debug = "silly"; 39 | await device.stopScript(3); 40 | console.log("state", device.adapter.getState()); 41 | assert.equal(device.adapter.getState().receivingResponseSubState, "SCRIPT_ABORTED"); 42 | await rawReplPromise; 43 | }); 44 | test("rawrepl can handle stopScript(0)", async () => { 45 | assert(!device.busy.get()); 46 | const rawReplPromise = device.runScript("import time\nprint('hello')\ntime.sleep(100)"); 47 | await new Promise((resolve) => setTimeout(resolve, 200)); 48 | 49 | // process.env.debug = "silly"; 50 | await device.stopScript(0); 51 | console.log("state", device.adapter.getState()); 52 | assert.equal(device.adapter.getState().receivingResponseSubState, "SCRIPT_ABORTED"); 53 | await rawReplPromise; 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/suite/integration/probs.config.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { resetFixture } = require("../../utils"); 3 | 4 | const workspaceDir = process.env.fixturePath; 5 | const PROJECT_STORE_TIMEOUT = 10000; 6 | 7 | /** 8 | * @param {Device} device 9 | */ 10 | const prepDevice = async (device) => { 11 | device.adapter.__proxyMeta.clearQueue(); 12 | device.adapter.__proxyMeta.skipCurrent(); 13 | console.log("[PREP] Waiting for idle..."); 14 | await device.adapter.__proxyMeta.idle; 15 | await device.connect(); 16 | console.log("[PREP] Safebooting..."); 17 | await pymakr.commands.safeBootDevice({ device }); 18 | // TODO timeout here should not be required. High priority! 19 | console.log("[PREP] Safebooting complete!"); 20 | // await new Promise((resolve) => setTimeout(resolve, 1500)); 21 | console.log("[PREP] Erasing..."); 22 | await pymakr.commands.eraseDevice({ device }); 23 | console.log("[PREP] Erasing complete!"); 24 | console.log("[PREP] Disconnecting..."); 25 | await device.disconnect(); 26 | console.log("[PREP] Disconnecting complete!"); 27 | }; 28 | 29 | module.exports = { 30 | async setupFile(file) { 31 | console.log("[PREP]", file); 32 | resetFixture(workspaceDir); 33 | /** @type {PyMakr} */ 34 | let pymakr; 35 | await vscode.commands.executeCommand("pymakr.getPymakr", (_pymakr) => (pymakr = _pymakr)); 36 | pymakr.log.level = 3; 37 | Object.assign(global, { pymakr, workspaceDir, PROJECT_STORE_TIMEOUT }); 38 | 39 | await Promise.all(pymakr.devicesStore.get().map(prepDevice)); 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /test/suite/integration/projects.test.js: -------------------------------------------------------------------------------- 1 | const { existsSync, readFileSync, writeFileSync, mkdirSync } = require("fs"); 2 | const vscode = require("vscode"); 3 | const { join } = require("path"); 4 | 5 | const projectPath1 = join(workspaceDir, "project-with-dist_dir"); 6 | const getProject = () => pymakr.projectsStore.get()[0]; 7 | const getDevice = () => pymakr.devicesStore.get()[0]; 8 | 9 | test("can find 0 projects and 1 workspace", () => { 10 | assert.equal(pymakr.projectsStore.get().length, 0); 11 | assert.equal(vscode.workspace.workspaceFolders.length, 1); 12 | }); 13 | 14 | test("can create project", async () => { 15 | await pymakr.commands.createProject(vscode.Uri.parse(projectPath1), { name: "my project", dist_dir: "device" }); 16 | assert(existsSync(projectPath1)); 17 | mkdirSync(projectPath1 + "/device"); 18 | test("new project has pymakr.conf", () => { 19 | const configFile = readFileSync(`${projectPath1}/pymakr.conf`, "utf8"); 20 | const config = JSON.parse(configFile); 21 | assert.equal(config.name, "my project"); 22 | }); 23 | test("projects store updates on new project", () => { 24 | return new Promise((resolve, reject) => { 25 | setTimeout(() => reject("project store timed out"), PROJECT_STORE_TIMEOUT); 26 | pymakr.projectsStore.next(resolve); 27 | }); 28 | }); 29 | test("new project shows up in vscode", async () => { 30 | assert.equal(pymakr.projectsStore.get().length, 1); 31 | assert.equal(getProject().name, "my project"); 32 | }); 33 | }); 34 | 35 | test("can add device to project", async () => { 36 | const project = getProject(); 37 | project.setDevices([getDevice()]); 38 | }); 39 | 40 | test("can upload project", async () => { 41 | test("has correct absoluteDistDir", () => { 42 | const project = getProject(); 43 | assert.match(project.absoluteDistDir, /project-with-dist_dir..?device/); 44 | }); 45 | test("uploads from dist_dir", async () => { 46 | writeFileSync(projectPath1 + "/device/script.py", 'print("hello world")'); 47 | const device = getDevice(); 48 | const project = getProject(); 49 | await device.connect(); 50 | await pymakr.commands.uploadProject({ device, project }); 51 | }); 52 | test("can read uploaded file", async () => { 53 | const file = await getDevice().adapter.getFile("script.py"); 54 | assert.equal(file.toString(), 'print("hello world")'); 55 | }); 56 | }); 57 | 58 | test("can change dist_dir to root", () => { 59 | const configFile = readFileSync(`${projectPath1}/pymakr.conf`, "utf8"); 60 | const config = JSON.parse(configFile); 61 | config.dist_dir = ""; 62 | writeFileSync(`${projectPath1}/pymakr.conf`, JSON.stringify(config, null, 2)); 63 | const device = getDevice(); 64 | const project = getProject(); 65 | test('"upload project" uploads the dist_dir', async () => { 66 | const root = device.config.rootPath; 67 | await pymakr.commands.uploadProject({ device, project }); 68 | const files = await device.adapter.listFiles(root, { recursive: true }); 69 | const filenames = files.map((file) => file.filename); 70 | assert.deepEqual(filenames, [`${root}`, `${root}/device`, `${root}/device/script.py`, `${root}/pymakr.conf`]); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/suite/integration/stress.test.js: -------------------------------------------------------------------------------- 1 | test("stress test", async () => { 2 | const device = pymakr.devicesStore.get()[0]; 3 | 4 | test("5x (re)connect", async () => { 5 | let error; 6 | try { 7 | const count = 5; 8 | for (let i = 0; i <= count; i++) { 9 | await device.connect(); 10 | assert(device.connected.get(), 'should be connected'); 11 | await device.disconnect(); 12 | assert(!device.connected.get(), 'should not be connected'); 13 | } 14 | } catch (err) { 15 | error = err; 16 | } 17 | assert(!error, error); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/suite/integration/utils.js: -------------------------------------------------------------------------------- 1 | const disconnectAllDevices = () => Promise.all(pymakr.devicesStore.get().map((device) => device.disconnect())); 2 | const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)); 3 | 4 | const timer = async (callback) => { 5 | const start = Date.now(); 6 | const result = await callback(); 7 | const time = Date.now() - start; 8 | return { time, result }; 9 | }; 10 | 11 | module.exports = { disconnectAllDevices, sleep, timer }; 12 | -------------------------------------------------------------------------------- /test/suite/runIntegrationTests.js: -------------------------------------------------------------------------------- 1 | async function run() { 2 | const { probs } = await import("probs"); 3 | await probs([__dirname + "/integration"], { 4 | runner: "main", 5 | concurrency: 1, 6 | reporter: "consoleReporter", 7 | timeout: 12000, 8 | // watch: true, 9 | // use npm run test:integration -- --pattern myfile.test.js for file patterns 10 | pattern: process.env.pattern ? [process.env.pattern] : [], 11 | }); 12 | } 13 | 14 | module.exports = { 15 | run, 16 | }; 17 | -------------------------------------------------------------------------------- /test/utils/index.js: -------------------------------------------------------------------------------- 1 | const { cpSync, rmSync, readdirSync, writeFileSync, readFileSync, existsSync } = require("fs"); 2 | const { resolve } = require("path"); 3 | 4 | const createFixture = (name, path) => { 5 | path = path || resolve(`test/temp/${name}.${Date.now()}`); 6 | if (existsSync(path)) rmSync(path, { recursive: true }); 7 | cpSync(`test/fixtures/${name}`, path, { recursive: true }); 8 | writeFileSync(path + "/_FIXTURE_ORIGIN", resolve(`test/fixtures/${name}`)); 9 | return path; 10 | }; 11 | 12 | const resetFixture = (path) => { 13 | const fixtureOrigin = readFileSync(path + "/_FIXTURE_ORIGIN", "utf-8"); 14 | readdirSync(path).forEach((file) => rmSync(`${path}/${file}`, { recursive: true })); 15 | cpSync(fixtureOrigin, path, { recursive: true }); 16 | writeFileSync(path + "/_FIXTURE_ORIGIN", fixtureOrigin); 17 | }; 18 | 19 | /** returns value of process arguments, like "--pattern some-file.js" */ 20 | const getArg = (name) => { 21 | const index = process.argv.indexOf(name) + 1; 22 | if (index) return process.argv[index]; 23 | }; 24 | 25 | module.exports = { createFixture, resetFixture, getArg }; 26 | 27 | // executes scripts from command line 28 | // eg.: utils/index.js createFixture led-example temp/test/integration-test 29 | (function execFromArg() { 30 | const [_bin, _script, fn, ...params] = process.argv; 31 | if (fn && module.exports[fn]) module.exports[fn](...params); 32 | })(); 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "checkJs": true, 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "lib": [ 9 | "ES2020" 10 | ], 11 | "resolveJsonModule": true, 12 | "rootDir": ".", 13 | "outDir": "./not_actually_used", 14 | }, 15 | "include": [ 16 | "src/**/*.js", 17 | "src/**/*.ts", 18 | "test/**/*.js", 19 | "test/**/*.ts", 20 | "types/typedef.js", 21 | "types/store.js" 22 | ], 23 | "exclude": [ 24 | "node_modules/**" 25 | ] 26 | } -------------------------------------------------------------------------------- /types/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {function():void} Unsubscribe 3 | */ 4 | 5 | /** 6 | * @template T 7 | * @namespace Store 8 | * @typedef {object} Writable 9 | * @prop {function():T} get Returns the current store value 10 | * @prop {function(T):void} set Set the store value 11 | * @prop {function(function(T):T):void} update Update the store value 12 | * @prop {function((payload: T, unsub: Unsubscribe)=>void):Unsubscribe} subscribe Subscribe to the store value 13 | * @prop {function(function(T):void):void} next Subscribe to the store value for a single update 14 | * @prop {(expected: T, strict?: boolean)=>Promise} when Returns a promise which resolves when the store value matches the expected value 15 | */ 16 | 17 | /** 18 | * @template T 19 | * @typedef {object} Readable 20 | * @prop {function():T} get 21 | * @prop {function((payload: T, unsub: Unsubscribe)=>void):Unsubscribe} subscribe Subscribe to the store value 22 | * @prop {function(function(T):void):void} next to the store value for a single update 23 | */ 24 | 25 | /** 26 | * @template T 27 | * @typedef {Object} storeOptions 28 | * @prop {boolean} lazy only call listeners if value has changed 29 | * @prop {function(Writable):void} onSub called when a listener subscribes 30 | * @prop {function(Writable):void} onUnsub called when a listener unsubscribes 31 | * @prop {function(Writable):void} onFirstSub called when the first listener subscribes 32 | * @prop {function(Writable):void} onLastUnsub Called when the last listener unsubscribes 33 | */ 34 | 35 | /** 36 | * @template T 37 | * @typedef { T extends Readable ? U : never } StoreValue 38 | */ 39 | 40 | /** 41 | * @template T 42 | * @typedef {{ [K in keyof T]: T[K] extends Readable ? U : never }} StoreValues 43 | */ 44 | -------------------------------------------------------------------------------- /types/typedef.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('../src/PyMakr').PyMakr} PyMakr 3 | * @typedef {import('../src/Device').Device} Device 4 | * @typedef {import('../src/Project').Project} Project 5 | * @typedef {import('vscode')} vscode 6 | */ 7 | 8 | /** 9 | * @typedef {Object} Config 10 | */ 11 | 12 | /** 13 | * @typedef {import("@serialport/bindings-cpp").PortInfo & {friendlyName: string}} DeviceInputRaw 14 | */ 15 | 16 | /** 17 | * @typedef {Object} DeviceInput 18 | * @prop {string} name 19 | * @prop {'serial'|'telnet'} protocol 20 | * @prop {string} address 21 | * @prop {string=} username 22 | * @prop {string=} password 23 | * @prop {string=} id if not specified, "://
" will be used 24 | * @prop {DeviceInputRaw=} raw 25 | */ 26 | 27 | /** 28 | * @typedef {Object} ProtocolAndAddress 29 | * @prop {string} protocol 30 | * @prop {string} address 31 | */ 32 | 33 | /** 34 | * @typedef {import('../src/providers/ProjectsProvider').ProjectTreeItem} ProjectTreeItem 35 | * @typedef {import('../src/providers/DevicesProvider').DeviceTreeItem} DeviceTreeItem 36 | * @typedef {import('../src/providers/ProjectsProvider').ProjectDeviceTreeItem} ProjectDeviceTreeItem 37 | * @typedef {DeviceTreeItem | ProjectDeviceTreeItem} AnyDeviceTreeItem 38 | * @typedef {import('vscode').TreeItem|import('vscode').Uri|string|Project} projectRef 39 | */ 40 | 41 | /** 42 | * @typedef {object} PymakrConfFile 43 | * @prop {string[]} py_ignore 44 | * @prop {string} name 45 | * @prop {string} dist_dir 46 | * @prop {boolean} ctrl_c_on_connect 47 | * @prop {boolean} reboot_after_upload 48 | * @prop {boolean} safe_boot_on_upload 49 | * @prop {PymakrConfFile_Dev} dev 50 | */ 51 | 52 | /** 53 | * @typedef {object} PymakrConfFile_Dev 54 | * @prop {'always'|'never'|'outOfSync'} uploadOnDevStart Uploads project to device when dev mode is started 55 | * @prop {'restartScript'|'softRestartDevice'|'hardRestartDevice'} onUpdate Action to run after file changes have been propagates 56 | * @prop {boolean} simulateDeepSleep Replaces deepsleep with\r\ntime.sleep(x)\nmachine.reset() 57 | */ 58 | --------------------------------------------------------------------------------